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