001/* 002 * Copyright 2023 the original author or authors. 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package de.cuioss.test.generator.junit; 017 018import java.lang.reflect.Method; 019 020import org.junit.jupiter.api.extension.BeforeEachCallback; 021import org.junit.jupiter.api.extension.ExtendWith; 022import org.junit.jupiter.api.extension.Extension; 023import org.junit.jupiter.api.extension.ExtensionContext; 024import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; 025import org.opentest4j.AssertionFailedError; 026import org.opentest4j.TestAbortedException; 027 028import de.cuioss.test.generator.internal.net.java.quickcheck.generator.distribution.RandomConfiguration; 029 030/** 031 * If enabled, either by using {@link ExtendWith} or 032 * {@link EnableGeneratorController} this {@link Extension} controls the seed 033 * initialization, by checking for {@link GeneratorSeed} and intercepts 034 * Test-failures by printing information providing the seed to reproduce. 035 * 036 * @author Oliver Wolff 037 * 038 */ 039public class GeneratorControllerExtension implements BeforeEachCallback, TestExecutionExceptionHandler { 040 041 private static final String MSG_TEMPLATE = """ 042 %s 043 GeneratorController seed was %sL.\s 044 Use a fixed seed by applying @GeneratorSeed(%sL) for the method/class,\s 045 or by using the system property '-D\ 046 """ + RandomConfiguration.SEED_SYSTEM_PROPERTY + "=%s'\n"; 047 048 @Override 049 public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { 050 051 if (throwable instanceof TestAbortedException) { 052 throw throwable; 053 } 054 if (throwable instanceof AssertionFailedError) { 055 var failure = new AssertionFailedError(createErrorMessage(throwable, RandomConfiguration.getLastSeed())); 056 failure.setStackTrace(throwable.getStackTrace()); 057 throw failure; 058 } else { 059 var failure = new AssertionFailedError( 060 throwable.getClass() + ": " + createErrorMessage(throwable, RandomConfiguration.getLastSeed())); 061 failure.setStackTrace(throwable.getStackTrace()); 062 throw failure; 063 } 064 065 } 066 067 @Override 068 @SuppressWarnings("java:S3655") // owolff: false positive: isPresent is checked 069 public void beforeEach(ExtensionContext context) throws Exception { 070 var seedSetByAnnotation = false; 071 long initialSeed = -1; 072 if (context.getElement().isPresent()) { 073 var annotatedElement = context.getElement().get(); 074 var seedAnnotation = annotatedElement.getAnnotation(GeneratorSeed.class); 075 if (null == seedAnnotation && annotatedElement instanceof Method method) { 076 seedAnnotation = method.getDeclaringClass().getAnnotation(GeneratorSeed.class); 077 } 078 if (null != seedAnnotation) { 079 initialSeed = seedAnnotation.value(); 080 seedSetByAnnotation = true; 081 } 082 } 083 if (seedSetByAnnotation) { 084 RandomConfiguration.setSeed(initialSeed); 085 } else { 086 RandomConfiguration.initSeed(); 087 } 088 } 089 090 private String createErrorMessage(Throwable e, Long seed) { 091 var causeMsg = e.getMessage() == null ? "" : e.getMessage(); 092 return MSG_TEMPLATE.formatted(causeMsg, seed, seed, seed); 093 } 094 095}