Skip to content

Commit 0f188c7

Browse files
committed
tests: Clean up LifecycleFuzzTest
Makes the test less indirect and also adds stronger assertions on the precise order in which lifecycle methods are called.
1 parent f72a5aa commit 0f188c7

File tree

5 files changed

+167
-30
lines changed

5 files changed

+167
-30
lines changed

examples/junit/src/test/java/com/example/BUILD.bazel

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
load("//bazel:fuzz_target.bzl", "java_fuzz_target_test")
22

3+
java_library(
4+
name = "test_successful_exception",
5+
srcs = ["TestSuccessfulException.java"],
6+
visibility = ["//src/test/java/com/code_intelligence/jazzer/junit:__subpackages__"],
7+
)
8+
39
java_binary(
410
name = "ExampleFuzzTests",
511
testonly = True,
@@ -9,11 +15,13 @@ java_binary(
915
"//src/test/java/com/code_intelligence/jazzer/junit:__pkg__",
1016
],
1117
deps = [
18+
":test_successful_exception",
1219
"//deploy:jazzer",
1320
"//deploy:jazzer-api",
1421
"//deploy:jazzer-junit",
1522
"//examples/junit/src/main/java/com/example:parser",
1623
"//examples/junit/src/test/resources:example_seed_corpora",
24+
"@maven//:com_google_truth_truth",
1725
"@maven//:org_junit_jupiter_junit_jupiter_api",
1826
"@maven//:org_junit_jupiter_junit_jupiter_params",
1927
"@maven//:org_mockito_mockito_core",
@@ -63,18 +71,20 @@ java_fuzz_target_test(
6371
java_fuzz_target_test(
6472
name = "LifecycleFuzzTest",
6573
srcs = ["LifecycleFuzzTest.java"],
66-
allowed_findings = ["java.io.IOException"],
74+
allowed_findings = ["com.example.TestSuccessfulException"],
6775
fuzzer_args = [
68-
"-runs=0",
76+
"-runs=3",
6977
],
7078
target_class = "com.example.LifecycleFuzzTest",
7179
verify_crash_reproducer = False,
7280
runtime_deps = [
7381
":junit_runtime",
7482
],
7583
deps = [
84+
":test_successful_exception",
7685
"//examples/junit/src/main/java/com/example:parser",
7786
"//src/main/java/com/code_intelligence/jazzer/junit:fuzz_test",
87+
"@maven//:com_google_truth_truth",
7888
"@maven//:org_junit_jupiter_junit_jupiter_api",
7989
],
8090
)

examples/junit/src/test/java/com/example/LifecycleFuzzTest.java

Lines changed: 116 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,73 +16,127 @@
1616

1717
package com.example;
1818

19+
import static com.google.common.truth.Truth.assertThat;
20+
import static java.util.Arrays.asList;
21+
import static java.util.Collections.unmodifiableList;
22+
23+
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow;
1924
import com.code_intelligence.jazzer.junit.FuzzTest;
25+
import com.example.LifecycleFuzzTest.LifecycleCallbacks1;
26+
import com.example.LifecycleFuzzTest.LifecycleCallbacks2;
27+
import com.example.LifecycleFuzzTest.LifecycleCallbacks3;
2028
import java.io.IOException;
29+
import java.util.ArrayList;
30+
import java.util.Collections;
31+
import java.util.List;
2132
import org.junit.jupiter.api.AfterAll;
2233
import org.junit.jupiter.api.AfterEach;
23-
import org.junit.jupiter.api.Assertions;
2434
import org.junit.jupiter.api.BeforeAll;
2535
import org.junit.jupiter.api.BeforeEach;
2636
import org.junit.jupiter.api.Disabled;
2737
import org.junit.jupiter.api.MethodOrderer;
2838
import org.junit.jupiter.api.TestMethodOrder;
39+
import org.junit.jupiter.api.extension.AfterEachCallback;
40+
import org.junit.jupiter.api.extension.BeforeEachCallback;
2941
import org.junit.jupiter.api.extension.ExtendWith;
3042
import org.junit.jupiter.api.extension.ExtensionContext;
3143
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
3244

3345
@TestMethodOrder(MethodOrderer.MethodName.class)
3446
@ExtendWith(LifecycleFuzzTest.LifecycleInstancePostProcessor.class)
47+
@ExtendWith(LifecycleCallbacks1.class)
48+
@ExtendWith(LifecycleCallbacks2.class)
49+
@ExtendWith(LifecycleCallbacks3.class)
3550
class LifecycleFuzzTest {
36-
// In fuzzing mode, the test is invoked once on the empty input and once with Jazzer.
37-
private static final int EXPECTED_EACH_COUNT =
38-
System.getenv().getOrDefault("JAZZER_FUZZ", "").isEmpty() ? 1 : 2;
39-
40-
private static int beforeAllCount = 0;
41-
private static int beforeEachGlobalCount = 0;
42-
private static int afterEachGlobalCount = 0;
43-
private static int afterAllCount = 0;
51+
private static final ArrayList<String> events = new ArrayList<>();
4452

4553
private boolean beforeEachCalledOnInstance = false;
4654
private boolean testInstancePostProcessorCalledOnInstance = false;
4755

4856
@BeforeAll
4957
static void beforeAll() {
50-
beforeAllCount++;
58+
events.add("beforeAll");
5159
}
5260

5361
@BeforeEach
54-
void beforeEach() {
55-
beforeEachGlobalCount++;
62+
void beforeEach1() {
63+
events.add("beforeEach1");
5664
beforeEachCalledOnInstance = true;
5765
}
5866

67+
@BeforeEach
68+
void beforeEach2() {
69+
events.add("beforeEach2");
70+
}
71+
72+
@BeforeEach
73+
void beforeEach3() {
74+
events.add("beforeEach3");
75+
}
76+
5977
@Disabled
6078
@FuzzTest
6179
void disabledFuzz(byte[] data) {
80+
events.add("disabledFuzz");
6281
throw new AssertionError("This test should not be executed");
6382
}
6483

65-
@FuzzTest(maxDuration = "1s")
84+
@FuzzTest(maxExecutions = 3)
6685
void lifecycleFuzz(byte[] data) {
67-
Assertions.assertEquals(1, beforeAllCount);
68-
Assertions.assertEquals(beforeEachGlobalCount, afterEachGlobalCount + 1);
69-
Assertions.assertTrue(beforeEachCalledOnInstance);
70-
Assertions.assertTrue(testInstancePostProcessorCalledOnInstance);
86+
events.add("lifecycleFuzz");
87+
assertThat(beforeEachCalledOnInstance).isTrue();
88+
assertThat(testInstancePostProcessorCalledOnInstance).isTrue();
89+
}
90+
91+
@AfterEach
92+
void afterEach1() {
93+
events.add("afterEach1");
7194
}
7295

7396
@AfterEach
74-
void afterEach() {
75-
afterEachGlobalCount++;
97+
void afterEach2() {
98+
events.add("afterEach2");
99+
}
100+
101+
@AfterEach
102+
void afterEach3() {
103+
events.add("afterEach3");
76104
}
77105

78106
@AfterAll
79-
static void afterAll() throws IOException {
80-
afterAllCount++;
81-
Assertions.assertEquals(1, beforeAllCount);
82-
Assertions.assertEquals(EXPECTED_EACH_COUNT, beforeEachGlobalCount);
83-
Assertions.assertEquals(EXPECTED_EACH_COUNT, afterEachGlobalCount);
84-
Assertions.assertEquals(1, afterAllCount);
85-
throw new IOException();
107+
static void afterAll() throws TestSuccessfulException {
108+
events.add("afterAll");
109+
110+
boolean isRegressionTest = "".equals(System.getenv("JAZZER_FUZZ"));
111+
boolean isFuzzingFromCommandLine = System.getenv("JAZZER_FUZZ") == null;
112+
boolean isFuzzingFromJUnit = !isFuzzingFromCommandLine && !isRegressionTest;
113+
114+
final List<String> expectedBeforeEachEvents = unmodifiableList(asList("beforeEachCallback1",
115+
"beforeEachCallback2", "beforeEachCallback3", "beforeEach1", "beforeEach2", "beforeEach3"));
116+
final List<String> expectedAfterEachEvents = unmodifiableList(asList("afterEach1", "afterEach2",
117+
"afterEach3", "afterEachCallback3", "afterEachCallback2", "afterEachCallback1"));
118+
119+
ArrayList<String> expectedEvents = new ArrayList<>();
120+
expectedEvents.add("beforeAll");
121+
122+
// When run from the command-line, the fuzz test is not separately executed on the empty seed.
123+
if (isRegressionTest || isFuzzingFromJUnit) {
124+
expectedEvents.addAll(expectedBeforeEachEvents);
125+
expectedEvents.add("lifecycleFuzz");
126+
expectedEvents.addAll(expectedAfterEachEvents);
127+
}
128+
if (isFuzzingFromJUnit || isFuzzingFromCommandLine) {
129+
expectedEvents.addAll(expectedBeforeEachEvents);
130+
// TODO: Fuzz tests currently don't run before each and after each methods between fuzz test
131+
// invocations.
132+
expectedEvents.addAll(Collections.nCopies(3, "lifecycleFuzz"));
133+
expectedEvents.addAll(expectedAfterEachEvents);
134+
}
135+
136+
expectedEvents.add("afterAll");
137+
138+
assertThat(events).containsExactlyElementsIn(expectedEvents).inOrder();
139+
throw new TestSuccessfulException("Lifecycle methods invoked as expected");
86140
}
87141

88142
static class LifecycleInstancePostProcessor implements TestInstancePostProcessor {
@@ -91,4 +145,40 @@ public void postProcessTestInstance(Object o, ExtensionContext extensionContext)
91145
((LifecycleFuzzTest) o).testInstancePostProcessorCalledOnInstance = true;
92146
}
93147
}
148+
149+
static class LifecycleCallbacks1 implements BeforeEachCallback, AfterEachCallback {
150+
@Override
151+
public void beforeEach(ExtensionContext extensionContext) {
152+
events.add("beforeEachCallback1");
153+
}
154+
155+
@Override
156+
public void afterEach(ExtensionContext extensionContext) {
157+
events.add("afterEachCallback1");
158+
}
159+
}
160+
161+
static class LifecycleCallbacks2 implements BeforeEachCallback, AfterEachCallback {
162+
@Override
163+
public void beforeEach(ExtensionContext extensionContext) {
164+
events.add("beforeEachCallback2");
165+
}
166+
167+
@Override
168+
public void afterEach(ExtensionContext extensionContext) {
169+
events.add("afterEachCallback2");
170+
}
171+
}
172+
173+
static class LifecycleCallbacks3 implements BeforeEachCallback, AfterEachCallback {
174+
@Override
175+
public void beforeEach(ExtensionContext extensionContext) {
176+
events.add("beforeEachCallback3");
177+
}
178+
179+
@Override
180+
public void afterEach(ExtensionContext extensionContext) {
181+
events.add("afterEachCallback3");
182+
}
183+
}
94184
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2023 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example;
18+
19+
/**
20+
* An exception thrown by a java_fuzz_target_test if the test run is considered successful.
21+
*
22+
* <p>Use this instead of a generic exception to ensure that tests do not pass if such a generic
23+
* exception is thrown unexpectedly.
24+
* <p>Use this instead of {@link com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow} and other
25+
* Jazzer-specific exceptions as using them in tests leads to classloader issues: The exception
26+
* classes may be loaded both in the bootstrap and the system classloader depending on when exactly
27+
* the agent (and with it the bootstrap jar) is installed, which can cause in `instanceof` checks
28+
* failing unexpectedly.
29+
*/
30+
public class TestSuccessfulException extends Exception {
31+
public TestSuccessfulException(String message) {
32+
super(message);
33+
}
34+
}

src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ java_test(
245245
"@maven//:org_junit_jupiter_junit_jupiter_engine",
246246
],
247247
deps = [
248+
"//examples/junit/src/test/java/com/example:test_successful_exception",
248249
"//src/main/java/com/code_intelligence/jazzer/api:hooks",
249250
"@maven//:junit_junit",
250251
"@maven//:org_junit_platform_junit_platform_engine",

src/test/java/com/code_intelligence/jazzer/junit/LifecycleTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import static org.junit.platform.testkit.engine.EventType.STARTED;
3333
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
3434

35+
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow;
36+
import com.example.TestSuccessfulException;
3537
import java.io.IOException;
3638
import java.nio.file.Path;
3739
import org.junit.Before;
@@ -80,7 +82,7 @@ public void fuzzingEnabled() {
8082
event(type(FINISHED), container(uniqueIdSubstrings(ENGINE, CLAZZ, LIFECYCLE_FUZZ)),
8183
finishedSuccessfully()),
8284
event(type(FINISHED), container(uniqueIdSubstrings(ENGINE, CLAZZ)),
83-
finishedWithFailure(instanceOf(IOException.class))),
85+
finishedWithFailure(instanceOf(TestSuccessfulException.class))),
8486
event(type(FINISHED), container(ENGINE), finishedSuccessfully()));
8587

8688
results.testEvents().assertEventsMatchExactly(
@@ -116,7 +118,7 @@ public void fuzzingDisabled() {
116118
container(uniqueIdSubstrings(ENGINE, CLAZZ, LIFECYCLE_FUZZ))),
117119
event(type(FINISHED), container(uniqueIdSubstrings(ENGINE, CLAZZ, LIFECYCLE_FUZZ))),
118120
event(type(FINISHED), container(uniqueIdSubstrings(ENGINE, CLAZZ)),
119-
finishedWithFailure(instanceOf(IOException.class))),
121+
finishedWithFailure(instanceOf(TestSuccessfulException.class))),
120122
event(type(FINISHED), container(ENGINE), finishedSuccessfully()));
121123

122124
results.testEvents().assertEventsMatchExactly(

0 commit comments

Comments
 (0)