aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/google/testing/junit/testparameterinjector/TestInfo.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/testing/junit/testparameterinjector/TestInfo.java')
-rw-r--r--src/main/java/com/google/testing/junit/testparameterinjector/TestInfo.java308
1 files changed, 308 insertions, 0 deletions
diff --git a/src/main/java/com/google/testing/junit/testparameterinjector/TestInfo.java b/src/main/java/com/google/testing/junit/testparameterinjector/TestInfo.java
new file mode 100644
index 0000000..7d16b6e
--- /dev/null
+++ b/src/main/java/com/google/testing/junit/testparameterinjector/TestInfo.java
@@ -0,0 +1,308 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.Math.min;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+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;
+
+ /** The maximum amount of characters that a single parameter can take up in {@link #getName()}. */
+ static final int MAX_PARAMETER_NAME_LENGTH = 100;
+
+ public abstract Method getMethod();
+
+ public String getName() {
+ if (getParameters().isEmpty()) {
+ return getMethod().getName();
+ } else {
+ return String.format(
+ "%s[%s]",
+ getMethod().getName(),
+ getParameters().stream().map(TestInfoParameter::getName).collect(joining(",")));
+ }
+ }
+
+ abstract ImmutableList<TestInfoParameter> getParameters();
+
+ public abstract ImmutableList<Annotation> getAnnotations();
+
+ @Nullable
+ public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+ for (Annotation annotation : getAnnotations()) {
+ if (annotationClass.isInstance(annotation)) {
+ return annotationClass.cast(annotation);
+ }
+ }
+ return null;
+ }
+
+ TestInfo withExtraParameters(List<TestInfoParameter> parameters) {
+ return new AutoValue_TestInfo(
+ getMethod(),
+ ImmutableList.<TestInfoParameter>builder()
+ .addAll(this.getParameters())
+ .addAll(parameters)
+ .build(),
+ getAnnotations());
+ }
+
+ TestInfo withExtraAnnotation(Annotation annotation) {
+ ImmutableList<Annotation> newAnnotations =
+ ImmutableList.<Annotation>builder().addAll(this.getAnnotations()).add(annotation).build();
+ return new AutoValue_TestInfo(getMethod(), 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(
+ BiFunction<TestInfoParameter, Integer, String> parameterWithIndexToNewName) {
+ return new AutoValue_TestInfo(
+ getMethod(),
+ IntStream.range(0, getParameters().size())
+ .mapToObj(
+ parameterIndex -> {
+ TestInfoParameter parameter = getParameters().get(parameterIndex);
+ return parameter.withName(
+ parameterWithIndexToNewName.apply(parameter, parameterIndex));
+ })
+ .collect(toImmutableList()),
+ getAnnotations());
+ }
+
+ public static TestInfo legacyCreate(Method method, String name, List<Annotation> annotations) {
+ return new AutoValue_TestInfo(
+ method, /* parameters= */ ImmutableList.of(), ImmutableList.copyOf(annotations));
+ }
+
+ static TestInfo createWithoutParameters(Method method, List<Annotation> annotations) {
+ return new AutoValue_TestInfo(
+ method, /* parameters= */ ImmutableList.of(), ImmutableList.copyOf(annotations));
+ }
+
+ static ImmutableList<TestInfo> shortenNamesIfNecessary(List<TestInfo> testInfos) {
+ if (testInfos.stream()
+ .anyMatch(
+ info ->
+ info.getName().length() > MAX_TEST_NAME_LENGTH
+ || info.getParameters().stream()
+ .anyMatch(param -> param.getName().length() > MAX_PARAMETER_NAME_LENGTH))) {
+ int numberOfParameters = testInfos.get(0).getParameters().size();
+
+ if (numberOfParameters == 0) {
+ return ImmutableList.copyOf(testInfos);
+ } else {
+ Set<Integer> parameterIndicesThatNeedUpdate =
+ IntStream.range(0, numberOfParameters)
+ .filter(
+ parameterIndex ->
+ testInfos.stream()
+ .anyMatch(
+ info ->
+ info.getParameters().get(parameterIndex).getName().length()
+ > getMaxCharactersPerParameter(info, numberOfParameters)))
+ .boxed()
+ .collect(toSet());
+
+ return testInfos.stream()
+ .map(
+ info ->
+ info.withUpdatedParameterNames(
+ (parameter, parameterIndex) ->
+ parameterIndicesThatNeedUpdate.contains(parameterIndex)
+ ? getShortenedName(
+ parameter,
+ getMaxCharactersPerParameter(info, numberOfParameters))
+ : info.getParameters().get(parameterIndex).getName()))
+ .collect(toImmutableList());
+ }
+ } 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;
+ return min(
+ // Subtract 4 characters to leave place for joining commas and the parameter index.
+ maxLengthOfAllParameters / numberOfParameters - 4,
+ // Subtract 3 characters to leave place for the parameter index
+ MAX_PARAMETER_NAME_LENGTH - 3);
+ }
+
+ static ImmutableList<TestInfo> deduplicateTestNames(List<TestInfo> testInfos) {
+ long uniqueTestNameCount = testInfos.stream().map(TestInfo::getName).distinct().count();
+ 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.getName().length() > maxCharactersPerParameter
+ ? parameter.getName().substring(0, maxCharactersPerParameter - 3) + "..."
+ : parameter.getName();
+ 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 testNameToInfo.keySet().stream()
+ .flatMap(
+ 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.stream();
+ } 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
+ IntStream.range(0, numParameters)
+ .filter(
+ parameterIndex ->
+ matchedInfos.stream()
+ .map(
+ info ->
+ getTypeSuffix(
+ info.getParameters()
+ .get(parameterIndex)
+ .getValue()))
+ .distinct()
+ .count()
+ > 1)
+ .boxed()
+ .collect(toSet());
+
+ return matchedInfos.stream()
+ .map(
+ testInfo ->
+ testInfo.withUpdatedParameterNames(
+ (parameter, parameterIndex) ->
+ indicesThatShouldGetSuffix.contains(parameterIndex)
+ ? parameter.getName() + getTypeSuffix(parameter.getValue())
+ : parameter.getName()));
+ }
+ })
+ .collect(toImmutableList());
+ }
+
+ 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 = testInfos.stream().map(TestInfo::getName).distinct().count();
+ 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 testInfos.stream()
+ .map(
+ testInfo ->
+ testInfo.withUpdatedParameterNames(
+ (parameter, parameterIndex) ->
+ String.format(
+ "%s.%s", parameter.getIndexInValueSource() + 1, parameter.getName())))
+ .collect(toImmutableList());
+ }
+ }
+
+ private static <E> Collector<E, ?, ImmutableList<E>> toImmutableList() {
+ return Collectors.collectingAndThen(Collectors.toList(), ImmutableList::copyOf);
+ }
+
+ @AutoValue
+ abstract static class TestInfoParameter {
+
+ abstract String getName();
+
+ @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();
+
+ TestInfoParameter withName(String newName) {
+ return create(newName, getValue(), getIndexInValueSource());
+ }
+
+ static TestInfoParameter create(String name, @Nullable Object value, int indexInValueSource) {
+ checkArgument(indexInValueSource >= 0);
+ return new AutoValue_TestInfo_TestInfoParameter(
+ checkNotNull(name), value, indexInValueSource);
+ }
+ }
+}