Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmake/FuzzTestHelpers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ function(fuzztest_flatbuffers_generate_headers)
set(generated_include_dir "${generated_target_dir}")
if (NOT ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX} STREQUAL "")
set(generated_include_dir "${generated_include_dir}/${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX}")
list(APPEND FLATBUFFERS_GENERATE_HEADERS_FLAGS
list(APPEND FLATBUFFERS_GENERATE_HEADERS_FLAGS
"--include-prefix" ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX})
endif()

Expand Down
1 change: 1 addition & 0 deletions e2e_tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ 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_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",
Expand Down
10 changes: 10 additions & 0 deletions e2e_tests/functional_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,16 @@ 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()."));
}

// Tests for the FuzzTest command line interface.
class GenericCommandLineInterfaceTest : public ::testing::Test {
protected:
Expand Down
10 changes: 10 additions & 0 deletions e2e_tests/testdata/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ 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_tests_for_corpus_database_testing",
testonly = 1,
Expand Down
16 changes: 16 additions & 0 deletions e2e_tests/testdata/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,19 @@ 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
)
target_link_libraries(
fuzz_test_without_init_fuzztest.stripped
PRIVATE
fuzztest::fuzztest
GTest::gtest
)
set_target_properties(
fuzz_test_without_init_fuzztest.stripped
PROPERTIES RUNTIME_OUTPUT_DIRECTORY
"${CMAKE_BINARY_DIR}/_main/e2e_tests/testdata"
)
28 changes: 28 additions & 0 deletions e2e_tests/testdata/fuzz_test_without_init_fuzztest.cc
Original file line number Diff line number Diff line change
@@ -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();
}
3 changes: 2 additions & 1 deletion fuzztest/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)

Expand Down Expand Up @@ -161,7 +163,6 @@ cc_library(
testonly = True,
srcs = ["llvm_fuzzer_wrapper.cc"],
deps = [
":fuzztest",
":fuzztest_macros",
"@abseil-cpp//absl/base:core_headers",
"@abseil-cpp//absl/base:no_destructor",
Expand Down
1 change: 1 addition & 0 deletions fuzztest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ fuzztest_cc_library(
DEPS
fuzztest::domain
fuzztest::fuzztest_macros
fuzztest::googletest_adaptor
)

fuzztest_cc_library(
Expand Down
5 changes: 4 additions & 1 deletion fuzztest/init_fuzztest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,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();
runtime.SetArgs(argc, argv);
const bool is_listing = absl::GetFlag(FUZZTEST_FLAG(list_fuzz_tests));
if (is_listing) {
for (const auto& name : ListRegisteredTests()) {
Expand Down Expand Up @@ -488,7 +490,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);
Expand Down
52 changes: 28 additions & 24 deletions fuzztest/internal/googletest_adaptor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "./fuzztest/internal/googletest_adaptor.h"

#include <cstdlib>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
Expand All @@ -25,12 +26,13 @@

namespace fuzztest::internal {

std::vector<std::string> GTest_TestAdaptor::GetFuzzTestsInCurrentShard() const {
std::vector<std::string> GTest_TestAdaptor::GetFuzzTestsInCurrentShard(
const Configuration& configuration) const {
std::vector<std::string> 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);
Expand All @@ -43,13 +45,13 @@ std::vector<std::string> GTest_TestAdaptor::GetFuzzTestsInCurrentShard() const {

namespace {
template <typename T>
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> 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(),
Expand All @@ -73,8 +75,7 @@ void RegisterFuzzTestAsGTest(int* argc, char*** argv, FuzzTest& test,

template <typename T>
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<std::string> crash_inputs =
Expand All @@ -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<T>(argc, argv, test, updated_configuration, input);
RegisterFuzzTestAsGTest<T>(test, updated_configuration, input);
}
}

template <typename T>
void RegisterTests(int* argc, char*** argv, FuzzTest& test,
const Configuration& configuration) {
RegisterFuzzTestAsGTest<T>(argc, argv, test, configuration);
RegisterSeparateRegressionTestForEachCrashingInput<T>(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);
}
});

Expand Down Expand Up @@ -194,4 +196,6 @@ std::vector<const testing::TestInfo*> GetRegisteredTests() {
return result;
}

bool ForceLinkGoogleTestAdaptor() { return true; }

} // namespace fuzztest::internal
54 changes: 33 additions & 21 deletions fuzztest/internal/googletest_adaptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#define FUZZTEST_FUZZTEST_GOOGLETEST_ADAPTOR_H_

#include <cstdlib>
#include <optional>
#include <string>
#include <utility>
#include <vector>
Expand All @@ -30,40 +31,50 @@ 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> 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) {
// In "bug reproduction" mode, sometimes we need to reproduce multiple
// 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());
#else
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(
Expand All @@ -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)),
Expand All @@ -85,15 +96,17 @@ 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.";
}
} 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.";
Expand All @@ -113,12 +126,11 @@ class GTest_TestAdaptor : public ::testing::Test {
}

private:
std::vector<std::string> GetFuzzTestsInCurrentShard() const;
std::vector<std::string> GetFuzzTestsInCurrentShard(
const Configuration& configuration) const;

FuzzTest& test_;
int* argc_;
char*** argv_;
Configuration configuration_;
std::optional<Configuration> configuration_;
};

template <typename Base, typename TestPartResult>
Expand All @@ -143,8 +155,8 @@ class GTest_EventListener : public Base {
};

// 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.
Expand Down
4 changes: 2 additions & 2 deletions fuzztest/internal/registry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ void ForEachTest(absl::FunctionRef<void(FuzzTest&)> func) {
for (auto& t : Regs()) func(t);
}

void RegisterImpl(BasicTestInfo test_info, FuzzTestFuzzerFactory factory) {
Regs().emplace_back(std::move(test_info), std::move(factory));
FuzzTest& RegisterImpl(BasicTestInfo test_info, FuzzTestFuzzerFactory factory) {
return (Regs().emplace_back(std::move(test_info), std::move(factory)));
}

void RegisterSetUpTearDownTestSuiteFunctions(
Expand Down
Loading
Loading