| // 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 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. | |
| */ | |
| 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. | |
| */ | |
| 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; | |
| } | |
| } |