From d93d576cbabf30d45dd6b64aada5d1e86598d160 Mon Sep 17 00:00:00 2001 From: Yamil Morales Date: Wed, 2 Sep 2026 10:45:09 -0700 Subject: [PATCH] No public description PiperOrigin-RevId: 975213681 --- e2e_tests/BUILD | 2 + e2e_tests/functional_test.cc | 18 ++++++ e2e_tests/testdata/BUILD | 21 +++++++ e2e_tests/testdata/CMakeLists.txt | 48 ++++++++++++++++ .../testdata/fuzz_test_with_custom_main.cc | 30 ++++++++++ .../fuzz_test_without_init_fuzztest.cc | 28 +++++++++ fuzztest/BUILD | 2 + fuzztest/init_fuzztest.cc | 5 +- fuzztest/internal/googletest_adaptor.cc | 50 ++++++++-------- fuzztest/internal/googletest_adaptor.h | 57 ++++++++++++------- fuzztest/internal/registry.cc | 3 + fuzztest/internal/registry.h | 2 + fuzztest/internal/runtime.h | 20 +++++++ 13 files changed, 240 insertions(+), 46 deletions(-) create mode 100644 e2e_tests/testdata/fuzz_test_with_custom_main.cc create mode 100644 e2e_tests/testdata/fuzz_test_without_init_fuzztest.cc diff --git a/e2e_tests/BUILD b/e2e_tests/BUILD index e58abc91a..ddf5e7e47 100644 --- a/e2e_tests/BUILD +++ b/e2e_tests/BUILD @@ -48,6 +48,8 @@ cc_test( "@com_google_fuzztest//centipede:centipede_uninstrumented", "@com_google_fuzztest//e2e_tests/testdata:data", "@com_google_fuzztest//e2e_tests/testdata:dynamically_registered_fuzz_tests.stripped", + "@com_google_fuzztest//e2e_tests/testdata:fuzz_test_with_custom_main.stripped", + "@com_google_fuzztest//e2e_tests/testdata:fuzz_test_without_init_fuzztest.stripped", "@com_google_fuzztest//e2e_tests/testdata:fuzz_tests_for_functional_testing.stripped", "@com_google_fuzztest//e2e_tests/testdata:fuzz_tests_with_invalid_seeds.stripped", "@com_google_fuzztest//e2e_tests/testdata:llvm_fuzzer_with_custom_mutator.stripped", diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc index 943eb7256..83a8ac296 100644 --- a/e2e_tests/functional_test.cc +++ b/e2e_tests/functional_test.cc @@ -730,6 +730,24 @@ TEST_F(UnitTestModeTest, FuzzTestsRecordFuzzTestProperty) { Optional(IsXmlWithExactlyFuzzTestsHavingFuzzTestProperty())); } +TEST_F(UnitTestModeTest, FailsLoudlyWhenInitFuzzTestIsNotCalled) { + auto [status, std_out, std_err] = + RunWithExactFuzzerFlags("*", "testdata/fuzz_test_without_init_fuzztest"); + + EXPECT_THAT(status, Ne(ExitCode(0))); + EXPECT_THAT_LOG( + std_out, HasSubstr("FUZZ_TEST(MySuite, MyFuzzTest) was registered, but " + "InitFuzzTest was never called in main().")); +} + +TEST_F(UnitTestModeTest, PassesWhenInitFuzzTestIsCalledInCustomMain) { + auto [status, std_out, std_err] = + RunWithExactFuzzerFlags("*", "testdata/fuzz_test_with_custom_main"); + + EXPECT_THAT(status, Eq(ExitCode(0))); + EXPECT_THAT_LOG(std_out, HasSubstr("[ PASSED ] 1 test.")); +} + // Tests for the FuzzTest command line interface. class GenericCommandLineInterfaceTest : public ::testing::Test { protected: diff --git a/e2e_tests/testdata/BUILD b/e2e_tests/testdata/BUILD index 0db3a8084..548a1c2ea 100644 --- a/e2e_tests/testdata/BUILD +++ b/e2e_tests/testdata/BUILD @@ -137,6 +137,27 @@ cc_binary( ], ) +cc_binary( + name = "fuzz_test_without_init_fuzztest", + testonly = 1, + srcs = ["fuzz_test_without_init_fuzztest.cc"], + deps = [ + "@com_google_fuzztest//fuzztest", + "@googletest//:gtest", + ], +) + +cc_binary( + name = "fuzz_test_with_custom_main", + testonly = 1, + srcs = ["fuzz_test_with_custom_main.cc"], + deps = [ + "@com_google_fuzztest//fuzztest", + "@com_google_fuzztest//fuzztest:init_fuzztest", + "@googletest//:gtest", + ], +) + cc_binary( name = "fuzz_tests_for_corpus_database_testing", testonly = 1, diff --git a/e2e_tests/testdata/CMakeLists.txt b/e2e_tests/testdata/CMakeLists.txt index ec93a80aa..3dbd057a8 100644 --- a/e2e_tests/testdata/CMakeLists.txt +++ b/e2e_tests/testdata/CMakeLists.txt @@ -116,3 +116,51 @@ set_target_properties( PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/_main/e2e_tests/testdata" ) + +add_executable( + fuzz_test_without_init_fuzztest.stripped + fuzz_test_without_init_fuzztest.cc +) +if (APPLE) + target_link_libraries( + fuzz_test_without_init_fuzztest.stripped + PRIVATE + fuzztest::fuzztest + -Wl,-force_load,$ + -Wl,-force_load,$ + GTest::gtest + ) +else () + target_link_libraries( + fuzz_test_without_init_fuzztest.stripped + PRIVATE + fuzztest::fuzztest + -Wl,--whole-archive + fuzztest::init_fuzztest + fuzztest::googletest_adaptor + -Wl,--no-whole-archive + GTest::gtest + ) +endif () +set_target_properties( + fuzz_test_without_init_fuzztest.stripped + PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${CMAKE_BINARY_DIR}/_main/e2e_tests/testdata" +) + +add_executable( + fuzz_test_with_custom_main.stripped + fuzz_test_with_custom_main.cc +) +target_link_libraries( + fuzz_test_with_custom_main.stripped + PRIVATE + fuzztest::fuzztest + fuzztest::init_fuzztest + GTest::gtest +) +set_target_properties( + fuzz_test_with_custom_main.stripped + PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${CMAKE_BINARY_DIR}/_main/e2e_tests/testdata" +) diff --git a/e2e_tests/testdata/fuzz_test_with_custom_main.cc b/e2e_tests/testdata/fuzz_test_with_custom_main.cc new file mode 100644 index 000000000..6f7546e99 --- /dev/null +++ b/e2e_tests/testdata/fuzz_test_with_custom_main.cc @@ -0,0 +1,30 @@ +// Copyright 2022 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 +// +// https://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. + +#include "gtest/gtest.h" +#include "./fuzztest/fuzztest.h" +#include "fuzztest/init_fuzztest.h" + +namespace { + +void MyFuzzTest(int) {} +FUZZ_TEST(MySuite, MyFuzzTest); + +} // namespace + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + fuzztest::InitFuzzTest(&argc, &argv); + return RUN_ALL_TESTS(); +} diff --git a/e2e_tests/testdata/fuzz_test_without_init_fuzztest.cc b/e2e_tests/testdata/fuzz_test_without_init_fuzztest.cc new file mode 100644 index 000000000..3f7d6a2b6 --- /dev/null +++ b/e2e_tests/testdata/fuzz_test_without_init_fuzztest.cc @@ -0,0 +1,28 @@ +// Copyright 2022 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 +// +// https://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. + +#include "gtest/gtest.h" +#include "./fuzztest/fuzztest.h" + +namespace { + +void MyFuzzTest(int) {} +FUZZ_TEST(MySuite, MyFuzzTest); + +} // namespace + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/fuzztest/BUILD b/fuzztest/BUILD index ab25066a7..9727b3228 100644 --- a/fuzztest/BUILD +++ b/fuzztest/BUILD @@ -55,10 +55,12 @@ config_setting( cc_library( name = "fuzztest", + testonly = True, hdrs = ["fuzztest.h"], deps = [ ":domain", ":fuzztest_macros", + "@com_google_fuzztest//fuzztest/internal:googletest_adaptor", ], ) diff --git a/fuzztest/init_fuzztest.cc b/fuzztest/init_fuzztest.cc index bb94b8477..09d911688 100644 --- a/fuzztest/init_fuzztest.cc +++ b/fuzztest/init_fuzztest.cc @@ -435,6 +435,8 @@ void RunSpecifiedFuzzTest(std::string_view name, std::string_view binary_id) { void InitFuzzTest(int* argc, char*** argv, std::string_view binary_id) { auto& runtime = internal::Runtime::instance(); + runtime.SetInitFuzzTestCalled(true); + runtime.SetArgs(argc, argv); const bool is_listing = absl::GetFlag(FUZZTEST_FLAG(list_fuzz_tests)); if (is_listing) { for (const auto& name : ListRegisteredTests()) { @@ -482,7 +484,8 @@ void InitFuzzTest(int* argc, char*** argv, std::string_view binary_id) { internal::Configuration configuration = CreateConfigurationsFromFlags(derived_binary_id); configuration.reproduction_command_template = reproduction_command_template; - internal::RegisterFuzzTestsAsGoogleTests(argc, argv, configuration); + runtime.SetConfiguration(configuration); + internal::RegisterSeparateRegressionTestsForEachCrashingInput(configuration); const bool is_fuzzing_or_replaying = (fuzzing_time_limit || replay_corpus_time_limit); diff --git a/fuzztest/internal/googletest_adaptor.cc b/fuzztest/internal/googletest_adaptor.cc index d36d49862..d98ff7ad7 100644 --- a/fuzztest/internal/googletest_adaptor.cc +++ b/fuzztest/internal/googletest_adaptor.cc @@ -2,6 +2,7 @@ #include "./fuzztest/internal/googletest_adaptor.h" #include +#include #include #include #include @@ -25,12 +26,13 @@ namespace fuzztest::internal { -std::vector GTest_TestAdaptor::GetFuzzTestsInCurrentShard() const { +std::vector GTest_TestAdaptor::GetFuzzTestsInCurrentShard( + const Configuration& configuration) const { std::vector result; for (const auto* test : GetRegisteredTests()) { if (!test->should_run()) continue; if (test->is_in_another_shard()) continue; - for (const auto& fuzztest : configuration_.fuzz_tests) { + for (const auto& fuzztest : configuration.fuzz_tests) { if (fuzztest == absl::StrCat(test->test_suite_name(), ".", test->name())) { result.push_back(fuzztest); @@ -43,13 +45,13 @@ std::vector GTest_TestAdaptor::GetFuzzTestsInCurrentShard() const { namespace { template -void RegisterFuzzTestAsGTest(int* argc, char*** argv, FuzzTest& test, - const Configuration& configuration, - absl::string_view crashing_input_path = "") { - auto fixture_factory = [argc, argv, &test, - configuration = configuration]() mutable -> T* { +void RegisterFuzzTestAsGTest( + FuzzTest& test, std::optional configuration = std::nullopt, + absl::string_view crashing_input_path = "") { + auto fixture_factory = [&test, configuration = + std::move(configuration)]() mutable -> T* { return new ::fuzztest::internal::GTest_TestAdaptor( - test, argc, argv, std::move(configuration)); + test, std::move(configuration)); }; if (crashing_input_path.empty()) { ::testing::RegisterTest(test.suite_name().c_str(), test.test_name().c_str(), @@ -73,8 +75,7 @@ void RegisterFuzzTestAsGTest(int* argc, char*** argv, FuzzTest& test, template void RegisterSeparateRegressionTestForEachCrashingInput( - int* argc, char*** argv, FuzzTest& test, - const Configuration& configuration) { + FuzzTest& test, const Configuration& configuration) { if (!configuration.reproduce_findings_as_separate_tests) return; #ifdef FUZZTEST_USE_CENTIPEDE const std::vector crash_inputs = @@ -87,28 +88,29 @@ void RegisterSeparateRegressionTestForEachCrashingInput( for (const std::string& input : crash_inputs) { Configuration updated_configuration = configuration; updated_configuration.crashing_input_to_reproduce = input; - RegisterFuzzTestAsGTest(argc, argv, test, updated_configuration, input); + RegisterFuzzTestAsGTest(test, updated_configuration, input); } } -template -void RegisterTests(int* argc, char*** argv, FuzzTest& test, - const Configuration& configuration) { - RegisterFuzzTestAsGTest(argc, argv, test, configuration); - RegisterSeparateRegressionTestForEachCrashingInput(argc, argv, test, - configuration); -} - } // namespace -void RegisterFuzzTestsAsGoogleTests(int* argc, char*** argv, - const Configuration& configuration) { +void RegisterFuzzTestAsGoogleTest(FuzzTest& test) { + if (test.uses_fixture()) { + RegisterFuzzTestAsGTest<::fuzztest::internal::GTest_TestAdaptor>(test); + } else { + RegisterFuzzTestAsGTest<::testing::Test>(test); + } +} + +void RegisterSeparateRegressionTestsForEachCrashingInput( + const Configuration& configuration) { ::fuzztest::internal::ForEachTest([&](auto& test) { if (test.uses_fixture()) { - RegisterTests<::fuzztest::internal::GTest_TestAdaptor>(argc, argv, test, - configuration); + RegisterSeparateRegressionTestForEachCrashingInput< + ::fuzztest::internal::GTest_TestAdaptor>(test, configuration); } else { - RegisterTests<::testing::Test>(argc, argv, test, configuration); + RegisterSeparateRegressionTestForEachCrashingInput<::testing::Test>( + test, configuration); } }); diff --git a/fuzztest/internal/googletest_adaptor.h b/fuzztest/internal/googletest_adaptor.h index 39ffcab40..94f969197 100644 --- a/fuzztest/internal/googletest_adaptor.h +++ b/fuzztest/internal/googletest_adaptor.h @@ -16,6 +16,7 @@ #define FUZZTEST_FUZZTEST_GOOGLETEST_ADAPTOR_H_ #include +#include #include #include #include @@ -30,23 +31,33 @@ namespace fuzztest::internal { class GTest_TestAdaptor : public ::testing::Test { public: - explicit GTest_TestAdaptor(FuzzTest& test, int* argc, char*** argv, - Configuration configuration) - : test_(test), - argc_(argc), - argv_(argv), - configuration_(std::move(configuration)) {} + explicit GTest_TestAdaptor( + FuzzTest& test, std::optional configuration = std::nullopt) + : test_(test), configuration_(std::move(configuration)) {} void TestBody() override { + if (!Runtime::instance().init_fuzztest_called()) { + ADD_FAILURE() + << "FUZZ_TEST(" << test_.suite_name() << ", " << test_.test_name() + << ") was registered, but InitFuzzTest was never called in main(). " + << "If you are using a custom main(), please call " + << "fuzztest::InitFuzzTest(&argc, &argv)" + << " before RUN_ALL_TESTS()."; + return; + } RecordProperty("fuzz_test", "true"); auto test = test_.make(); - configuration_.fuzz_tests_in_current_shard = GetFuzzTestsInCurrentShard(); + Configuration configuration = configuration_.has_value() + ? *configuration_ + : Runtime::instance().configuration(); + configuration.fuzz_tests_in_current_shard = + GetFuzzTestsInCurrentShard(configuration); // We replay a reproducer in the same process to help debugging when // (1) we're replaying a single reproducer and (2) we're running locally. const bool running_locally = !std::getenv("FUZZTEST_RUNNING_UNDER_CI"); - configuration_.replay_in_single_process = - configuration_.crashing_input_to_reproduce.has_value() && + configuration.replay_in_single_process = + configuration.crashing_input_to_reproduce.has_value() && testing::UnitTest::GetInstance()->test_to_run_count() == 1 && running_locally; if (Runtime::instance().run_mode() == RunMode::kUnitTest) { @@ -54,8 +65,8 @@ class GTest_TestAdaptor : public ::testing::Test { // bugs, i.e., run multiple tests that lead to a crash. #if defined(GTEST_HAS_DEATH_TEST) && !defined(FUZZTEST_USE_CENTIPEDE) const bool needs_subprocess = - configuration_.crashing_input_to_reproduce.has_value() && - (!configuration_.replay_in_single_process || + configuration.crashing_input_to_reproduce.has_value() && + (!configuration.replay_in_single_process || // EXPECT_EXIT is required in the death-test subprocess, but in // the subprocess there's only one test to run. testing::internal::InDeathTestChild()); @@ -63,7 +74,7 @@ class GTest_TestAdaptor : public ::testing::Test { const bool needs_subprocess = false; #endif if (needs_subprocess) { - configuration_.preprocess_crash_reproducing = [] { + configuration.preprocess_crash_reproducing = [] { // EXPECT_EXIT disables event forwarding in gtest and as a result, // EXPECT/ASSERT-s are disabled. Here, we overwrite this option. testing::UnitTest::GetInstance()->listeners().SuppressEventForwarding( @@ -76,7 +87,7 @@ class GTest_TestAdaptor : public ::testing::Test { // test below fails without terminating the process. #ifdef GTEST_HAS_DEATH_TEST EXPECT_EXIT( - (test->RunInUnitTestMode(configuration_), + (test->RunInUnitTestMode(configuration), void( R"( FuzzTest failure! Please see 'actual message' below for the crash report. )"), std::exit(0)), @@ -85,7 +96,7 @@ class GTest_TestAdaptor : public ::testing::Test { EXPECT_TRUE(false) << "Death test is not supported."; #endif } else { - EXPECT_TRUE(test->RunInUnitTestMode(configuration_) || + EXPECT_TRUE(test->RunInUnitTestMode(configuration) || Runtime::instance().skipping_requested()) << "Failure(s) found in the unit-test mode - please see the test " "log for more details."; @@ -93,7 +104,9 @@ class GTest_TestAdaptor : public ::testing::Test { } else { // TODO(b/245753736): Consider using `tolerate_failure` when FuzzTest can // tolerate crashes in fuzzing mode. - EXPECT_TRUE(test->RunInFuzzingMode(argc_, argv_, configuration_) || + EXPECT_TRUE(test->RunInFuzzingMode(Runtime::instance().argc(), + Runtime::instance().argv(), + configuration) || Runtime::instance().skipping_requested()) << "Failure(s) found in the fuzzing mode - please see the test log " "for more details."; @@ -113,12 +126,11 @@ class GTest_TestAdaptor : public ::testing::Test { } private: - std::vector GetFuzzTestsInCurrentShard() const; + std::vector GetFuzzTestsInCurrentShard( + const Configuration& configuration) const; FuzzTest& test_; - int* argc_; - char*** argv_; - Configuration configuration_; + std::optional configuration_; }; template @@ -142,9 +154,12 @@ class GTest_EventListener : public Base { } }; +// Registers a single FUZZ_TEST as a GoogleTest TEST. +void RegisterFuzzTestAsGoogleTest(FuzzTest& test); + // Registers FUZZ_TEST as GoogleTest TEST-s. -void RegisterFuzzTestsAsGoogleTests(int* argc, char*** argv, - const Configuration& configuration); +void RegisterSeparateRegressionTestsForEachCrashingInput( + const Configuration& configuration); // Set listing mode validator for GoogleTest to check that fuzz test listing was // properly handled. diff --git a/fuzztest/internal/registry.cc b/fuzztest/internal/registry.cc index ac695f00c..34334d007 100644 --- a/fuzztest/internal/registry.cc +++ b/fuzztest/internal/registry.cc @@ -57,8 +57,11 @@ void ForEachTest(absl::FunctionRef func) { for (auto& t : Regs()) func(t); } +__attribute__((weak)) void RegisterFuzzTestAsGoogleTest(FuzzTest& test) {} + void RegisterImpl(BasicTestInfo test_info, FuzzTestFuzzerFactory factory) { Regs().emplace_back(std::move(test_info), std::move(factory)); + RegisterFuzzTestAsGoogleTest(Regs().back()); } void RegisterSetUpTearDownTestSuiteFunctions( diff --git a/fuzztest/internal/registry.h b/fuzztest/internal/registry.h index 0e8a44646..b2cfedde4 100644 --- a/fuzztest/internal/registry.h +++ b/fuzztest/internal/registry.h @@ -36,6 +36,8 @@ namespace internal { void RegisterImpl(BasicTestInfo test_info, FuzzTestFuzzerFactory factory); +void RegisterFuzzTestAsGoogleTest(FuzzTest& test); + void ForEachTest(absl::FunctionRef func); using SetUpTearDownTestSuiteFunction = void (*)(); diff --git a/fuzztest/internal/runtime.h b/fuzztest/internal/runtime.h index 1e0cbdd2b..4aa429177 100644 --- a/fuzztest/internal/runtime.h +++ b/fuzztest/internal/runtime.h @@ -163,6 +163,21 @@ class Runtime { void SetRunMode(RunMode run_mode) { run_mode_ = run_mode; } RunMode run_mode() const { return run_mode_; } + void SetInitFuzzTestCalled(bool v) { init_fuzztest_called_ = v; } + bool init_fuzztest_called() const { return init_fuzztest_called_; } + + void SetArgs(int* argc, char*** argv) { + argc_ = argc; + argv_ = argv; + } + int* argc() const { return argc_; } + char*** argv() const { return argv_; } + + void SetConfiguration(Configuration configuration) { + configuration_ = std::move(configuration); + } + const Configuration& configuration() const { return configuration_; } + // Enables the crash reporter. // REQUIRES: `SetCurrentTest()` has been called with non-null arguments. void EnableReporter(const RuntimeStats* stats, absl::Time (*clock_fn)()) { @@ -265,6 +280,11 @@ class Runtime { const RuntimeStats* stats_ = nullptr; absl::Time (*clock_fn_)() = nullptr; + bool init_fuzztest_called_ = false; + int* argc_ = nullptr; + char*** argv_ = nullptr; + Configuration configuration_; + // We use a simple custom spinlock instead of absl::Mutex to reduce // dependencies and avoid potential issues with code instrumentation. class ABSL_LOCKABLE Spinlock {