aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar mstaib <mstaib@google.com>2017-07-19 21:50:20 +0200
committerGravatar Klaus Aehlig <aehlig@google.com>2017-07-20 10:29:13 +0200
commit2cb56d53efb3964f1bd3ab3cb19f43ae7a2fdce0 (patch)
treea3fd9cbdf4ae538ccef5bd34c36c4fdf64bed7b9 /src
parentd1e564bbe72c9de5f22e5b6dc8af26ce7520bbc8 (diff)
Add test framework for OptionsBase classes and their Converters.
Because OptionsBase implements equals() as a final method, subclasses can only add fields in certain ways for OptionsBase to properly obey equals() semantics. Specifically, all fields must be public and @Option annotated. The OptionsTester checks for these two things. Additionally, Converters must make sure to always return equals() values on equals() (or equivalent) input. The OptionsTester includes a check that all Converters named by the OptionsBase subclass being tested have matching ConverterTesters, and if valid default values are specified (i.e., on Options which are not multi-valued or default null), that these defaults are among the values tested by the ConverterTesters. The ConverterTesters themselves are wrapped EqualsTesters, testing that the output of a Converter obeys equals() as expected for the same input (or equivalent ones), and is consistent across calls to the same Converter instance or different Converter instances. Between these two, OptionsBase subclasses can have reasonable certainty that two instances of themselves which were parsed equally - or underwent equivalent transformations - will be equal. This does not actually test any OptionsBase subclasses or Converter implementations; it merely adds a framework. Future changes will cover automatically testing all of the OptionsBase subclasses in a RuleClassProvider, but naturally, this requires writing test data for each Converter in the Bazel codebase first. RELNOTES: None. PiperOrigin-RevId: 162522445
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/google/devtools/common/options/BUILD2
-rw-r--r--src/main/java/com/google/devtools/common/options/testing/BUILD29
-rw-r--r--src/main/java/com/google/devtools/common/options/testing/ConverterTester.java198
-rw-r--r--src/main/java/com/google/devtools/common/options/testing/ConverterTesterMap.java84
-rw-r--r--src/main/java/com/google/devtools/common/options/testing/OptionsTester.java140
-rw-r--r--src/test/java/com/google/devtools/common/options/BUILD2
-rw-r--r--src/test/java/com/google/devtools/common/options/testing/AllTests.java25
-rw-r--r--src/test/java/com/google/devtools/common/options/testing/BUILD38
-rw-r--r--src/test/java/com/google/devtools/common/options/testing/ConverterTesterMapTest.java120
-rw-r--r--src/test/java/com/google/devtools/common/options/testing/ConverterTesterTest.java407
-rw-r--r--src/test/java/com/google/devtools/common/options/testing/OptionsTesterTest.java434
11 files changed, 1477 insertions, 2 deletions
diff --git a/src/main/java/com/google/devtools/common/options/BUILD b/src/main/java/com/google/devtools/common/options/BUILD
index cd52dd5b1d..949720187b 100644
--- a/src/main/java/com/google/devtools/common/options/BUILD
+++ b/src/main/java/com/google/devtools/common/options/BUILD
@@ -36,5 +36,5 @@ java_library(
filegroup(
name = "srcs",
testonly = 0, # All srcs should be not test only, overwrite package default.
- srcs = glob(["**"]),
+ srcs = glob(["**"]) + ["//src/main/java/com/google/devtools/common/options/testing:srcs"],
)
diff --git a/src/main/java/com/google/devtools/common/options/testing/BUILD b/src/main/java/com/google/devtools/common/options/testing/BUILD
new file mode 100644
index 0000000000..fc57c99d03
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/testing/BUILD
@@ -0,0 +1,29 @@
+# Description:
+# Testing tools for the devtools-common options parser.
+package(
+ default_testonly = 1,
+ default_visibility = ["//src:__subpackages__"],
+)
+
+licenses(["notice"]) # Apache 2.0
+
+filegroup(
+ name = "srcs",
+ testonly = 0,
+ srcs = glob(
+ ["**"],
+ ),
+ visibility = ["//src/main/java/com/google/devtools/common/options:__pkg__"],
+)
+
+java_library(
+ name = "testing",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//src/main/java/com/google/devtools/common/options",
+ "//third_party:guava",
+ "//third_party:guava-testlib",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
diff --git a/src/main/java/com/google/devtools/common/options/testing/ConverterTester.java b/src/main/java/com/google/devtools/common/options/testing/ConverterTester.java
new file mode 100644
index 0000000000..7d0b8d3974
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/testing/ConverterTester.java
@@ -0,0 +1,198 @@
+// 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.testing.EqualsTester;
+import com.google.devtools.common.options.Converter;
+import com.google.devtools.common.options.OptionsParsingException;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+
+/**
+ * A tester to confirm that {@link Converter} instances produce equal results on multiple calls with
+ * the same input.
+ */
+public final class ConverterTester {
+
+ private final Converter<?> converter;
+ private final Class<? extends Converter<?>> converterClass;
+ private final EqualsTester tester = new EqualsTester();
+ private final LinkedHashSet<String> testedInputs = new LinkedHashSet<>();
+ private final ArrayList<ImmutableList<String>> inputLists = new ArrayList<>();
+
+ /** Creates a new ConverterTester which will test the given Converter class. */
+ public ConverterTester(Class<? extends Converter<?>> converterClass) {
+ this.converterClass = converterClass;
+ this.converter = createConverter();
+ }
+
+ private Converter<?> createConverter() {
+ try {
+ return converterClass.getDeclaredConstructor().newInstance();
+ } catch (ReflectiveOperationException ex) {
+ throw new AssertionError("Failed to create converter", ex);
+ }
+ }
+
+ /** Returns the class this ConverterTester is testing. */
+ public Class<? extends Converter<?>> getConverterClass() {
+ return converterClass;
+ }
+
+ /**
+ * Returns whether this ConverterTester has a test for the given input, i.e., addEqualityGroup
+ * was called with the given string.
+ */
+ public boolean hasTestForInput(String input) {
+ return testedInputs.contains(input);
+ }
+
+ /**
+ * Adds a set of valid inputs which are expected to convert to equal values.
+ *
+ * <p>The inputs added here will be converted to values using the Converter class passed to the
+ * constructor of this instance; the resulting values must be equal (and have equal hashCodes):
+ *
+ * <ul>
+ * <li>to themselves
+ * <li>to another copy of themselves generated from the same Converter instance
+ * <li>to another copy of themselves generated from a different Converter instance
+ * <li>to the other values converted from inputs in the same addEqualityGroup call
+ * </ul>
+ *
+ * <p>They must NOT be equal:
+ *
+ * <ul>
+ * <li>to null
+ * <li>to an instance of an arbitrary class
+ * <li>to any values converted from inputs in a different addEqualityGroup call
+ * </ul>
+ *
+ * @throws AssertionError if an {@link OptionsParsingException} is thrown from the
+ * {@link Converter#convert} method when converting any of the inputs.
+ * @see EqualsTester#addEqualityGroup
+ */
+ public ConverterTester addEqualityGroup(String... inputs) {
+ ImmutableList.Builder<WrappedItem> wrapped = new ImmutableList.Builder<>();
+ ImmutableList<String> inputList = ImmutableList.copyOf(inputs);
+ inputLists.add(inputList);
+ for (String input : inputList) {
+ testedInputs.add(input);
+ try {
+ wrapped.add(new WrappedItem(input, converter.convert(input)));
+ } catch (OptionsParsingException ex) {
+ throw new AssertionError("Failed to parse input: \"" + input + "\"", ex);
+ }
+ }
+ tester.addEqualityGroup(wrapped.build().toArray());
+ return this;
+ }
+
+ /**
+ * Tests the convert method of the wrapped Converter class, verifying the properties listed in the
+ * Javadoc listed for {@link #addEqualityGroup}.
+ *
+ * @throws AssertionError if one of the expected properties did not hold up
+ * @see EqualsTester#testEquals
+ */
+ public ConverterTester testConvert() {
+ tester.testEquals();
+ testItems();
+ return this;
+ }
+
+ private void testItems() {
+ for (ImmutableList<String> inputList : inputLists) {
+ for (String input : inputList) {
+ Converter<?> converter = createConverter();
+ Converter<?> converter2 = createConverter();
+
+ Object converted;
+ Object convertedAgain;
+ Object convertedDifferentConverterInstance;
+ try {
+ converted = converter.convert(input);
+ convertedAgain = converter.convert(input);
+ convertedDifferentConverterInstance = converter2.convert(input);
+ } catch (OptionsParsingException ex) {
+ throw new AssertionError("Failed to parse input: \"" + input + "\"", ex);
+ }
+
+ assertWithMessage(
+ "Input \""
+ + input
+ + "\" was not equal to itself when converted twice by the same Converter")
+ .that(convertedAgain)
+ .isEqualTo(converted);
+ assertWithMessage(
+ "Input \""
+ + input
+ + "\" did not have a consistent hashCode when converted twice "
+ + "by the same Converter")
+ .that(convertedAgain.hashCode())
+ .isEqualTo(converted.hashCode());
+ assertWithMessage(
+ "Input \""
+ + input
+ + "\" was not equal to itself when converted twice by a different Converter")
+ .that(convertedDifferentConverterInstance)
+ .isEqualTo(converted);
+ assertWithMessage(
+ "Input \""
+ + input
+ + "\" did not have a consistent hashCode when converted twice "
+ + "by a different Converter")
+ .that(convertedDifferentConverterInstance.hashCode())
+ .isEqualTo(converted.hashCode());
+ }
+ }
+ }
+
+ /**
+ * A wrapper around the objects passed to EqualsTester to give them a more useful toString() so
+ * that the mapping between the input text which actually appears in the source file and the
+ * object produced from parsing it is more obvious.
+ */
+ private static final class WrappedItem {
+ private final String argument;
+ private final Object wrapped;
+
+ private WrappedItem(String argument, Object wrapped) {
+ this.argument = argument;
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Converted input \"%s\" => [%s]", argument, wrapped);
+ }
+
+ @Override
+ public int hashCode() {
+ return wrapped.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof WrappedItem) {
+ return this.wrapped.equals(((WrappedItem) other).wrapped);
+ }
+ return this.wrapped.equals(other);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/common/options/testing/ConverterTesterMap.java b/src/main/java/com/google/devtools/common/options/testing/ConverterTesterMap.java
new file mode 100644
index 0000000000..afa0231ea7
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/testing/ConverterTesterMap.java
@@ -0,0 +1,84 @@
+// 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 com.google.common.collect.ForwardingMap;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.common.options.Converter;
+import java.util.Map;
+
+/**
+ * An immutable mapping from {@link Converter} classes to {@link ConverterTester}s which test them.
+ *
+ * <p>Note that the ConverterTesters within are NOT immutable.
+ */
+public final class ConverterTesterMap
+ extends ForwardingMap<Class<? extends Converter<?>>, ConverterTester> {
+
+ private final ImmutableMap<Class<? extends Converter<?>>, ConverterTester> delegate;
+
+ private ConverterTesterMap(
+ ImmutableMap<Class<? extends Converter<?>>, ConverterTester> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ protected Map<Class<? extends Converter<?>>, ConverterTester> delegate() {
+ return delegate;
+ }
+
+ /** A builder to construct new {@link ConverterTesterMap}s. */
+ public static final class Builder {
+ private final ImmutableMap.Builder<Class<? extends Converter<?>>, ConverterTester> delegate;
+
+ public Builder() {
+ this.delegate = new ImmutableMap.Builder<>();
+ }
+
+ /**
+ * Adds a new ConverterTester, mapping it to the class of converter it tests. Only one tester
+ * for each class is permitted; duplicates will cause {@link #build} to fail.
+ */
+ public Builder add(ConverterTester item) {
+ delegate.put(item.getConverterClass(), item);
+ return this;
+ }
+
+ /**
+ * Adds the entries from the given {@link ConverterTesterMap}. Only one tester for each class is
+ * permitted; duplicates will cause {@link #build} to fail.
+ */
+ public Builder addAll(ConverterTesterMap map) {
+ // this is safe because we know the other map was constructed the same way this one was
+ delegate.putAll(map);
+ return this;
+ }
+
+ /**
+ * Adds all of the ConverterTesters from the given iterable. Only one tester for each class is
+ * permitted; duplicates will cause {@link #build} to fail.
+ */
+ public Builder addAll(Iterable<ConverterTester> items) {
+ for (ConverterTester item : items) {
+ add(item);
+ }
+ return this;
+ }
+
+ public ConverterTesterMap build() {
+ return new ConverterTesterMap(delegate.build());
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/common/options/testing/OptionsTester.java b/src/main/java/com/google/devtools/common/options/testing/OptionsTester.java
new file mode 100644
index 0000000000..fa58462494
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/testing/OptionsTester.java
@@ -0,0 +1,140 @@
+// 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 = new 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 there are no non-public fields which would interfere with option parsing. */
+ public OptionsTester testAllOptionFieldsPublic() {
+ for (Field field : getAllFields(optionsClass)) {
+ if (field.isAnnotationPresent(Option.class)) {
+ assertWithMessage(
+ field
+ + " is Option-annotated, but is not public; it will not be considered as part"
+ + " of the options. Change the visibility to public.")
+ .that(Modifier.isPublic(field.getModifiers()))
+ .isTrue();
+ }
+ if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) {
+ assertWithMessage(
+ field
+ + " is Option-annotated, but is either static or final; it cannot be properly"
+ + " set by the option parser. Remove either the annotation or the modifier(s).")
+ .that(field.getAnnotation(Option.class))
+ .isNull();
+ }
+ }
+ 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 =
+ new 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;
+ }
+}
diff --git a/src/test/java/com/google/devtools/common/options/BUILD b/src/test/java/com/google/devtools/common/options/BUILD
index 87e8a7db92..a0a4e36fd7 100644
--- a/src/test/java/com/google/devtools/common/options/BUILD
+++ b/src/test/java/com/google/devtools/common/options/BUILD
@@ -1,6 +1,6 @@
filegroup(
name = "srcs",
- srcs = glob(["**"]),
+ srcs = glob(["**"]) + ["//src/test/java/com/google/devtools/common/options/testing:srcs"],
visibility = ["//src:__pkg__"],
)
diff --git a/src/test/java/com/google/devtools/common/options/testing/AllTests.java b/src/test/java/com/google/devtools/common/options/testing/AllTests.java
new file mode 100644
index 0000000000..4edbfda878
--- /dev/null
+++ b/src/test/java/com/google/devtools/common/options/testing/AllTests.java
@@ -0,0 +1,25 @@
+// Copyright 2014 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 com.google.devtools.build.lib.testutil.ClasspathSuite;
+
+import org.junit.runner.RunWith;
+
+/**
+ * Test suite for options parsing framework.
+ */
+@RunWith(ClasspathSuite.class)
+public class AllTests {
+}
diff --git a/src/test/java/com/google/devtools/common/options/testing/BUILD b/src/test/java/com/google/devtools/common/options/testing/BUILD
new file mode 100644
index 0000000000..0e2d3e15cc
--- /dev/null
+++ b/src/test/java/com/google/devtools/common/options/testing/BUILD
@@ -0,0 +1,38 @@
+# Description:
+# Tests of the testing tools for the devtools-common options parser.
+package(
+ default_testonly = 1,
+ default_visibility = ["//visibility:private"],
+)
+
+licenses(["notice"]) # Apache 2.0
+
+filegroup(
+ name = "srcs",
+ testonly = 0,
+ srcs = glob(
+ ["**"],
+ ),
+ visibility = ["//src/test/java/com/google/devtools/common/options:__pkg__"],
+)
+
+java_library(
+ name = "OptionsTesterTest_lib",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//src/main/java/com/google/devtools/common/options",
+ "//src/main/java/com/google/devtools/common/options/testing",
+ "//src/main/protobuf:option_filters_java_proto",
+ "//src/test/java/com/google/devtools/build/lib:testutil",
+ "//third_party:guava",
+ "//third_party:guava-testlib",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
+
+java_test(
+ name = "OptionsTesterTest",
+ test_class = "com.google.devtools.common.options.testing.AllTests",
+ runtime_deps = [":OptionsTesterTest_lib"],
+)
diff --git a/src/test/java/com/google/devtools/common/options/testing/ConverterTesterMapTest.java b/src/test/java/com/google/devtools/common/options/testing/ConverterTesterMapTest.java
new file mode 100644
index 0000000000..0265f13b6a
--- /dev/null
+++ b/src/test/java/com/google/devtools/common/options/testing/ConverterTesterMapTest.java
@@ -0,0 +1,120 @@
+// 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.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.common.options.Converters;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the ConverterTesterMap map builder. */
+@RunWith(JUnit4.class)
+public final class ConverterTesterMapTest {
+
+ @Test
+ public void add_mapsTestedConverterClassToTester() throws Exception {
+ ConverterTester stringTester = new ConverterTester(Converters.StringConverter.class);
+ ConverterTester intTester = new ConverterTester(Converters.IntegerConverter.class);
+ ConverterTester doubleTester = new ConverterTester(Converters.DoubleConverter.class);
+ ConverterTester booleanTester = new ConverterTester(Converters.BooleanConverter.class);
+ ConverterTesterMap map =
+ new ConverterTesterMap.Builder()
+ .add(stringTester)
+ .add(intTester)
+ .add(doubleTester)
+ .add(booleanTester)
+ .build();
+ assertThat(map)
+ .containsExactly(
+ Converters.StringConverter.class,
+ stringTester,
+ Converters.IntegerConverter.class,
+ intTester,
+ Converters.DoubleConverter.class,
+ doubleTester,
+ Converters.BooleanConverter.class,
+ booleanTester);
+ }
+
+ @Test
+ public void addAll_mapsTestedConverterClassesToTester() throws Exception {
+ ConverterTester stringTester = new ConverterTester(Converters.StringConverter.class);
+ ConverterTester intTester = new ConverterTester(Converters.IntegerConverter.class);
+ ConverterTester doubleTester = new ConverterTester(Converters.DoubleConverter.class);
+ ConverterTester booleanTester = new ConverterTester(Converters.BooleanConverter.class);
+ ConverterTesterMap map =
+ new ConverterTesterMap.Builder()
+ .addAll(ImmutableList.of(stringTester, intTester, doubleTester, booleanTester))
+ .build();
+ assertThat(map)
+ .containsExactly(
+ Converters.StringConverter.class,
+ stringTester,
+ Converters.IntegerConverter.class,
+ intTester,
+ Converters.DoubleConverter.class,
+ doubleTester,
+ Converters.BooleanConverter.class,
+ booleanTester);
+ }
+
+ @Test
+ public void addAll_dumpsConverterTesterMapIntoNewMap() throws Exception {
+ ConverterTester stringTester = new ConverterTester(Converters.StringConverter.class);
+ ConverterTester intTester = new ConverterTester(Converters.IntegerConverter.class);
+ ConverterTester doubleTester = new ConverterTester(Converters.DoubleConverter.class);
+ ConverterTester booleanTester = new ConverterTester(Converters.BooleanConverter.class);
+ ConverterTesterMap baseMap =
+ new ConverterTesterMap.Builder()
+ .addAll(ImmutableList.of(stringTester, intTester, doubleTester))
+ .build();
+ ConverterTesterMap map =
+ new ConverterTesterMap.Builder().addAll(baseMap).add(booleanTester).build();
+ assertThat(map)
+ .containsExactly(
+ Converters.StringConverter.class,
+ stringTester,
+ Converters.IntegerConverter.class,
+ intTester,
+ Converters.DoubleConverter.class,
+ doubleTester,
+ Converters.BooleanConverter.class,
+ booleanTester);
+ }
+
+ @Test
+ public void build_forbidsDuplicates() throws Exception {
+ ConverterTesterMap.Builder builder =
+ new ConverterTesterMap.Builder()
+ .add(new ConverterTester(Converters.StringConverter.class))
+ .add(new ConverterTester(Converters.IntegerConverter.class))
+ .add(new ConverterTester(Converters.DoubleConverter.class))
+ .add(new ConverterTester(Converters.BooleanConverter.class))
+ .add(new ConverterTester(Converters.BooleanConverter.class));
+
+ try {
+ builder.build();
+ fail("expected build() with duplicate to fail");
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(Converters.BooleanConverter.class.getSimpleName());
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/common/options/testing/ConverterTesterTest.java b/src/test/java/com/google/devtools/common/options/testing/ConverterTesterTest.java
new file mode 100644
index 0000000000..947791dc87
--- /dev/null
+++ b/src/test/java/com/google/devtools/common/options/testing/ConverterTesterTest.java
@@ -0,0 +1,407 @@
+// 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.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.devtools.common.options.Converter;
+import com.google.devtools.common.options.Converters;
+import com.google.devtools.common.options.OptionsParsingException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests to exercise the functionality of {@link ConverterTester}. */
+@RunWith(JUnit4.class)
+public final class ConverterTesterTest {
+
+ @Test
+ public void construction_throwsAssertionErrorIfConverterCreationFails() throws Exception {
+ try {
+ new ConverterTester(UnconstructableConverter.class);
+ } catch (AssertionError expected) {
+ assertThat(expected) // AssertionError
+ .hasCauseThat() // InvocationTargetException
+ .hasCauseThat() // UnsupportedOperationException
+ .hasMessageThat()
+ .contains("YOU CAN'T MAKE ME!");
+ return;
+ }
+ fail("expected tester creation to fail");
+ }
+
+ /** Test converter for construction_throwsAssertionErrorIfConverterCreationFails. */
+ public static final class UnconstructableConverter implements Converter<String> {
+ public UnconstructableConverter() {
+ throw new UnsupportedOperationException("YOU CAN'T MAKE ME!");
+ }
+
+ @Override
+ public String convert(String input) throws OptionsParsingException {
+ return input;
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "anything, if you can get an instance";
+ }
+ }
+
+ @Test
+ public void getConverterClass_returnsConstructorArg() throws Exception {
+ ConverterTester tester = new ConverterTester(Converters.BooleanConverter.class);
+ assertThat(tester.getConverterClass()).isEqualTo(Converters.BooleanConverter.class);
+ }
+
+ @Test
+ public void hasTestForInput_returnsTrueIffInputPassedToAddEqualityGroup() throws Exception {
+ ConverterTester tester =
+ new ConverterTester(Converters.DoubleConverter.class)
+ .addEqualityGroup("1.0", "1", "1.00")
+ .addEqualityGroup("2");
+
+ assertThat(tester.hasTestForInput("1.0")).isTrue();
+ assertThat(tester.hasTestForInput("1")).isTrue();
+ assertThat(tester.hasTestForInput("1.00")).isTrue();
+ assertThat(tester.hasTestForInput("2")).isTrue();
+
+ assertThat(tester.hasTestForInput("3")).isFalse();
+ assertThat(tester.hasTestForInput("1.000")).isFalse();
+ assertThat(tester.hasTestForInput("not a double")).isFalse();
+ }
+
+ @Test
+ public void addEqualityGroup_throwsIfConversionFails() throws Exception {
+ ConverterTester tester =
+ new ConverterTester(ThrowingConverter.class)
+ .addEqualityGroup("okay")
+ .addEqualityGroup("also okay", "pretty fine");
+ try {
+ tester.addEqualityGroup("wrong");
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("\"wrong\"");
+ assertThat(expected).hasCauseThat().hasMessageThat().contains("HOW DARE YOU");
+ return;
+ }
+ fail("expected addEqualityGroup to fail");
+ }
+
+ /** Test converter for addEqualityGroup_throwsIfConversionFails. */
+ public static final class ThrowingConverter implements Converter<String> {
+ @Override
+ public String convert(String input) throws OptionsParsingException {
+ if ("wrong".equals(input)) {
+ throw new OptionsParsingException("HOW DARE YOU");
+ }
+ return input;
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "just don't give the wrong answer";
+ }
+ }
+
+ @Test
+ public void testConvert_passesWhenAllInstancesObeyEqualsAndItemsOnlyEqualToOthersInSameGroup() {
+ new ConverterTester(Converters.DoubleConverter.class)
+ .addEqualityGroup("1.0", "1", "1.00")
+ .addEqualityGroup("2", "2", "2.0000", "2.0", "+2")
+ .addEqualityGroup("3")
+ .addEqualityGroup("3.1415")
+ .testConvert();
+ }
+
+ @Test
+ public void testConvert_testsHashCodeConsistencyForConvertedInstance() {
+ ConverterTester tester =
+ new ConverterTester(InconsistentHashCodeConverter.class)
+ .addEqualityGroup("input doesn't matter");
+ try {
+ tester.testConvert();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("\"input doesn't matter\"");
+ assertThat(expected).hasMessageThat().contains("hashCode");
+ assertThat(expected).hasMessageThat().contains("must be consistent");
+ return;
+ }
+ fail("expected the tester to notice the bad hash code implementation");
+ }
+
+ /** A class with a badly implemented hashCode which is not consistent across calls. */
+ public static final class InconsistentHashCode {
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof InconsistentHashCode;
+ }
+
+ private int howManyTimesHaveIBeenHashedAlready = 0;
+
+ @Override
+ public int hashCode() {
+ howManyTimesHaveIBeenHashedAlready += 1;
+ return howManyTimesHaveIBeenHashedAlready;
+ }
+ }
+
+ /** Test converter for testConvert_testsHashCodeConsistencyForConvertedInstance. */
+ public static final class InconsistentHashCodeConverter
+ implements Converter<InconsistentHashCode> {
+ @Override
+ public InconsistentHashCode convert(String input) throws OptionsParsingException {
+ return new InconsistentHashCode();
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "anything, I don't even look at it";
+ }
+ }
+
+ @Test
+ public void testConvert_testsHashCodeConsistencyForSameConverter() {
+ ConverterTester tester =
+ new ConverterTester(IncrementingHashCodeConverter.class)
+ .addEqualityGroup("meaningless input");
+ try {
+ tester.testConvert();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("\"meaningless input\"");
+ assertThat(expected).hasMessageThat().contains("consistent hashCode");
+ assertThat(expected).hasMessageThat().contains("same Converter");
+ return;
+ }
+ fail("expected the tester to notice the mismatched hash codes");
+ }
+
+ /** A class with a configurable hashCode set in the constructor. */
+ public static final class SettableHashCode {
+ private final int hashCode;
+
+ public SettableHashCode(int hashCode) {
+ this.hashCode = hashCode;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof SettableHashCode;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+
+ /** Test converter for testConvert_testsHashCodeConsistencyForSameConverter. */
+ public static final class IncrementingHashCodeConverter
+ implements Converter<SettableHashCode> {
+ private int howManyInstancesHaveIMadeAlready = 0;
+
+ @Override
+ public SettableHashCode convert(String input) throws OptionsParsingException {
+ howManyInstancesHaveIMadeAlready += 1;
+ return new SettableHashCode(howManyInstancesHaveIMadeAlready);
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "whatever, I'm pretty much just going to ignore it";
+ }
+ }
+
+ @Test
+ public void testConvert_testsHashCodeConsistencyForDifferentConverters() {
+ ConverterTester tester =
+ new ConverterTester(StaticIncrementingHashCodeConverter.class)
+ .addEqualityGroup("some kind of input");
+ try {
+ tester.testConvert();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("\"some kind of input\"");
+ assertThat(expected).hasMessageThat().contains("consistent hashCode");
+ assertThat(expected).hasMessageThat().contains("different Converter");
+ return;
+ }
+ fail("expected the tester to notice the mismatched hash codes");
+ }
+
+ /** Test converter for testConvert_testsHashCodeConsistencyForDifferentConverters. */
+ public static final class StaticIncrementingHashCodeConverter
+ implements Converter<SettableHashCode> {
+ private static int howManyInstancesHaveIMadeAlready = 0;
+
+ private final int hashCode;
+
+ public StaticIncrementingHashCodeConverter() {
+ howManyInstancesHaveIMadeAlready += 1;
+ this.hashCode = howManyInstancesHaveIMadeAlready;
+ }
+
+ @Override
+ public SettableHashCode convert(String input) throws OptionsParsingException {
+ return new SettableHashCode(hashCode);
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a string or null, I'm easy";
+ }
+ }
+
+
+ @Test
+ public void testConvert_testsSelfEqualityForConvertedInstance() {
+ ConverterTester tester =
+ new ConverterTester(SelfLoathingConverter.class)
+ .addEqualityGroup("self-loathing");
+ try {
+ tester.testConvert();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("\"self-loathing\"");
+ assertThat(expected).hasMessageThat().contains("must be Object#equals to itself");
+ return;
+ }
+ fail("expected the tester to notice the bad equals implementation");
+ }
+
+ /** A class which is equal to every instance of its class except itself. */
+ public static final class SelfLoathingObject {
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof SelfLoathingObject && other != this;
+ }
+
+ @Override
+ public int hashCode() {
+ return 4; // chosen by fair hashing algorithm
+ }
+ }
+
+ /** Test converter for testConvert_testsSelfEqualityForConvertedInstance. */
+ public static final class SelfLoathingConverter implements Converter<SelfLoathingObject> {
+ @Override
+ public SelfLoathingObject convert(String input) throws OptionsParsingException {
+ return new SelfLoathingObject();
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "whatever... why even ask me to convert anything..............";
+ }
+ }
+
+ @Test
+ public void testConvert_testsEqualityForSameConverter() {
+ ConverterTester tester =
+ new ConverterTester(CountingConverter.class)
+ .addEqualityGroup("countables");
+ try {
+ tester.testConvert();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("\"countables\"");
+ assertThat(expected).hasMessageThat().contains("equal to itself");
+ assertThat(expected).hasMessageThat().contains("same Converter");
+ return;
+ }
+ fail("expected the tester to notice the converter giving unequal objects");
+ }
+
+ /** Test converter for testConvert_testsEqualityForSameConverter. */
+ public static final class CountingConverter implements Converter<Integer> {
+ private int howManyInstancesHaveIMadeAlready = 0;
+
+ @Override
+ public Integer convert(String input) throws OptionsParsingException {
+ howManyInstancesHaveIMadeAlready += 1;
+ return howManyInstancesHaveIMadeAlready;
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "I can count anything!";
+ }
+ }
+
+ @Test
+ public void testConvert_testsEqualityForDifferentConverters() {
+ ConverterTester tester =
+ new ConverterTester(StaticCountingConverter.class)
+ .addEqualityGroup("words I like");
+ try {
+ tester.testConvert();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("\"words I like\"");
+ assertThat(expected).hasMessageThat().contains("equal to itself");
+ assertThat(expected).hasMessageThat().contains("different Converter");
+ return;
+ }
+ fail("expected the tester to notice the converters giving unequal objects");
+ }
+
+ /** Test converter for testConvert_testsEqualityForDifferentConverters. */
+ public static final class StaticCountingConverter implements Converter<Integer> {
+ private static int howManyInstancesHaveIMadeAlready = 0;
+
+ private final int output;
+
+ public StaticCountingConverter() {
+ howManyInstancesHaveIMadeAlready += 1;
+ this.output = howManyInstancesHaveIMadeAlready;
+ }
+
+ @Override
+ public Integer convert(String input) throws OptionsParsingException {
+ return output;
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "your favorite text";
+ }
+ }
+
+ @Test
+ public void testConvert_testsEqualityForItemsInSameGroup() {
+ ConverterTester tester =
+ new ConverterTester(Converters.DoubleConverter.class)
+ .addEqualityGroup("+1.000", "2.30");
+ try {
+ tester.testConvert();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("\"+1.000\"");
+ assertThat(expected).hasMessageThat().contains("\"2.30\"");
+ return;
+ }
+ fail("expected the tester to notice the two non-equal conversion results in the same group");
+ }
+
+ @Test
+ public void testConvert_testsNonEqualityForItemsInDifferentGroups() {
+ ConverterTester tester =
+ new ConverterTester(Converters.DoubleConverter.class)
+ .addEqualityGroup("+1.000")
+ .addEqualityGroup("1.0");
+ try {
+ tester.testConvert();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("\"+1.000\"");
+ assertThat(expected).hasMessageThat().contains("\"1.0\"");
+ return;
+ }
+ fail("expected the tester to notice the two equal conversion results in different groups");
+ }
+}
diff --git a/src/test/java/com/google/devtools/common/options/testing/OptionsTesterTest.java b/src/test/java/com/google/devtools/common/options/testing/OptionsTesterTest.java
new file mode 100644
index 0000000000..6c350ee9d1
--- /dev/null
+++ b/src/test/java/com/google/devtools/common/options/testing/OptionsTesterTest.java
@@ -0,0 +1,434 @@
+// 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.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.devtools.common.options.Converter;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionsBase;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the OptionsTester. */
+@RunWith(JUnit4.class)
+public final class OptionsTesterTest {
+
+ @Test
+ public void optionAnnotationCheck_PassesWhenAllFieldsAnnotated() throws Exception {
+ new OptionsTester(OptionAnnotationCheckAllFieldsAnnotated.class)
+ .testAllInstanceFieldsAnnotatedWithOption();
+ }
+
+ /** Test options class for optionAnnotationCheck_PassesWhenAllFieldsAnnotated. */
+ public static class BaseAllFieldsAnnotated extends OptionsBase {
+ @Option(
+ name = "public inherited field with annotation",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ defaultValue = "defaultFoo"
+ )
+ public String inheritedField;
+ }
+
+ /** Test options class for optionAnnotationCheck_PassesWhenAllFieldsAnnotated. */
+ public static final class OptionAnnotationCheckAllFieldsAnnotated extends BaseAllFieldsAnnotated {
+ @Option(
+ name = "public declared field with annotation",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ defaultValue = "defaultFoo"
+ )
+ public String publicField;
+
+ @Option(
+ name = "private declared field with annotation",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ defaultValue = "defaultFoo"
+ )
+ @SuppressWarnings("unused")
+ private String privateField;
+ }
+
+ @Test
+ public void optionAnnotationCheck_AllowsUnAnnotatedStaticFields() throws Exception {
+ new OptionsTester(OptionAnnotationCheckUnAnnotatedStaticField.class)
+ .testAllInstanceFieldsAnnotatedWithOption();
+ }
+
+ /** Test options class for optionAnnotationCheck_AllowsUnAnnotatedStaticFields. */
+ public static class BaseUnAnnotatedStaticField extends OptionsBase {
+ public static String parentClassUnAnnotatedStaticField;
+ }
+
+ /** Test options class for optionAnnotationCheck_AllowsUnAnnotatedStaticFields. */
+ public static final class OptionAnnotationCheckUnAnnotatedStaticField
+ extends BaseUnAnnotatedStaticField {
+ @SuppressWarnings("unused")
+ private static String privateDeclaredUnAnnotatedStaticField;
+ }
+
+ @Test
+ public void optionAnnotationCheck_FailsWhenFieldNotAnnotated() throws Exception {
+ try {
+ new OptionsTester(OptionAnnotationCheckUnAnnotatedField.class)
+ .testAllInstanceFieldsAnnotatedWithOption();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("unAnnotatedField");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ /** Test options class for optionAnnotationCheck_FailsWhenFieldNotAnnotated. */
+ public static class OptionAnnotationCheckUnAnnotatedField extends OptionsBase {
+ public String unAnnotatedField;
+ }
+
+ @Test
+ public void optionAnnotationCheck_FailsForUnAnnotatedFieldsInSuperclass() throws Exception {
+ try {
+ new OptionsTester(OptionAnnotationCheckInheritedUnAnnotatedField.class)
+ .testAllInstanceFieldsAnnotatedWithOption();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("unAnnotatedField");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ /** Test options class for optionAnnotationCheck_FailsForUnAnnotatedFieldsInSuperclass. */
+ public static final class OptionAnnotationCheckInheritedUnAnnotatedField
+ extends OptionAnnotationCheckUnAnnotatedField {}
+
+ @Test
+ public void optionAnnotationCheck_FailsForPrivateUnAnnotatedField() throws Exception {
+ try {
+ new OptionsTester(OptionAnnotationCheckPrivateUnAnnotatedField.class)
+ .testAllInstanceFieldsAnnotatedWithOption();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("privateUnAnnotatedField");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ /** Test options class for optionAnnotationCheck_FailsForPrivateUnAnnotatedField. */
+ public static final class OptionAnnotationCheckPrivateUnAnnotatedField extends OptionsBase {
+ @SuppressWarnings("unused")
+ private String privateUnAnnotatedField;
+ }
+
+ @Test
+ public void optionAccessCheck_PassesWhenAllFieldsPublicNotStaticNotFinal() throws Exception {
+ new OptionsTester(OptionAccessCheckAllFieldsPublicNotStaticNotFinal.class)
+ .testAllOptionFieldsPublic();
+ }
+
+ /** Test options class for optionAccessCheck_PassesWhenAllFieldsPublicNotStaticNotFinal. */
+ public static class BaseAllFieldsPublicNotStaticNotFinal extends OptionsBase {
+ @Option(
+ name = "public inherited field with annotation",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ defaultValue = "defaultFoo"
+ )
+ public String inheritedField;
+ }
+
+ /** Test options class for optionAccessCheck_PassesWhenAllFieldsPublicNotStaticNotFinal. */
+ public static final class OptionAccessCheckAllFieldsPublicNotStaticNotFinal
+ extends BaseAllFieldsPublicNotStaticNotFinal {
+ @Option(
+ name = "public declared field with annotation",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ defaultValue = "defaultFoo"
+ )
+ public String annotatedField;
+ }
+
+ @Test
+ public void optionAccessCheck_IgnoresNonAnnotatedFields() throws Exception {
+ new OptionsTester(OptionAccessCheckNonAnnotatedFields.class).testAllOptionFieldsPublic();
+ }
+
+ /** Test options class for optionAccessCheck_IgnoresNonAnnotatedFields. */
+ public static class BaseNonAnnotatedFields extends OptionsBase {
+ protected static String parentClassUnAnnotatedStaticField;
+ final String parentClassUnAnnotatedFinalField = "";
+ }
+
+ /** Test options class for optionAccessCheck_IgnoresNonAnnotatedFields. */
+ public static final class OptionAccessCheckNonAnnotatedFields extends BaseNonAnnotatedFields {
+ @SuppressWarnings("unused")
+ private static String privateDeclaredUnAnnotatedStaticFinalField;
+
+ public static final String PUBLIC_DECLARED_UN_ANNOTATED_STATIC_FIELD = "";
+ protected String protectedDeclaredField;
+ }
+
+ @Test
+ public void optionAccessCheck_FailsForNonPublicFields() throws Exception {
+ try {
+ new OptionsTester(OptionAccessCheckNonPublicStaticField.class).testAllOptionFieldsPublic();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("protectedField");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ /** Test options class for optionAccessCheck_FailsForNonPublicFields. */
+ public static class OptionAccessCheckNonPublicStaticField extends OptionsBase {
+ @Option(
+ name = "protected declared field with annotation",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ defaultValue = "defaultFoo"
+ )
+ protected String protectedField;
+ }
+
+ @Test
+ public void optionAccessCheck_FailsForNonPublicFieldsInSuperclass() throws Exception {
+ try {
+ new OptionsTester(OptionAccessCheckInheritedUnAnnotatedField.class)
+ .testAllOptionFieldsPublic();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("protectedField");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ /** Test options class for optionAccessCheck_FailsForNonPublicFieldsInSuperclass. */
+ public static final class OptionAccessCheckInheritedUnAnnotatedField
+ extends OptionAccessCheckNonPublicStaticField {}
+
+ @Test
+ public void optionAccessCheck_FailsForStaticFields() throws Exception {
+ try {
+ new OptionsTester(OptionAccessCheckPublicStaticField.class).testAllOptionFieldsPublic();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("staticField");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ /** Test options class for optionAccessCheck_FailsForStaticFields. */
+ public static final class OptionAccessCheckPublicStaticField extends OptionsBase {
+ @Option(
+ name = "public static declared field with annotation",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ defaultValue = "defaultFoo"
+ )
+ public static String staticField;
+ }
+
+ @Test
+ public void optionAccessCheck_FailsForFinalFields() throws Exception {
+ try {
+ new OptionsTester(OptionAccessCheckPublicFinalField.class).testAllOptionFieldsPublic();
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("finalField");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ /** Test options class for optionAccessCheck_FailsForStaticFields. */
+ public static final class OptionAccessCheckPublicFinalField extends OptionsBase {
+ @Option(
+ name = "public final declared field with annotation",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ defaultValue = "defaultFoo"
+ )
+ public final String finalField = "";
+ }
+
+ /** Test converter class for testing testAllDefaultValuesTestedBy. */
+ public static final class TestConverter implements Converter<String> {
+ @Override
+ public String convert(String input) {
+ return input;
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a string";
+ }
+ }
+
+ @Test
+ public void defaultTestCheck_PassesIfAllDefaultsTestedIgnoringNullAndAllowMultiple() {
+ new OptionsTester(DefaultTestCheck.class)
+ .testAllDefaultValuesTestedBy(
+ new ConverterTesterMap.Builder()
+ .add(
+ new ConverterTester(TestConverter.class)
+ .addEqualityGroup("testedDefault", "otherTestedDefault"))
+ .build());
+ }
+
+ /** Test options class for defaultTestCheck_PassesIfAllDefaultsTested. */
+ public static final class DefaultTestCheck extends OptionsBase {
+ @Option(
+ name = "tested field",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ converter = TestConverter.class,
+ defaultValue = "testedDefault"
+ )
+ public String testedField;
+
+ @Option(
+ name = "field implicitly using default converter",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ defaultValue = "implicitConverterDefault"
+ )
+ public String implicitConverterField;
+
+ @Option(
+ name = "other tested field",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ converter = TestConverter.class,
+ defaultValue = "otherTestedDefault"
+ )
+ public String otherTestedField;
+
+ @Option(
+ name = "field with null default",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ converter = TestConverter.class,
+ defaultValue = "null"
+ )
+ public String nullDefaultField;
+
+ @Option(
+ name = "allowMultiple field",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ converter = TestConverter.class,
+ defaultValue = "allowMultipleDefault",
+ allowMultiple = true
+ )
+ public List<String> allowMultipleField;
+ }
+
+ @Test
+ public void defaultTestCheck_FailsIfTesterIsPresentButValueIsNotTested() {
+ try {
+ new OptionsTester(DefaultTestCheckUntestedField.class)
+ .testAllDefaultValuesTestedBy(
+ new ConverterTesterMap.Builder()
+ .add(new ConverterTester(TestConverter.class).addEqualityGroup("testedDefault"))
+ .build());
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("untestedField");
+ assertThat(expected).hasMessageThat().contains("untestedDefault");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ @Test
+ public void defaultTestCheck_FailsIfTesterIsAbsent() {
+ try {
+ new OptionsTester(DefaultTestCheckUntestedField.class)
+ .testAllDefaultValuesTestedBy(new ConverterTesterMap.Builder().build());
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("TestConverter");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ /**
+ * Test options class for defaultTestCheck_FailsIfTesterIsPresentButValueIsNotTested and
+ * defaultTestCheck_FailsIfTesterIsAbsent.
+ */
+ public static final class DefaultTestCheckUntestedField extends OptionsBase {
+ @Option(
+ name = "untested field",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ converter = TestConverter.class,
+ defaultValue = "untestedDefault"
+ )
+ public String untestedField;
+ }
+
+ @Test
+ public void defaultTestCheck_FailsIfTesterIsAbsentEvenForNullDefault() {
+ try {
+ new OptionsTester(DefaultTestCheckUntestedNullField.class)
+ .testAllDefaultValuesTestedBy(new ConverterTesterMap.Builder().build());
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("TestConverter");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ /** Test options class for defaultTestCheck_FailsIfTesterIsAbsentEvenForNullDefault. */
+ public static final class DefaultTestCheckUntestedNullField extends OptionsBase {
+ @Option(
+ name = "untested field",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ converter = TestConverter.class,
+ defaultValue = "null"
+ )
+ public String untestedNullField;
+ }
+
+ @Test
+ public void defaultTestCheck_FailsIfTesterIsAbsentEvenForAllowMultiple() {
+ try {
+ new OptionsTester(DefaultTestCheckUntestedMultipleField.class)
+ .testAllDefaultValuesTestedBy(new ConverterTesterMap.Builder().build());
+ } catch (AssertionError expected) {
+ assertThat(expected).hasMessageThat().contains("TestConverter");
+ return;
+ }
+ fail("test is expected to have failed");
+ }
+
+ /** Test options class for defaultTestCheck_FailsIfTesterIsAbsentEvenForAllowMultiple. */
+ public static final class DefaultTestCheckUntestedMultipleField extends OptionsBase {
+ @Option(
+ name = "untested field",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.NO_OP},
+ converter = TestConverter.class,
+ defaultValue = "untestedDefault",
+ allowMultiple = true
+ )
+ public List<String> untestedMultipleField;
+ }
+}