| // 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 = 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); | |
| } | |
| } | |
| } |