Skip to content

Commit 3ccb8f7

Browse files
committed
Added Footnotes.addAfterFailure(..)
See #310
1 parent 2e54bc6 commit 3ccb8f7

File tree

3 files changed

+156
-55
lines changed

3 files changed

+156
-55
lines changed

api/src/main/java/net/jqwik/api/footnotes/Footnotes.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package net.jqwik.api.footnotes;
22

3+
import java.util.function.*;
4+
35
import org.apiguardian.api.*;
46

57
import static org.apiguardian.api.API.Status.*;
@@ -24,4 +26,6 @@ public interface Footnotes {
2426
*/
2527
void addFootnote(String footnote);
2628

29+
@API(status = EXPERIMENTAL, since = "1.7.2")
30+
void addAfterFailure(Supplier<String> footnoteSupplier);
2731
}

api/src/main/java/net/jqwik/api/footnotes/FootnotesHook.java

Lines changed: 117 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.lang.reflect.*;
44
import java.util.*;
5+
import java.util.function.*;
56

67
import org.apiguardian.api.*;
78

@@ -10,82 +11,147 @@
1011
import net.jqwik.api.lifecycle.*;
1112

1213
@API(status = API.Status.INTERNAL)
13-
class FootnotesHook implements ResolveParameterHook, AroundTryHook {
14+
class FootnotesHook implements RegistrarHook {
1415

15-
@Override
16-
public PropagationMode propagateTo() {
17-
return PropagationMode.ALL_DESCENDANTS;
16+
private static Store<FootnotesCollector> getFootnotesCollectorStore() {
17+
return Store.getOrCreate(
18+
Tuple.of(FootnotesHook.class, "footnotes"),
19+
Lifespan.TRY, FootnotesCollector::new
20+
);
1821
}
1922

2023
@Override
21-
public Optional<ParameterSupplier> resolve(
22-
ParameterResolutionContext parameterContext,
23-
LifecycleContext lifecycleContext
24-
) {
25-
if (parameterContext.typeUsage().isOfType(Footnotes.class)) {
26-
ParameterSupplier footnotesSupplier = optionalTry -> {
27-
Tuple2<String, Store<List<String>>> labelAndStore = optionalTry
28-
.map(tryLifecycleContext -> Tuple.of(tryLifecycleContext.label(), getFootnotesStore()))
29-
.orElseThrow(() -> {
30-
String message = String.format(
31-
"Illegal argument [%s] in method [%s].%n" +
32-
"Objects of type %s can only be injected directly " +
33-
"in property methods or in @BeforeTry and @AfterTry " +
34-
"lifecycle methods.",
35-
parameterContext.parameter(),
36-
parameterContext.optionalMethod().map(Method::toString).orElse("unknown"),
37-
Footnotes.class
38-
);
39-
return new IllegalArgumentException(message);
40-
});
41-
42-
return new StoreBasedFootnotes(labelAndStore);
43-
};
44-
return Optional.of(footnotesSupplier);
45-
}
46-
return Optional.empty();
24+
public void registerHooks(Registrar registrar) {
25+
registrar.register(FootnotesResolveParameter.class, PropagationMode.ALL_DESCENDANTS);
26+
registrar.register(FootnotesInnermost.class, PropagationMode.ALL_DESCENDANTS);
27+
registrar.register(FootnotesOutermost.class, PropagationMode.ALL_DESCENDANTS);
4728
}
4829

49-
private Store<List<String>> getFootnotesStore() {
50-
return Store.getOrCreate(
51-
Tuple.of(FootnotesHook.class, "footnotes"),
52-
Lifespan.TRY, ArrayList::new
53-
);
30+
static class FootnotesResolveParameter implements ResolveParameterHook {
31+
32+
@Override
33+
public Optional<ParameterSupplier> resolve(
34+
ParameterResolutionContext parameterContext,
35+
LifecycleContext lifecycleContext
36+
) {
37+
if (parameterContext.typeUsage().isOfType(Footnotes.class)) {
38+
ParameterSupplier footnotesSupplier = optionalTry -> {
39+
Tuple2<String, Store<FootnotesCollector>> labelAndStore =
40+
optionalTry
41+
.map(tryLifecycleContext -> Tuple.of(tryLifecycleContext.label(), getFootnotesCollectorStore()))
42+
.orElseThrow(() -> {
43+
String message = String.format(
44+
"Illegal argument [%s] in method [%s].%n" +
45+
"Objects of type %s can only be injected directly " +
46+
"in property methods or in @BeforeTry and @AfterTry " +
47+
"lifecycle methods.",
48+
parameterContext.parameter(),
49+
parameterContext.optionalMethod()
50+
.map(Method::toString)
51+
.orElse("unknown"),
52+
Footnotes.class
53+
);
54+
return new IllegalArgumentException(message);
55+
});
56+
57+
return new StoreBasedFootnotes(labelAndStore);
58+
};
59+
return Optional.of(footnotesSupplier);
60+
}
61+
return Optional.empty();
62+
}
5463
}
5564

56-
@Override
57-
public TryExecutionResult aroundTry(TryLifecycleContext context, TryExecutor aTry, List<Object> parameters) {
58-
TryExecutionResult executionResult = aTry.execute(parameters);
59-
if (executionResult.isFalsified()) {
60-
List<String> footnotes = getFootnotes();
61-
return executionResult.withFootnotes(footnotes);
65+
static class FootnotesOutermost implements AroundTryHook {
66+
67+
@Override
68+
public TryExecutionResult aroundTry(TryLifecycleContext context, TryExecutor aTry, List<Object> parameters) {
69+
TryExecutionResult executionResult = aTry.execute(parameters);
70+
if (executionResult.isFalsified()) {
71+
getFootnotesCollectorStore().get().evaluateOutermost();
72+
return executionResult.withFootnotes(getFootnotes());
73+
}
74+
return executionResult;
75+
}
76+
77+
private List<String> getFootnotes() {
78+
return getFootnotesCollectorStore().get().getFootnotes();
79+
}
80+
81+
@Override
82+
public int aroundTryProximity() {
83+
// Outside lifecycle methods
84+
return -20;
6285
}
63-
return executionResult;
6486
}
6587

66-
@Override
67-
public int aroundTryProximity() {
68-
// Outside lifecycle methods
69-
return -20;
88+
static class FootnotesInnermost implements AroundTryHook {
89+
90+
@Override
91+
public TryExecutionResult aroundTry(TryLifecycleContext context, TryExecutor aTry, List<Object> parameters) {
92+
TryExecutionResult executionResult = aTry.execute(parameters);
93+
if (executionResult.isFalsified()) {
94+
getFootnotesCollectorStore().get().evaluateInnermost();
95+
}
96+
return executionResult;
97+
}
98+
99+
@Override
100+
public int aroundTryProximity() {
101+
// Mostly first thing after property method execution
102+
return 99;
103+
}
70104
}
71105

72-
private List<String> getFootnotes() {
73-
return getFootnotesStore().get();
106+
private static class FootnotesCollector {
107+
108+
private final List<String> footnotes = new ArrayList<>();
109+
private final List<String> collectedFootnotes = new ArrayList<>();
110+
private final List<Supplier<String>> collectedSuppliers = new ArrayList<>();
111+
112+
private List<String> getFootnotes() {
113+
return footnotes;
114+
}
115+
116+
private void addFootnote(String footnote) {
117+
collectedFootnotes.add(footnote);
118+
}
119+
120+
private void addAfterFailure(Supplier<String> footnoteSupplier) {
121+
collectedSuppliers.add(footnoteSupplier);
122+
}
123+
124+
private void evaluateInnermost() {
125+
for (Supplier<String> footnoteSupplier : collectedSuppliers) {
126+
footnotes.add(footnoteSupplier.get());
127+
}
128+
}
129+
130+
private void evaluateOutermost() {
131+
for (String footnote : collectedFootnotes) {
132+
footnotes.add(footnote);
133+
}
134+
}
74135
}
75136

76137
private static class StoreBasedFootnotes implements Footnotes {
77138

78139
private final String label;
79-
private final Store<List<String>> store;
140+
private final Store<FootnotesCollector> store;
80141

81-
public StoreBasedFootnotes(Tuple2<String, Store<List<String>>> labelAndStore) {
142+
private StoreBasedFootnotes(Tuple2<String, Store<FootnotesCollector>> labelAndStore) {
82143
this.label = labelAndStore.get1();
83144
this.store = labelAndStore.get2();
84145
}
85146

86147
@Override
87148
public void addFootnote(String footnote) {
88-
store.get().add(footnote);
149+
store.get().addFootnote(footnote);
150+
}
151+
152+
@Override
153+
public void addAfterFailure(Supplier<String> footnoteSupplier) {
154+
store.get().addAfterFailure(footnoteSupplier);
89155
}
90156

91157
@Override

engine/src/test/java/net/jqwik/engine/execution/reporting/EnableFootnotesTests.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,23 @@ void afterTry(Footnotes footnotes) {
2121
footnotes.addFootnote("after try");
2222
}
2323

24-
@AddLifecycleHook(CheckTries.class)
24+
@AddLifecycleHook(CheckFootnotesInOrder.class)
2525
@Property
26-
void addFootnotes(@ForAll int anInt, Footnotes footnotes) {
26+
void normalFootnotesAreAddedInOrder(@ForAll int anInt, Footnotes footnotes) {
2727
footnotes.addFootnote("anInt=" + anInt);
2828
footnotes.addFootnote("footnote");
2929
assertThat(anInt).isLessThan(42);
3030
}
3131

32-
static class CheckTries implements AroundTryHook {
32+
@AddLifecycleHook(CheckAfterFailureFirst.class)
33+
@Property
34+
void afterFailureFootnotesAreEvaluatedFirst(@ForAll int anInt, Footnotes footnotes) {
35+
footnotes.addAfterFailure(() -> "after failure anInt=" + anInt);
36+
footnotes.addAfterFailure(() -> "after failure footnote");
37+
assertThat(anInt).isLessThan(42);
38+
}
39+
40+
static class CheckFootnotesInOrder implements AroundTryHook {
3341

3442
@Override
3543
public TryExecutionResult aroundTry(TryLifecycleContext context, TryExecutor aTry, List<Object> parameters) throws Throwable {
@@ -47,7 +55,30 @@ public TryExecutionResult aroundTry(TryLifecycleContext context, TryExecutor aTr
4755

4856
@Override
4957
public int aroundTryProximity() {
50-
// Closer than FootnotesHook
58+
// Outside all footnotes hooks
59+
return -90;
60+
}
61+
}
62+
63+
static class CheckAfterFailureFirst implements AroundTryHook {
64+
65+
@Override
66+
public TryExecutionResult aroundTry(TryLifecycleContext context, TryExecutor aTry, List<Object> parameters) throws Throwable {
67+
TryExecutionResult result = aTry.execute(parameters);
68+
if (result.isFalsified()) {
69+
assertThat(result.footnotes()).hasSize(4);
70+
assertThat(result.footnotes().get(0)).startsWith("after failure anInt=");
71+
assertThat(result.footnotes().get(1)).isEqualTo("after failure footnote");
72+
assertThat(result.footnotes().get(2)).isEqualTo("before try");
73+
assertThat(result.footnotes().get(3)).isEqualTo("after try");
74+
return TryExecutionResult.satisfied();
75+
}
76+
return result;
77+
}
78+
79+
@Override
80+
public int aroundTryProximity() {
81+
// Outside all footnotes hooks
5182
return -90;
5283
}
5384
}

0 commit comments

Comments
 (0)