diff --git a/api/src/main/java/net/jqwik/api/lifecycle/BeforeTry.java b/api/src/main/java/net/jqwik/api/lifecycle/BeforeTry.java index 95a08d7df..79221571c 100644 --- a/api/src/main/java/net/jqwik/api/lifecycle/BeforeTry.java +++ b/api/src/main/java/net/jqwik/api/lifecycle/BeforeTry.java @@ -7,10 +7,11 @@ import static org.apiguardian.api.API.Status.*; /** - * Annotate methods of a container class with {@code @BeforeTry} - * to have them run once before each try - the actual invocation of the property + * Annotate methods or member variables of a container class with {@code @BeforeTry}. + * Annotated methods will then be run once before each try - the actual invocation of the property * method with generated parameters - including properties of * embedded containers. + * Annotated members will be freshly initialized before each try. * *

{@code @BeforeTry} methods are inherited from superclasses * and implemented interfaces as long as they are not hidden @@ -28,7 +29,7 @@ * * @see AfterTry */ -@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @API(status = MAINTAINED, since = "1.4.0") public @interface BeforeTry { diff --git a/engine/src/main/java/net/jqwik/engine/hooks/Hooks.java b/engine/src/main/java/net/jqwik/engine/hooks/Hooks.java index 1859429e3..621ca6483 100644 --- a/engine/src/main/java/net/jqwik/engine/hooks/Hooks.java +++ b/engine/src/main/java/net/jqwik/engine/hooks/Hooks.java @@ -24,6 +24,9 @@ public static class AroundProperty { public static class AroundTry { // Should run close to property method public static final int TRY_LIFECYCLE_METHODS_PROXIMITY = -10; + + // Should run first thing + public static final int BEFORE_TRY_MEMBERS_PROXIMITY = -100; } } diff --git a/engine/src/main/java/net/jqwik/engine/hooks/lifecycle/BeforeTryMembersHook.java b/engine/src/main/java/net/jqwik/engine/hooks/lifecycle/BeforeTryMembersHook.java new file mode 100644 index 000000000..1e715066a --- /dev/null +++ b/engine/src/main/java/net/jqwik/engine/hooks/lifecycle/BeforeTryMembersHook.java @@ -0,0 +1,63 @@ +package net.jqwik.engine.hooks.lifecycle; + +import java.lang.reflect.*; +import java.util.*; + +import org.junit.platform.engine.support.hierarchical.*; + +import net.jqwik.api.lifecycle.*; +import net.jqwik.engine.hooks.*; +import net.jqwik.engine.support.*; + +public class BeforeTryMembersHook implements AroundTryHook { + + private void beforeTry(TryLifecycleContext context) { + System.out.println("initialize before try members"); + // List beforeTryMethods = LifecycleMethods.findBeforeTryMethods(context.containerClass()); + // callTryMethods(beforeTryMethods, context); + } + + private void callTryMethods(List methods, TryLifecycleContext context) { + Object testInstance = context.testInstance(); + ThrowableCollector throwableCollector = new ThrowableCollector(ignore -> false); + for (Method method : methods) { + Object[] parameters = MethodParameterResolver.resolveParameters(method, context); + throwableCollector.execute(() -> callMethod(method, testInstance, parameters)); + } + throwableCollector.assertEmpty(); + } + + private void callMethod(Method method, Object target, Object[] parameters) { + JqwikReflectionSupport.invokeMethodPotentiallyOuter(method, target, parameters); + } + + private void afterTry(TryLifecycleContext context) { + List afterTryMethods = LifecycleMethods.findAfterTryMethods(context.containerClass()); + callTryMethods(afterTryMethods, context); + } + + @Override + public PropagationMode propagateTo() { + return PropagationMode.ALL_DESCENDANTS; + } + + @Override + public boolean appliesTo(Optional element) { + return element.map(e -> e instanceof Method).orElse(false); + } + + @Override + public int aroundTryProximity() { + return Hooks.AroundTry.BEFORE_TRY_MEMBERS_PROXIMITY; + } + + @Override + public TryExecutionResult aroundTry(TryLifecycleContext context, TryExecutor aTry, List parameters) { + beforeTry(context); + try { + return aTry.execute(parameters); + } finally { + afterTry(context); + } + } +} diff --git a/engine/src/main/resources/META-INF/services/net.jqwik.api.lifecycle.LifecycleHook b/engine/src/main/resources/META-INF/services/net.jqwik.api.lifecycle.LifecycleHook index 078d7f021..56973be2f 100644 --- a/engine/src/main/resources/META-INF/services/net.jqwik.api.lifecycle.LifecycleHook +++ b/engine/src/main/resources/META-INF/services/net.jqwik.api.lifecycle.LifecycleHook @@ -2,6 +2,7 @@ net.jqwik.engine.hooks.lifecycle.AutoCloseableHook net.jqwik.engine.hooks.lifecycle.ContainerLifecycleMethodsHook net.jqwik.engine.hooks.lifecycle.PropertyLifecycleMethodsHook net.jqwik.engine.hooks.lifecycle.TryLifecycleMethodsHook +net.jqwik.engine.hooks.lifecycle.BeforeTryMembersHook net.jqwik.engine.hooks.DisabledHook net.jqwik.engine.hooks.statistics.StatisticsHook net.jqwik.engine.hooks.ResolveReporterHook diff --git a/engine/src/test/java/net/jqwik/engine/execution/lifecycle/BeforeTryOnMemberVariableTests.java b/engine/src/test/java/net/jqwik/engine/execution/lifecycle/BeforeTryOnMemberVariableTests.java new file mode 100644 index 000000000..1af6d9265 --- /dev/null +++ b/engine/src/test/java/net/jqwik/engine/execution/lifecycle/BeforeTryOnMemberVariableTests.java @@ -0,0 +1,44 @@ +package net.jqwik.engine.execution.lifecycle; + +import org.assertj.core.api.*; + +import net.jqwik.api.*; +import net.jqwik.api.constraints.*; +import net.jqwik.api.lifecycle.*; + +class BeforeTryOnMemberVariableTests { + + @BeforeTry + private int member = 41; + + @BeforeTry + void testBefore() { + System.out.println("before try outer"); + } + + @Property(tries = 10) + void beforeTryOnMember(@ForAll @IntRange(min = 1, max = 100) int addend) { + Assertions.assertThat(member).isEqualTo(41); + member = member + addend; + } + + @Group + class InnerTests { + + @BeforeTry + private int innerMember = 42; + + @BeforeTry + void testBeforeInner() { + System.out.println("before try inner"); + } + + @Property(tries = 10) + void beforeTryOnMemberInInnerGroup(@ForAll @IntRange(min = 1, max = 100) int addend) { + Assertions.assertThat(member).isEqualTo(41); + member = member + addend; + Assertions.assertThat(innerMember).isEqualTo(42); + innerMember = innerMember + addend; + } + } +}