Skip to content

Commit

Permalink
Expose a mechanism for registring fuzz tests with dynamically generat…
Browse files Browse the repository at this point in the history
…ed names.

PiperOrigin-RevId: 576953286
  • Loading branch information
fniksic authored and copybara-github committed Oct 26, 2023
1 parent f2bc69f commit 88dde14
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 24 deletions.
1 change: 1 addition & 0 deletions e2e_tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ cc_test(
srcs = ["functional_test.cc"],
data = [
"@com_google_fuzztest//centipede:centipede_uninstrumented",
"@com_google_fuzztest//e2e_tests/testdata:dynamically_registered_fuzz_tests.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",
],
Expand Down
16 changes: 14 additions & 2 deletions e2e_tests/functional_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -548,8 +548,9 @@ class GenericCommandLineInterfaceTest : public ::testing::Test {
RunResults RunWith(
std::string_view flags,
const absl::flat_hash_map<std::string, std::string>& env = {},
absl::Duration timeout = absl::Minutes(10)) {
std::vector<std::string> args = {BinaryPath(kDefaultTargetBinary)};
absl::Duration timeout = absl::Minutes(10),
absl::string_view binary = kDefaultTargetBinary) {
std::vector<std::string> args = {BinaryPath(binary)};
std::vector<std::string> split_flags = absl::StrSplit(flags, ' ');
args.insert(args.end(), split_flags.begin(), split_flags.end());
return RunCommand(args, env, timeout);
Expand All @@ -565,6 +566,17 @@ TEST_F(GenericCommandLineInterfaceTest, FuzzTestsAreFoundInTheBinary) {
EXPECT_THAT(status, Eq(ExitCode(0)));
}

TEST_F(GenericCommandLineInterfaceTest,
DynamicallyRegisteredFuzzTestsAreFound) {
auto [status, std_out, std_err] =
RunWith(/*flags=*/"--list_fuzz_tests", /*env=*/{},
/*timeout=*/absl::Minutes(1),
/*binary=*/"testdata/dynamically_registered_fuzz_tests");
EXPECT_THAT(std_out, HasSubstr("[*] Fuzz test: TestSuiteOne.DoesNothing/1"));
EXPECT_THAT(std_out, HasSubstr("[*] Fuzz test: TestSuiteTwo.DoesNothing/2"));
EXPECT_THAT(status, Eq(ExitCode(0)));
}

// Tests for the FuzzTest command line interface in fuzzing mode, which can only
// run with coverage instrumentation enabled.
class FuzzingModeCommandLineInterfaceTest
Expand Down
10 changes: 10 additions & 0 deletions e2e_tests/testdata/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,13 @@ cc_binary(
"@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
],
)

cc_binary(
name = "dynamically_registered_fuzz_tests",
testonly = 1,
srcs = ["dynamically_registered_fuzz_tests.cc"],
deps = [
"@com_google_fuzztest//fuzztest",
"@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
],
)
11 changes: 11 additions & 0 deletions e2e_tests/testdata/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,14 @@ set_target_properties(
PROPERTIES RUNTIME_OUTPUT_DIRECTORY
"${CMAKE_BINARY_DIR}/com_google_fuzztest/e2e_tests/testdata"
)

add_executable(
dynamically_registered_fuzz_tests.stripped
dynamically_registered_fuzz_tests.cc
)
link_fuzztest(dynamically_registered_fuzz_tests.stripped)
set_target_properties(
dynamically_registered_fuzz_tests.stripped
PROPERTIES RUNTIME_OUTPUT_DIRECTORY
"${CMAKE_BINARY_DIR}/com_google_fuzztest/e2e_tests/testdata"
)
29 changes: 29 additions & 0 deletions e2e_tests/testdata/dynamically_registered_fuzz_tests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2023 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.

#include "./fuzztest/fuzztest.h"

namespace {

void DoesNothing(int) {}

[[maybe_unused]] static bool registration = [] {
fuzztest::RegisterFuzzTest(fuzztest::GetRegistration(
"TestSuiteOne", "DoesNothing/1", __FILE__, __LINE__, DoesNothing));
fuzztest::RegisterFuzzTest(fuzztest::GetRegistration(
"TestSuiteTwo", "DoesNothing/2", __FILE__, __LINE__, DoesNothing));
return true;
}();

} // namespace
1 change: 1 addition & 0 deletions fuzztest/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ cc_library(
deps = [
":domain",
":io",
":registration",
":registry",
],
)
Expand Down
1 change: 1 addition & 0 deletions fuzztest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ target_link_libraries(
PUBLIC
fuzztest_domain
fuzztest_io
fuzztest_registration
fuzztest_registry
fuzztest_runtime
absl::str_format
Expand Down
3 changes: 3 additions & 0 deletions fuzztest/fuzztest.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
#include <tuple>
#include <vector>

// IWYU pragma: begin_exports
#include "./fuzztest/domain.h"
#include "./fuzztest/internal/registration.h"
#include "./fuzztest/internal/registry.h"
// IWYU pragma: end_exports

namespace fuzztest {

Expand Down
51 changes: 49 additions & 2 deletions fuzztest/internal/registration.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
#include "./fuzztest/internal/meta.h"
#include "./fuzztest/internal/type_support.h"

namespace fuzztest::internal {
namespace fuzztest {
namespace internal {

struct BasicTestInfo {
std::string suite_name;
Expand Down Expand Up @@ -295,6 +296,52 @@ class Registration : private Base {
TargetFunction target_function_;
};

} // namespace fuzztest::internal
} // namespace internal

// Returns a registration for a fuzz test based on `Fixture` and
// `target_function`. `target_function` must be a pointer to a member function
// of `Fixture` or its base class.
//
// This is an advanced API; in almost all cases you should prefer registring
// your fixture-based fuzz tests with the FUZZ_TEST_F macro. Unlike the macro,
// this function allows customizing `suite_name`, `test_name`, `file`, and
// `line`. For example, it is suitable when you want to register fuzz tests with
// dynamically generated test names.
//
// Note: If `Fixture` is a GoogleTest fixture, make sure that all fuzz tests
// registered with `suite_name` also use `Fixture`. Otherwise, you may encounter
// undefined behavior when it comes to test suite setup and teardown.
template <typename Fixture, typename TargetFunction>
auto GetRegistrationWithFixture(std::string suite_name, std::string test_name,
std::string file, int line,
TargetFunction target_function) {
return ::fuzztest::internal::Registration<Fixture, TargetFunction>(
::fuzztest::internal::BasicTestInfo{std::move(suite_name),
std::move(test_name), std::move(file),
line, true},
target_function);
}

// Returns a registration for a fuzz test based on `target_function`, which
// should be a function pointer.
//
// This is an advanced API; in almost all cases you should prefer registring
// your fuzz tests with the FUZZ_TEST macro. Unlike the macro, this function
// allows customizing `suite_name`, `test_name`, `file`, and `line`. For
// example, it is suitable when you want to register fuzz tests with dynamically
// generated test names.
template <typename TargetFunction>
auto GetRegistration(std::string suite_name, std::string test_name,
std::string file, int line,
TargetFunction target_function) {
return ::fuzztest::internal::Registration<::fuzztest::internal::NoFixture,
TargetFunction>(
::fuzztest::internal::BasicTestInfo{std::move(suite_name),
std::move(test_name), std::move(file),
line, false},
target_function);
}

} // namespace fuzztest

#endif // FUZZTEST_FUZZTEST_INTERNAL_REGISTRATION_H_
52 changes: 32 additions & 20 deletions fuzztest/internal/registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
#include "./fuzztest/internal/registration.h"
#include "./fuzztest/internal/runtime.h"

namespace fuzztest::internal {
namespace fuzztest {
namespace internal {

void RegisterImpl(BasicTestInfo test_info, FuzzTestFuzzerFactory factory);

Expand Down Expand Up @@ -67,7 +68,7 @@ struct RegistrationToken {

template <typename RegBase, typename Fixture, typename TargetFunction,
typename SeedProvider>
FuzzTestFuzzerFactory GetFuzzTestFuzzerFactory(
static FuzzTestFuzzerFactory GetFuzzTestFuzzerFactory(
Registration<Fixture, TargetFunction, RegBase, SeedProvider>&& reg) {
#if defined(FUZZTEST_COMPATIBILITY_MODE) && defined(FUZZTEST_USE_CENTIPEDE)
#error FuzzTest compatibility mode cannot work together with Centipede.
Expand Down Expand Up @@ -100,23 +101,34 @@ struct RegisterStub {
}
};

#define INTERNAL_FUZZ_TEST(suite_name, func) \
[[maybe_unused]] static ::fuzztest::internal::RegistrationToken \
fuzztest_reg_##suite_name##func = \
::fuzztest::internal::RegistrationToken{} = \
::fuzztest::internal::Registration< \
::fuzztest::internal::NoFixture, decltype(+func)>( \
{#suite_name, #func, __FILE__, __LINE__, false}, +func)

#define INTERNAL_FUZZ_TEST_F(suite_name, test_name, fixture, func) \
[[maybe_unused]] static ::fuzztest::internal::RegistrationToken \
fuzztest_reg_##suite_name##test_name = \
::fuzztest::internal::RegistrationToken{} = \
::fuzztest::internal::Registration<fixture, \
decltype(&fixture::func)>( \
{#suite_name, #test_name, __FILE__, __LINE__, true}, \
&fixture::func)

} // namespace fuzztest::internal
#define INTERNAL_FUZZ_TEST(suite_name, func) \
[[maybe_unused]] static ::fuzztest::internal::RegistrationToken \
fuzztest_reg_##suite_name##func = \
::fuzztest::internal::RegistrationToken{} = \
::fuzztest::GetRegistration<decltype(+func)>( \
#suite_name, #func, __FILE__, __LINE__, +func)

#define INTERNAL_FUZZ_TEST_F(suite_name, test_name, fixture, func) \
[[maybe_unused]] static ::fuzztest::internal::RegistrationToken \
fuzztest_reg_##suite_name##test_name = \
::fuzztest::internal::RegistrationToken{} = \
::fuzztest::GetRegistrationWithFixture< \
fixture, decltype(&fixture::func)>( \
#suite_name, #test_name, __FILE__, __LINE__, &fixture::func)

} // namespace internal

// Registers a fuzz test defined by the registration `reg`. You can obtain a
// fuzz test registration by calling functions `GetRegistration()` and
// `GetRegistrationWithFixture()`.
//
// Make sure to only call this function before calling
// `RegisterFuzzTestsAsGoogleTests()`.
template <typename Registration>
void RegisterFuzzTest(Registration&& reg) {
::fuzztest::internal::RegistrationToken{} = std::forward<Registration>(reg);
}

} // namespace fuzztest

#endif // FUZZTEST_FUZZTEST_INTERNAL_REGISTRY_H_

0 comments on commit 88dde14

Please sign in to comment.