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}