• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Bazel Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //    http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.devtools.common.options.testing;
16 
17 import static com.google.common.truth.Truth.assertWithMessage;
18 
19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.ImmutableListMultimap;
21 import com.google.devtools.common.options.Converter;
22 import com.google.devtools.common.options.Option;
23 import com.google.devtools.common.options.OptionsBase;
24 import java.lang.reflect.Field;
25 import java.lang.reflect.Modifier;
26 
27 /**
28  * A tester to validate certain useful properties of OptionsBase subclasses. These are not required
29  * for parsing options in these classes, but can be helpful for e.g. ensuring that equality is not
30  * violated.
31  */
32 public final class OptionsTester {
33 
34   private final Class<? extends OptionsBase> optionsClass;
35 
OptionsTester(Class<? extends OptionsBase> optionsClass)36   public OptionsTester(Class<? extends OptionsBase> optionsClass) {
37     this.optionsClass = optionsClass;
38   }
39 
getAllFields(Class<? extends OptionsBase> optionsClass)40   private static ImmutableList<Field> getAllFields(Class<? extends OptionsBase> optionsClass) {
41     ImmutableList.Builder<Field> builder = ImmutableList.builder();
42     Class<? extends OptionsBase> current = optionsClass;
43     while (!OptionsBase.class.equals(current)) {
44       builder.add(current.getDeclaredFields());
45       // the input extends OptionsBase and we haven't seen OptionsBase yet, so this must also extend
46       // (or be) OptionsBase
47       @SuppressWarnings("unchecked")
48       Class<? extends OptionsBase> superclass =
49           (Class<? extends OptionsBase>) current.getSuperclass();
50       current = superclass;
51     }
52     return builder.build();
53   }
54 
55   /**
56    * Tests that there are no non-Option instance fields. Fields not annotated with @Option will not
57    * be considered for equality.
58    */
testAllInstanceFieldsAnnotatedWithOption()59   public OptionsTester testAllInstanceFieldsAnnotatedWithOption() {
60     for (Field field : getAllFields(optionsClass)) {
61       if (!Modifier.isStatic(field.getModifiers())) {
62         assertWithMessage(
63                 field
64                     + " is missing an @Option annotation; it will not be considered for equality.")
65             .that(field.getAnnotation(Option.class))
66             .isNotNull();
67       }
68     }
69     return this;
70   }
71 
72   /**
73    * Tests that the default values of this class were part of the test data for the appropriate
74    * ConverterTester, ensuring that the defaults at least obey proper equality semantics.
75    *
76    * <p>The default converters are not tested in this way.
77    *
78    * <p>Note that testConvert is not actually run on the ConverterTesters; it is expected that they
79    * are run elsewhere.
80    */
testAllDefaultValuesTestedBy(ConverterTesterMap testers)81   public OptionsTester testAllDefaultValuesTestedBy(ConverterTesterMap testers) {
82     ImmutableListMultimap.Builder<Class<? extends Converter<?>>, Field> converterClassesBuilder =
83         ImmutableListMultimap.builder();
84     for (Field field : getAllFields(optionsClass)) {
85       Option option = field.getAnnotation(Option.class);
86       if (option != null && !Converter.class.equals(option.converter())) {
87         @SuppressWarnings("unchecked") // converter is rawtyped; see comment on Option.converter()
88         Class<? extends Converter<?>> converter =
89             (Class<? extends Converter<?>>) option.converter();
90         converterClassesBuilder.put(converter, field);
91       }
92     }
93     ImmutableListMultimap<Class<? extends Converter<?>>, Field> converterClasses =
94         converterClassesBuilder.build();
95     for (Class<? extends Converter<?>> converter : converterClasses.keySet()) {
96       assertWithMessage(
97               "Converter " + converter.getCanonicalName() + " has no corresponding ConverterTester")
98           .that(testers)
99           .containsKey(converter);
100       for (Field field : converterClasses.get(converter)) {
101         Option option = field.getAnnotation(Option.class);
102         if (!option.allowMultiple() && !"null".equals(option.defaultValue())) {
103           assertWithMessage(
104                   "Default value \""
105                       + option.defaultValue()
106                       + "\" on "
107                       + field
108                       + " is not tested in the corresponding ConverterTester for "
109                       + converter.getCanonicalName())
110               .that(testers.get(converter).hasTestForInput(option.defaultValue()))
111               .isTrue();
112         }
113       }
114     }
115     return this;
116   }
117 }
118