blob: d14d7722c19a00f4ee05e564d184dee4262e8927 [file]
// Copyright 2026 Google LLC
//
// 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.
use googletest::matchers;
use googletest::prelude::*;
use std::fs;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use test_utils::EnvVars;
fn get_target_binary_path(fixture: &EnvVars) -> PathBuf {
if let Some(path) = std::env::var_os("CARGO_BIN_EXE_replay_fuzz_tests_bin") {
return PathBuf::from(path);
}
fixture
.target_binary_path
.parent()
.expect("target_binary_path must have a parent")
.join("replay_fuzz_tests_bin")
}
#[gtest]
fn replay_by_id_reproduces_panic(fixture: &EnvVars) {
let test_name = "__fuzztest_mod__find_two_bugs_fuzz_test::find_two_bugs_fuzz_test";
let target_binary_path = get_target_binary_path(fixture);
let db_dir = fixture.tmp_dir_path.join("replay_by_id_reproduces_panic").join("corpus_db");
fs::create_dir_all(&db_dir).expect("Failed to create db directory");
let workdir_root_dir =
fixture.tmp_dir_path.join("replay_by_id_reproduces_panic").join("workdir_root");
fs::create_dir_all(&workdir_root_dir).expect("Failed to workdir_root directory");
// 1. Run Centipede to fuzz the target and populate the corpus database.
Command::new(&target_binary_path)
.arg(test_name)
.arg("--exact")
.env("FUZZTEST_FUZZ_FOR", "15s")
.env("FUZZTEST_CORPUS_DB", &db_dir)
.env("FUZZTEST_WORKDIR_ROOT", &workdir_root_dir)
.env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
.status()
.expect("Failed to spawn binary");
// 2. Retrieve the crash IDs from the corpus database using FUZZTEST_LIST_CRASH_IDS.
let crash_ids_file =
fixture.tmp_dir_path.join("replay_by_id_reproduces_panic").join("crash_ids.txt");
let status = Command::new(&target_binary_path)
.arg(test_name)
.arg("--exact")
.env("FUZZTEST_LIST_CRASH_IDS", "true")
.env("FUZZTEST_LIST_CRASH_IDS_FILE", &crash_ids_file)
.env("FUZZTEST_CORPUS_DB", &db_dir)
.env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
.status()
.expect("Failed to execute target binary to list crash IDs");
expect_true!(status.success());
let crash_ids_contents =
fs::read_to_string(&crash_ids_file).expect("Failed to read crash IDs file");
let crash_ids: Vec<&str> =
crash_ids_contents.lines().filter(|line| !line.trim().is_empty()).collect();
expect_that!(crash_ids.len(), eq(2));
// 3. Verify that replaying each crash ID reproduces a panic or segfault.
let mut reproduced_panic = false;
let mut reproduced_segfault = false;
for crash_id in &crash_ids {
let process = Command::new(&target_binary_path)
.arg(test_name)
.arg("--exact")
.arg("--nocapture")
.env("FUZZTEST_REPLAY_ID", crash_id)
.env("FUZZTEST_CORPUS_DB", &db_dir)
.env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to spawn binary");
let output = process.wait_with_output().expect("Should terminate");
let stderr = String::from_utf8_lossy(&output.stderr);
expect_that!(output.status.success(), eq(false));
expect_that!(stderr, matchers::contains_substring("FuzzTest controller reported failure"));
if stderr.contains("Bug 1 found!") {
reproduced_panic = true;
} else if stderr.contains("Bug 2 found!") {
reproduced_segfault = true;
}
}
expect_that!(reproduced_panic, eq(true));
expect_that!(reproduced_segfault, eq(true));
}
#[gtest]
fn replay_all_reproduces_all_failures(fixture: &EnvVars) {
let test_name = "__fuzztest_mod__find_two_bugs_fuzz_test::find_two_bugs_fuzz_test";
let target_binary_path = get_target_binary_path(fixture);
let db_dir =
fixture.tmp_dir_path.join("replay_all_reproduces_all_failures").join("corpus_db_all");
fs::create_dir_all(&db_dir).expect("Failed to create db directory");
let workdir_root_dir =
fixture.tmp_dir_path.join("replay_all_reproduces_all_failures").join("workdir_root_all");
fs::create_dir_all(&workdir_root_dir).expect("Failed to workdir_root_all directory");
// 1. Run Centipede to fuzz the target and populate the corpus database.
Command::new(&target_binary_path)
.arg(test_name)
.arg("--exact")
.env("FUZZTEST_FUZZ_FOR", "15s")
.env("FUZZTEST_CORPUS_DB", &db_dir)
.env("FUZZTEST_WORKDIR_ROOT", &workdir_root_dir)
.env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
.status()
.expect("Failed to spawn binary");
// 2. Replay all findings using FUZZTEST_REPLAY_FINDINGS=true.
let process = Command::new(&target_binary_path)
.arg(test_name)
.arg("--exact")
.env("FUZZTEST_REPLAY_FINDINGS", "true")
.env("FUZZTEST_CORPUS_DB", &db_dir)
.env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to spawn binary");
let output = process.wait_with_output().expect("Should terminate");
let stderr = String::from_utf8_lossy(&output.stderr);
expect_that!(output.status.success(), eq(true));
expect_that!(stderr, matchers::contains_substring("Bug 1 found!"));
expect_that!(stderr, matchers::contains_substring("Bug 2 found!"));
}