Home | History | Annotate | Download | only in testing
      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 
     36   public OptionsTester(Class<? extends OptionsBase> optionsClass) {
     37     this.optionsClass = optionsClass;
     38   }
     39 
     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    */
     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    */
     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