001package de.cuioss.test.generator.junit; 002 003import java.lang.reflect.Method; 004 005import org.junit.jupiter.api.extension.BeforeEachCallback; 006import org.junit.jupiter.api.extension.ExtendWith; 007import org.junit.jupiter.api.extension.Extension; 008import org.junit.jupiter.api.extension.ExtensionContext; 009import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; 010import org.opentest4j.AssertionFailedError; 011import org.opentest4j.TestAbortedException; 012 013import de.cuioss.test.generator.internal.net.java.quickcheck.generator.distribution.RandomConfiguration; 014 015/** 016 * If enabled, either by using {@link ExtendWith} or {@link EnableGeneratorController} this 017 * {@link Extension} controls the seed initialization, by checking for {@link GeneratorSeed} and 018 * intercepts Test-failures by printing information providing the seed to reproduce. 019 * 020 * @author Oliver Wolff 021 * 022 */ 023public class GeneratorControllerExtension implements BeforeEachCallback, TestExecutionExceptionHandler { 024 025 private static final String MSG_TEMPLATE = "%s\nGeneratorController seed was %sL. " 026 + "\nUse a fixed seed by applying @GeneratorSeed(%sL) for the method/class, " 027 + "\nor by using the system property '-D" + RandomConfiguration.SEED_SYSTEM_PROPERTY 028 + "=%s'\n"; 029 030 @Override 031 public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { 032 033 if (throwable instanceof TestAbortedException) { 034 throw throwable; 035 } 036 if (throwable instanceof AssertionFailedError) { 037 var failure = new AssertionFailedError(createErrorMessage(throwable, RandomConfiguration.getLastSeed())); 038 failure.setStackTrace(throwable.getStackTrace()); 039 throw failure; 040 } else { 041 var failure = 042 new AssertionFailedError( 043 throwable.getClass() + ": " + createErrorMessage(throwable, RandomConfiguration.getLastSeed())); 044 failure.setStackTrace(throwable.getStackTrace()); 045 throw failure; 046 } 047 048 } 049 050 @Override 051 @SuppressWarnings("java:S3655") // owolff: false positive: isPresent is checked 052 public void beforeEach(ExtensionContext context) throws Exception { 053 var seedSetByAnnotation = false; 054 long initialSeed = -1; 055 if (context.getElement().isPresent()) { 056 var annotatedElement = context.getElement().get(); 057 var seedAnnotation = annotatedElement.getAnnotation(GeneratorSeed.class); 058 if (null == seedAnnotation && annotatedElement instanceof Method) { 059 seedAnnotation = ((Method) annotatedElement).getDeclaringClass() 060 .getAnnotation(GeneratorSeed.class); 061 } 062 if (null != seedAnnotation) { 063 initialSeed = seedAnnotation.value(); 064 seedSetByAnnotation = true; 065 } 066 } 067 if (seedSetByAnnotation) { 068 RandomConfiguration.setSeed(initialSeed); 069 } else { 070 RandomConfiguration.initSeed(); 071 } 072 } 073 074 private String createErrorMessage(Throwable e, Long seed) { 075 var causeMsg = e.getMessage() == null ? "" : e.getMessage(); 076 return String.format(MSG_TEMPLATE, causeMsg, seed, seed, seed); 077 } 078 079}