blob: a197edc3956b69313bd2b1fb9a6d57e516388af2 [file] [log] [blame]
// Copyright 2022 Code Intelligence GmbH
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.code_intelligence.jazzer.junit;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.testkit.engine.EventConditions.container;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.EventConditions.test;
import static org.junit.platform.testkit.engine.EventConditions.type;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.platform.launcher.TagFilter;
import org.junit.platform.testkit.engine.EngineExecutionResults;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.junit.platform.testkit.engine.EventType;
import org.junit.rules.TemporaryFolder;
import org.opentest4j.AssertionFailedError;
public class FuzzingWithCrashTest {
private static final String CRASHING_SEED_NAME = "crashing_seed";
// Crashes ByteFuzzTest since 'b' % 2 == 0.
private static final byte[] CRASHING_SEED_CONTENT = new byte[] {'b', 'a', 'c'};
private static final String CRASHING_SEED_DIGEST = "5e4dec23c9afa48bd5bee3daa2a0ab66e147012b";
@Rule public TemporaryFolder temp = new TemporaryFolder();
Path baseDir;
Path inputsDirectory;
@Before
public void setup() throws IOException {
baseDir = temp.getRoot().toPath();
// Create a fake test resource directory structure with an inputs directory to verify that
// Jazzer uses it and emits a crash file into it.
inputsDirectory = baseDir.resolve(
Paths.get("src", "test", "resources", "com", "example", "ValidFuzzTestsInputs"));
Files.createDirectories(inputsDirectory);
Files.write(inputsDirectory.resolve(CRASHING_SEED_NAME), CRASHING_SEED_CONTENT);
}
private EngineExecutionResults executeTests() {
return EngineTestKit.engine("com.code_intelligence.jazzer")
.selectors(selectClass("com.example.ValidFuzzTests"))
.filters(TagFilter.includeTags("jazzer"))
.configurationParameter(
"jazzer.instrument", "com.other.package.**,com.example.**,com.yet.another.package.*")
.configurationParameter("jazzer.internal.basedir", baseDir.toAbsolutePath().toString())
.execute();
}
@Test
public void fuzzingEnabled() throws IOException {
assumeFalse(System.getenv("JAZZER_FUZZ").isEmpty());
EngineExecutionResults results = executeTests();
results.containerEvents().debug().assertEventsMatchExactly(
event(type(EventType.STARTED), container("com.code_intelligence.jazzer")),
event(type(EventType.FINISHED), container("com.code_intelligence.jazzer")));
results.testEvents().debug().assertEventsMatchExactly(
event(type(EventType.STARTED),
test("com.example.ValidFuzzTests", "byteFuzz(byte[]) (Fuzzing)")),
event(type(EventType.FINISHED),
test("com.example.ValidFuzzTests", "byteFuzz(byte[]) (Fuzzing)"),
finishedWithFailure(instanceOf(AssertionFailedError.class))),
event(type(EventType.SKIPPED),
test("com.example.ValidFuzzTests", "noCrashFuzz(byte[]) (Fuzzing)")),
event(type(EventType.SKIPPED),
test("com.example.ValidFuzzTests", "dataFuzz(FuzzedDataProvider) (Fuzzing)")));
// Jazzer first tries the empty input, which doesn't crash the ByteFuzzTest. The second input is
// the seed we planted, which is crashing, so verify that a crash file with the same content is
// created in our fake seed corpus, but not in the current working directory.
try (Stream<Path> crashFiles = Files.list(baseDir).filter(
path -> path.getFileName().toString().startsWith("crash-"))) {
assertThat(crashFiles).isEmpty();
}
try (Stream<Path> seeds = Files.list(inputsDirectory)) {
assertThat(seeds).containsExactly(inputsDirectory.resolve("crash-" + CRASHING_SEED_DIGEST),
inputsDirectory.resolve(CRASHING_SEED_NAME));
}
assertThat(Files.readAllBytes(inputsDirectory.resolve("crash-" + CRASHING_SEED_DIGEST)))
.isEqualTo(CRASHING_SEED_CONTENT);
// Verify that the engine created the generated corpus directory. As a seed produced the crash,
// it should be empty.
Path generatedCorpus =
baseDir.resolve(Paths.get(".cifuzz-corpus", "com.example.ValidFuzzTests"));
assertThat(Files.isDirectory(generatedCorpus)).isTrue();
try (Stream<Path> entries = Files.list(generatedCorpus)) {
assertThat(entries).isEmpty();
}
}
@Test
public void fuzzingDisabled() throws IOException {
assumeTrue(System.getenv("JAZZER_FUZZ").isEmpty());
EngineExecutionResults results = executeTests();
// When fuzzing isn't requested, the Jazzer test engine doesn't discover any tests.
results.containerEvents().debug().assertEventsMatchExactly(
event(type(EventType.STARTED), container("com.code_intelligence.jazzer")),
event(type(EventType.FINISHED), container("com.code_intelligence.jazzer"),
finishedSuccessfully()));
results.testEvents().debug().assertEventsMatchExactly();
// No fuzzing means no crashes means no new seeds.
try (Stream<Path> seeds = Files.list(inputsDirectory)) {
assertThat(seeds).containsExactly(inputsDirectory.resolve(CRASHING_SEED_NAME));
}
}
}