| // Copyright 2017 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.devtools.common.options.testing; |
| |
| import static com.google.common.truth.Truth.assertWithMessage; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableListMultimap; |
| import com.google.devtools.common.options.Converter; |
| import com.google.devtools.common.options.Option; |
| import com.google.devtools.common.options.OptionsBase; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| |
| /** |
| * A tester to validate certain useful properties of OptionsBase subclasses. These are not required |
| * for parsing options in these classes, but can be helpful for e.g. ensuring that equality is not |
| * violated. |
| */ |
| public final class OptionsTester { |
| |
| private final Class<? extends OptionsBase> optionsClass; |
| |
| public OptionsTester(Class<? extends OptionsBase> optionsClass) { |
| this.optionsClass = optionsClass; |
| } |
| |
| private static ImmutableList<Field> getAllFields(Class<? extends OptionsBase> optionsClass) { |
| ImmutableList.Builder<Field> builder = ImmutableList.builder(); |
| Class<? extends OptionsBase> current = optionsClass; |
| while (!OptionsBase.class.equals(current)) { |
| builder.add(current.getDeclaredFields()); |
| // the input extends OptionsBase and we haven't seen OptionsBase yet, so this must also extend |
| // (or be) OptionsBase |
| @SuppressWarnings("unchecked") |
| Class<? extends OptionsBase> superclass = |
| (Class<? extends OptionsBase>) current.getSuperclass(); |
| current = superclass; |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Tests that there are no non-Option instance fields. Fields not annotated with @Option will not |
| * be considered for equality. |
| */ |
| @CanIgnoreReturnValue |
| public OptionsTester testAllInstanceFieldsAnnotatedWithOption() { |
| for (Field field : getAllFields(optionsClass)) { |
| if (!Modifier.isStatic(field.getModifiers())) { |
| assertWithMessage( |
| field |
| + " is missing an @Option annotation; it will not be considered for equality.") |
| .that(field.getAnnotation(Option.class)) |
| .isNotNull(); |
| } |
| } |
| return this; |
| } |
| |
| /** |
| * Tests that the default values of this class were part of the test data for the appropriate |
| * ConverterTester, ensuring that the defaults at least obey proper equality semantics. |
| * |
| * <p>The default converters are not tested in this way. |
| * |
| * <p>Note that testConvert is not actually run on the ConverterTesters; it is expected that they |
| * are run elsewhere. |
| */ |
| @CanIgnoreReturnValue |
| public OptionsTester testAllDefaultValuesTestedBy(ConverterTesterMap testers) { |
| ImmutableListMultimap.Builder<Class<? extends Converter<?>>, Field> converterClassesBuilder = |
| ImmutableListMultimap.builder(); |
| for (Field field : getAllFields(optionsClass)) { |
| Option option = field.getAnnotation(Option.class); |
| if (option != null && !Converter.class.equals(option.converter())) { |
| @SuppressWarnings("unchecked") // converter is rawtyped; see comment on Option.converter() |
| Class<? extends Converter<?>> converter = |
| (Class<? extends Converter<?>>) option.converter(); |
| converterClassesBuilder.put(converter, field); |
| } |
| } |
| ImmutableListMultimap<Class<? extends Converter<?>>, Field> converterClasses = |
| converterClassesBuilder.build(); |
| for (Class<? extends Converter<?>> converter : converterClasses.keySet()) { |
| assertWithMessage( |
| "Converter " + converter.getCanonicalName() + " has no corresponding ConverterTester") |
| .that(testers) |
| .containsKey(converter); |
| for (Field field : converterClasses.get(converter)) { |
| Option option = field.getAnnotation(Option.class); |
| if (!option.allowMultiple() && !"null".equals(option.defaultValue())) { |
| assertWithMessage( |
| "Default value \"" |
| + option.defaultValue() |
| + "\" on " |
| + field |
| + " is not tested in the corresponding ConverterTester for " |
| + converter.getCanonicalName()) |
| .that(testers.get(converter).hasTestForInput(option.defaultValue())) |
| .isTrue(); |
| } |
| } |
| } |
| return this; |
| } |
| } |