aboutsummaryrefslogtreecommitdiff
path: root/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestInfo.java
diff options
context:
space:
mode:
Diffstat (limited to 'junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestInfo.java')
-rw-r--r--junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestInfo.java325
1 files changed, 325 insertions, 0 deletions
diff --git a/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestInfo.java b/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestInfo.java
new file mode 100644
index 0000000..7ed3412
--- /dev/null
+++ b/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestInfo.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * 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.testing.junit.testparameterinjector.junit5;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ContiguousSet;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.collect.Range;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/** A POJO containing information about a test (name and anotations). */
+@AutoValue
+abstract class TestInfo {
+
+ /**
+ * The maximum amount of characters that {@link #getName()} can have.
+ *
+ * <p>See b/168325767 for the reason behind this. tl;dr the name is put into a Unix file with max
+ * 255 characters. The surrounding constant characters take up 31 characters. The max is reduced
+ * by an additional 24 characters to account for future changes.
+ */
+ static final int MAX_TEST_NAME_LENGTH = 200;
+
+ public abstract Method getMethod();
+
+ /**
+ * The test class that is being run.
+ *
+ * <p>Note that this is not always the same as the class that declares {@link #getMethod()}
+ * because test methods can be inherited.
+ */
+ public abstract Class<?> getTestClass();
+
+ public final String getName() {
+ if (getParameters().isEmpty()) {
+ return getMethod().getName();
+ } else {
+ return String.format(
+ "%s[%s]",
+ getMethod().getName(),
+ FluentIterable.from(getParameters())
+ .transform(TestInfoParameter::getValueInTestName)
+ .join(Joiner.on(",")));
+ }
+ }
+
+ abstract ImmutableList<TestInfoParameter> getParameters();
+
+ public abstract ImmutableList<Annotation> getAnnotations();
+
+ @Nullable
+ public final <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+ for (Annotation annotation : getAnnotations()) {
+ if (annotationClass.isInstance(annotation)) {
+ return annotationClass.cast(annotation);
+ }
+ }
+ return null;
+ }
+
+ final TestInfo withExtraParameters(List<TestInfoParameter> parameters) {
+ return new AutoValue_TestInfo(
+ getMethod(),
+ getTestClass(),
+ ImmutableList.<TestInfoParameter>builder()
+ .addAll(this.getParameters())
+ .addAll(parameters)
+ .build(),
+ getAnnotations());
+ }
+
+ final TestInfo withExtraAnnotation(Annotation annotation) {
+ ImmutableList<Annotation> newAnnotations =
+ ImmutableList.<Annotation>builder().addAll(this.getAnnotations()).add(annotation).build();
+ return new AutoValue_TestInfo(getMethod(), getTestClass(), getParameters(), newAnnotations);
+ }
+
+ /**
+ * Returns a new TestInfo instance with updated parameter names.
+ *
+ * @param parameterWithIndexToNewName A function of the parameter and its index in the {@link
+ * #getParameters()} list to the new name.
+ */
+ private TestInfo withUpdatedParameterNames(
+ Java8BiFunction<TestInfoParameter, Integer, String> parameterWithIndexToNewName) {
+ return new AutoValue_TestInfo(
+ getMethod(),
+ getTestClass(),
+ FluentIterable.from(
+ ContiguousSet.create(
+ Range.closedOpen(0, getParameters().size()), DiscreteDomain.integers()))
+ .transform(
+ parameterIndex -> {
+ TestInfoParameter parameter = getParameters().get(parameterIndex);
+ return parameter.withValueInTestName(
+ parameterWithIndexToNewName.apply(parameter, parameterIndex));
+ })
+ .toList(),
+ getAnnotations());
+ }
+
+ public static TestInfo legacyCreate(
+ Method method, Class<?> testClass, String name, List<Annotation> annotations) {
+ return new AutoValue_TestInfo(
+ method, testClass, /* parameters= */ ImmutableList.of(), ImmutableList.copyOf(annotations));
+ }
+
+ static TestInfo createWithoutParameters(
+ Method method, Class<?> testClass, List<Annotation> annotations) {
+ return new AutoValue_TestInfo(
+ method, testClass, /* parameters= */ ImmutableList.of(), ImmutableList.copyOf(annotations));
+ }
+
+ static ImmutableList<TestInfo> shortenNamesIfNecessary(List<TestInfo> testInfos) {
+ if (FluentIterable.from(testInfos)
+ .anyMatch(info -> info.getName().length() > MAX_TEST_NAME_LENGTH)) {
+ int numberOfParameters = testInfos.get(0).getParameters().size();
+
+ if (numberOfParameters == 0) {
+ return ImmutableList.copyOf(testInfos);
+ } else {
+ Set<Integer> parameterIndicesThatNeedUpdate =
+ FluentIterable.from(
+ ContiguousSet.create(
+ Range.closedOpen(0, numberOfParameters), DiscreteDomain.integers()))
+ .filter(
+ parameterIndex ->
+ FluentIterable.from(testInfos)
+ .anyMatch(
+ info ->
+ info.getParameters()
+ .get(parameterIndex)
+ .getValueInTestName()
+ .length()
+ > getMaxCharactersPerParameter(info, numberOfParameters)))
+ .toSet();
+
+ return FluentIterable.from(testInfos)
+ .transform(
+ info ->
+ info.withUpdatedParameterNames(
+ (parameter, parameterIndex) ->
+ parameterIndicesThatNeedUpdate.contains(parameterIndex)
+ ? getShortenedName(
+ parameter,
+ getMaxCharactersPerParameter(info, numberOfParameters))
+ : info.getParameters().get(parameterIndex).getValueInTestName()))
+ .toList();
+ }
+ } else {
+ return ImmutableList.copyOf(testInfos);
+ }
+ }
+
+ private static int getMaxCharactersPerParameter(TestInfo testInfo, int numberOfParameters) {
+ int maxLengthOfAllParameters =
+ // Subtract 2 characters for square brackets
+ MAX_TEST_NAME_LENGTH - testInfo.getMethod().getName().length() - 2;
+
+ // Subtract 4 characters to leave place for joining commas and the parameter index.
+ return maxLengthOfAllParameters / numberOfParameters - 4;
+ }
+
+ static ImmutableList<TestInfo> deduplicateTestNames(List<TestInfo> testInfos) {
+ long uniqueTestNameCount =
+ FluentIterable.from(testInfos).transform(TestInfo::getName).toSet().size();
+ if (testInfos.size() == uniqueTestNameCount) {
+ // Return early if there are no duplicates
+ return ImmutableList.copyOf(testInfos);
+ } else {
+ return deduplicateWithNumberPrefixes(maybeAddTypesIfDuplicate(testInfos));
+ }
+ }
+
+ private static String getShortenedName(
+ TestInfoParameter parameter, int maxCharactersPerParameter) {
+ if (maxCharactersPerParameter < 4) {
+ // Not enough characters for "..." suffix
+ return String.valueOf(parameter.getIndexInValueSource() + 1);
+ } else {
+ String shortenedName =
+ parameter.getValueInTestName().length() > maxCharactersPerParameter
+ ? parameter.getValueInTestName().substring(0, maxCharactersPerParameter - 3) + "..."
+ : parameter.getValueInTestName();
+ return String.format("%s.%s", parameter.getIndexInValueSource() + 1, shortenedName);
+ }
+ }
+
+ private static ImmutableList<TestInfo> maybeAddTypesIfDuplicate(List<TestInfo> testInfos) {
+ Multimap<String, TestInfo> testNameToInfo =
+ MultimapBuilder.linkedHashKeys().arrayListValues().build();
+ for (TestInfo testInfo : testInfos) {
+ testNameToInfo.put(testInfo.getName(), testInfo);
+ }
+
+ return FluentIterable.from(testNameToInfo.keySet())
+ .transformAndConcat(
+ testName -> {
+ Collection<TestInfo> matchedInfos = testNameToInfo.get(testName);
+ if (matchedInfos.size() == 1) {
+ // There was only one method with this name, so no deduplication is necessary
+ return matchedInfos;
+ } else {
+ // Found tests with duplicate test names
+ int numParameters = matchedInfos.iterator().next().getParameters().size();
+ Set<Integer> indicesThatShouldGetSuffix =
+ // Find parameter indices for which a suffix would allow the reader to
+ // differentiate
+ FluentIterable.from(
+ ContiguousSet.create(
+ Range.closedOpen(0, numParameters), DiscreteDomain.integers()))
+ .filter(
+ parameterIndex ->
+ FluentIterable.from(matchedInfos)
+ .transform(
+ info ->
+ getTypeSuffix(
+ info.getParameters()
+ .get(parameterIndex)
+ .getValue()))
+ .toSet()
+ .size()
+ > 1)
+ .toSet();
+
+ return FluentIterable.from(matchedInfos)
+ .transform(
+ testInfo ->
+ testInfo.withUpdatedParameterNames(
+ (parameter, parameterIndex) ->
+ indicesThatShouldGetSuffix.contains(parameterIndex)
+ ? parameter.getValueInTestName()
+ + getTypeSuffix(parameter.getValue())
+ : parameter.getValueInTestName()));
+ }
+ })
+ .toList();
+ }
+
+ private static String getTypeSuffix(@Nullable Object value) {
+ if (value == null) {
+ return " (null reference)";
+ } else {
+ return String.format(" (%s)", value.getClass().getSimpleName());
+ }
+ }
+
+ private static ImmutableList<TestInfo> deduplicateWithNumberPrefixes(
+ ImmutableList<TestInfo> testInfos) {
+ long uniqueTestNameCount =
+ FluentIterable.from(testInfos).transform(TestInfo::getName).toSet().size();
+ if (testInfos.size() == uniqueTestNameCount) {
+ return ImmutableList.copyOf(testInfos);
+ } else {
+ // There are still duplicates, even after adding type suffixes. As a last resort: add a
+ // counter to all parameters to guarantee that each case is unique.
+ return FluentIterable.from(testInfos)
+ .transform(
+ testInfo ->
+ testInfo.withUpdatedParameterNames(
+ (parameter, parameterIndex) ->
+ String.format(
+ "%s.%s",
+ parameter.getIndexInValueSource() + 1,
+ parameter.getValueInTestName())))
+ .toList();
+ }
+ }
+
+ @AutoValue
+ abstract static class TestInfoParameter {
+
+ abstract String getValueInTestName();
+
+ @Nullable
+ abstract Object getValue();
+
+ /**
+ * The index of this parameter value in the list of all values provided by the provider that
+ * returned this value.
+ */
+ abstract int getIndexInValueSource();
+
+ final TestInfoParameter withValueInTestName(String newValueInTestName) {
+ return create(newValueInTestName, getValue(), getIndexInValueSource());
+ }
+
+ static TestInfoParameter create(
+ String valueInTestName, @Nullable Object value, int indexInValueSource) {
+ checkArgument(indexInValueSource >= 0);
+ return new AutoValue_TestInfo_TestInfoParameter(
+ checkNotNull(valueInTestName), value, indexInValueSource);
+ }
+ }
+
+ /** Copy of Java8's java.util.BiFunction which is not available in older versions of the JDK */
+ interface Java8BiFunction<I, J, K> {
+ K apply(I a, J b);
+ }
+}