Skip to content

Commit

Permalink
provide name of test class to re-run in failure reproduction info, if…
Browse files Browse the repository at this point in the history
… test class hierarchy of failed test contains Lifecycle PER_CLASS

Signed-off-by: Jonas Höf <jonas.hoef@tngtech.com>
  • Loading branch information
jonashoef committed Dec 9, 2024
1 parent 99af692 commit 6491137
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 45 deletions.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ at com.tngtech.valueprovider.ValueProviderRule$1.evaluate(ValueProviderRule.java
```

If the failure is related to random data, you can easily reproduce it. Just specify the above shown JVM system
properties in the command line when you re-run the test, e.g., in your IDE:
properties in the command line when you re-run the failed test, e.g., in your IDE:

```
-Dvalue.provider.factory.test.class.seed=0
Expand All @@ -524,8 +524,17 @@ for details). The nesting may be arbitrarily deep, i.e. `@Nested` classes may co
test lifecycle may be chosen individually for the main test class as well as for each `@Nested` test class. As long as
the main test class and all `@Nested` test classes use the default test lifecycle `PER_METHOD`, it is again sufficient
to rerun the individual test method, regardless if it is in the main class or any nested class. As soon as the lifecycle
`PER_CLASS` is used for one of the classes in the nesting hierarchy where the failure occured, you have to rerun __all
test methods of this hierarchy__ of test classes to reproduce the failure.
`PER_CLASS` is used for one or more classes in the nesting hierarchy where the failure occured, you have to re-run __all
test methods of this hierarchy__ of test classes to reproduce the failure. For convenience, the failure message
generated by the infrastructure provides the name of the root test class of this hierarchy in addition to the seed
values as shown in the following example:

```
"If the failure is related to random ValueProviders, re-run all tests of 'com.tngtech.valueprovider.ValueProviderExceptionTest' and specify the following system properties for the JVM to reproduce:
-Dvalue.provider.factory.test.class.seed=0
-Dvalue.provider.factory.test.method.seed=-5385145878463633929
-Dvalue.provider.factory.reference.date.time=2024-12-09T17:02:50.109"
```

### Reproducible ValueProviders

Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,43 @@
package com.tngtech.valueprovider;

import static com.tngtech.valueprovider.InitializationCreator.VALUE_PROVIDER_FACTORY_REFERENCE_DATE_TIME_PROPERTY;
import static com.tngtech.valueprovider.InitializationCreator.VALUE_PROVIDER_FACTORY_TEST_CLASS_SEED_PROPERTY;
import static com.tngtech.valueprovider.InitializationCreator.VALUE_PROVIDER_FACTORY_TEST_METHOD_SEED_PROPERTY;
import static com.tngtech.valueprovider.ValueProviderFactory.getFormattedReferenceDateTime;
import static com.tngtech.valueprovider.ValueProviderFactory.getTestClassSeed;
import static com.tngtech.valueprovider.ValueProviderFactory.getTestMethodSeed;
import java.util.Optional;

import com.google.common.annotations.VisibleForTesting;

import static com.tngtech.valueprovider.InitializationCreator.*;
import static com.tngtech.valueprovider.ValueProviderFactory.*;
import static java.lang.String.format;
import static java.util.Optional.empty;

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class ValueProviderException extends RuntimeException {
ValueProviderException() {
super(provideFailureReproductionInfo());
this(empty());
}

ValueProviderException(Optional<Class<?>> testClassToReRunForReproduction) {
super(provideFailureReproductionInfo(testClassToReRunForReproduction));
}

static String provideFailureReproductionInfo() {
@VisibleForTesting
static String provideFailureReproductionInfo(Optional<Class<?>> testClassToReRunForReproduction) {
long testClassSeed = getTestClassSeed();
long testMethodSeed = getTestMethodSeed();
String referenceDateTime = getFormattedReferenceDateTime();
return format(
"If the failure is related to random ValueProviders, specify the following system properties for the JVM to reproduce:%n" +
"If the failure is related to random ValueProviders, %sspecify the following system properties for the JVM to reproduce:%n" +
"-D%s=%d%n" +
"-D%s=%d%n" +
"-D%s=%s",
formatReRunMessageFor(testClassToReRunForReproduction),
VALUE_PROVIDER_FACTORY_TEST_CLASS_SEED_PROPERTY, testClassSeed,
VALUE_PROVIDER_FACTORY_TEST_METHOD_SEED_PROPERTY, testMethodSeed,
VALUE_PROVIDER_FACTORY_REFERENCE_DATE_TIME_PROPERTY, referenceDateTime);
}

private static String formatReRunMessageFor(Optional<Class<?>> testClassToReRunForReproduction) {
return testClassToReRunForReproduction.map(testClass ->
format("re-run all tests of '%s' and ", testClass.getName()))
.orElse("");
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package com.tngtech.valueprovider;

import java.util.Optional;

import org.junit.jupiter.api.Test;

import static com.tngtech.valueprovider.InitializationCreator.VALUE_PROVIDER_FACTORY_REFERENCE_DATE_TIME_PROPERTY;
import static com.tngtech.valueprovider.InitializationCreator.VALUE_PROVIDER_FACTORY_TEST_CLASS_SEED_PROPERTY;
import static com.tngtech.valueprovider.InitializationCreator.VALUE_PROVIDER_FACTORY_TEST_METHOD_SEED_PROPERTY;
import static com.tngtech.valueprovider.ValueProviderFactory.getFormattedReferenceDateTime;
import static com.tngtech.valueprovider.ValueProviderFactory.getTestClassSeed;
import static com.tngtech.valueprovider.ValueProviderFactory.getTestMethodSeed;
import static com.tngtech.valueprovider.InitializationCreator.*;
import static com.tngtech.valueprovider.ValueProviderFactory.*;
import static org.assertj.core.api.Assertions.assertThat;

class ValueProviderExceptionTest {
Expand All @@ -26,4 +24,14 @@ void should_show_seed_values_reference_date_time_and_respective_system_propertie
VALUE_PROVIDER_FACTORY_TEST_METHOD_SEED_PROPERTY,
VALUE_PROVIDER_FACTORY_REFERENCE_DATE_TIME_PROPERTY);
}

@Test
void should_show_test_class_to_re_run_for_failure_reproduction_if_provided() {
Class<?> testClassToReRun = this.getClass();
ValueProviderException exception = new ValueProviderException(Optional.of(testClassToReRun));

String message = exception.getMessage();

assertThat(message).contains(testClassToReRun.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import static com.tngtech.valueprovider.ValueProviderExtension.TestMethodCycleState.*;
import static java.lang.System.identityHashCode;
import static java.util.Optional.empty;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD;

Expand Down Expand Up @@ -78,9 +79,13 @@ public void beforeEach(ExtensionContext context) {
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
logger.debug("{} handleTestExecutionException {}",
identityHashCode(this), buildQualifiedTestMethodName(context));
// If the test class hierarchy of the failed test method contains any class(es) with Lifecycle PER_CLASS,
// all test methods of this hierarchy must be re-run to reproduce the failure.
// The root test class of the hierarchy must therefore be shown in the failure reproduction info.
Optional<Class<?>> testClassToReRunForReproduction = getRootClassInHierarchyWithLifecyclePerClass(context);
// Note: handleTestExecutionException() is invoked BEFORE afterEach, i.e. BEFORE seed is reset,
// so that the correct seed values appear in the failure message
throwable.addSuppressed(new ValueProviderException());
throwable.addSuppressed(new ValueProviderException(testClassToReRunForReproduction));
throw throwable;
}

Expand Down Expand Up @@ -179,6 +184,24 @@ private static boolean isLastTestClassInHierarchyWithLifecyclePerClass(Extension
return remainingLifecyclesInHierarchy.isEmpty() || containsOnlyLifecyclePerMethod(remainingLifecyclesInHierarchy);
}

private static Optional<Class<?>> getRootClassInHierarchyWithLifecyclePerClass(ExtensionContext startContext) {
List<Class<?>> testClassesInHierarchyWithLifecyclePerClass = new ArrayList<>();
traverseContextHierarchy(startContext, context ->
addTestClassAtBeginningIfLifecyclePerClass(context, testClassesInHierarchyWithLifecyclePerClass));
if (testClassesInHierarchyWithLifecyclePerClass.isEmpty()) {
return empty();
}
return Optional.of(testClassesInHierarchyWithLifecyclePerClass.get(0));
}

private static void addTestClassAtBeginningIfLifecyclePerClass(ExtensionContext context, List<Class<?>> addTo) {
if (!isLifecycle(context, PER_CLASS)) {
return;
}
context.getTestClass().ifPresent(testClass ->
addTo.add(0, testClass));
}

private static boolean testClassHierarchyHasOnlyLifecyclePerMethod(ExtensionContext context) {
Set<Lifecycle> lifecyclesInHierarchy = determineLifecyclesInTestClassHierarchy(Optional.of(context));
return containsOnlyLifecyclePerMethod(lifecyclesInHierarchy);
Expand Down
Loading

0 comments on commit 6491137

Please sign in to comment.