feat(test): add fuzz testing support
Some checks reported errors
continuous-integration/drone/push Build was killed
Some checks reported errors
continuous-integration/drone/push Build was killed
This commit is contained in:
parent
638a009047
commit
60ad7cdc70
5 changed files with 169 additions and 4 deletions
|
@ -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)
|
||||
|
|
24
modules/test/private/fuzz.cpp
Normal file
24
modules/test/private/fuzz.cpp
Normal 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);
|
||||
}
|
62
modules/test/public/fuzz.hpp
Normal file
62
modules/test/public/fuzz.hpp
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue