feat(test): add fuzz testing support
Some checks reported errors
continuous-integration/drone/push Build was killed

This commit is contained in:
light7734 2025-07-30 23:02:53 +03:30
parent 638a009047
commit 60ad7cdc70
Signed by: light7734
GPG key ID: 8C30176798F1A6BA
5 changed files with 169 additions and 4 deletions

View file

@ -1,2 +1,4 @@
add_library_module(test test.cpp entrypoint.cpp)
add_library_module(fuzz_test test.cpp fuzz.cpp)
add_test_module(test test.test.cpp)

View file

@ -0,0 +1,24 @@
#include <test/test.hpp>
namespace lt::test {
auto process_fuzz_input(const uint8_t *data, size_t size) -> int32_t
try
{
details::Registry::process_fuzz_input(data, size);
return EXIT_SUCCESS;
}
catch (const std::exception &exp)
{
std::cout << "Fuzz input resulted in uncaught exception:\n";
std::cout << "\texception.what: " << exp.what() << '\n';
std::cout << "\tinput size: " << size << '\n';
return EXIT_FAILURE;
}
}; // namespace lt::test
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
return lt::test::process_fuzz_input(data, size);
}

View file

@ -0,0 +1,62 @@
#include <cstring>
#include <test/test.hpp>
namespace lt::test {
class FuzzDataProvider
{
public:
FuzzDataProvider(const uint8_t *data, size_t size): m_data(data, size)
{
}
template<typename T>
requires(
std::is_trivially_constructible_v<T> //
&& std::is_trivially_copy_constructible_v<T> //
&& std::is_trivially_copy_assignable_v<T>
)
auto consume() -> std::optional<T>
{
if (m_data.size() < sizeof(T))
{
return std::nullopt;
}
T value;
std::memcpy(&value, m_data.data(), sizeof(T));
m_data = m_data.subspan(sizeof(T));
return value;
}
auto consume_string(size_t size) -> std::optional<std::string>
{
if (m_data.size() < size)
{
return std::nullopt;
}
// NOLINTNEXTLINE
auto value = std::string { (const char *)m_data.data(), size };
m_data = m_data.subspan(size);
return value;
}
auto consume_remaining_as_string() -> std::string
{
if (m_data.empty())
{
return std::string {};
}
return { m_data.begin(), m_data.end() };
};
private:
std::span<const uint8_t> m_data;
};
} // namespace lt::test

View file

@ -27,13 +27,26 @@ namespace details {
class Registry
{
public:
using Suite = void (*)();
using FuzzFunction = int32_t (*)(const uint8_t *, size_t);
using SuiteFunction = void (*)();
static void register_suite(Suite suite)
static void register_suite(SuiteFunction suite)
{
instance().m_suites.emplace_back(suite);
}
static void register_fuzz_harness(FuzzFunction suite)
{
if (instance().m_fuzz_harness)
{
throw std::logic_error {
"Attempting to register fuzz harness while one is already registered",
};
}
instance().m_fuzz_harness = suite;
}
static auto run_all() -> int32_t
{
for (auto &test : instance().m_suites)
@ -49,6 +62,18 @@ public:
return instance().m_failed_count;
}
static auto process_fuzz_input(const uint8_t *data, size_t size) -> int32_t
{
if (!instance().m_fuzz_harness)
{
throw std::logic_error {
"Attempting to process fuzz input with no active harness",
};
}
return instance().m_fuzz_harness(data, size);
}
static void increment_passed_count()
{
++instance().m_pasesed_count;
@ -71,7 +96,9 @@ private:
return registry;
}
std::vector<void (*)()> m_suites;
std::vector<SuiteFunction> m_suites;
FuzzFunction m_fuzz_harness {};
int32_t m_pasesed_count {};
int32_t m_failed_count {};
@ -85,7 +112,7 @@ struct Case
auto operator=(std::invocable auto test) -> void // NOLINT
{
std::cout << "[Running-----------] --> ";
std::cout << name << '\n';
std::cout << name << '\n';
try
{
@ -117,6 +144,18 @@ struct TestSuite
}
};
struct TestFuzzHarness
{
template<class TestFuzzHarness>
constexpr TestFuzzHarness(TestFuzzHarness suite)
{
#ifndef LIGHT_SKIP_FUZZ_TESTS
details::Registry::register_fuzz_harness(+suite);
#endif
}
};
using Suite = const TestSuite;
using FuzzHarness = const TestFuzzHarness;
} // namespace lt::test

View file

@ -113,6 +113,44 @@ function (add_test_module target_lib_name)
)
endfunction ()
function (add_fuzz_module target_lib_name)
if (NOT ${ENABLE_TESTS})
return()
endif ()
set(source_files)
set(source_directory "${CMAKE_CURRENT_SOURCE_DIR}/private")
foreach (source_file ${ARGN})
list(APPEND source_files "${source_directory}/${source_file}")
endforeach ()
message("Adding fuzz executable ${target_lib_name}_fuzz with source files: ${source_files}")
set(PUBLIC_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/public_includes")
file(MAKE_DIRECTORY "${PUBLIC_INCLUDE_DIR}")
file(CREATE_LINK
"${CMAKE_CURRENT_SOURCE_DIR}/public/"
"${PUBLIC_INCLUDE_DIR}/${target_lib_name}"
SYMBOLIC
)
set(PRIVATE_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/private_includes")
file(MAKE_DIRECTORY "${PRIVATE_INCLUDE_DIR}")
file(CREATE_LINK
"${CMAKE_CURRENT_SOURCE_DIR}/private/"
"${PRIVATE_INCLUDE_DIR}/${target_lib_name}"
SYMBOLIC
)
add_executable(${target_lib_name}_fuzz ${source_files})
target_link_libraries(${target_lib_name}_fuzz PRIVATE ${target_lib_name} base fuzz_test)
target_link_options(${target_lib_name}_fuzz PRIVATE -fsanitize=fuzzer)
target_compile_options(${target_lib_name}_fuzz PRIVATE -fsanitize=fuzzer)
target_include_directories(${target_lib_name}_fuzz
PRIVATE ${PUBLIC_INCLUDE_DIR}
PRIVATE ${PRIVATE_INCLUDE_DIR}
)
endfunction ()
function (add_option option help)
option(${option} ${help})