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(test test.cpp entrypoint.cpp)
|
||||||
|
add_library_module(fuzz_test test.cpp fuzz.cpp)
|
||||||
|
|
||||||
add_test_module(test test.test.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
|
class Registry
|
||||||
{
|
{
|
||||||
public:
|
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);
|
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
|
static auto run_all() -> int32_t
|
||||||
{
|
{
|
||||||
for (auto &test : instance().m_suites)
|
for (auto &test : instance().m_suites)
|
||||||
|
@ -49,6 +62,18 @@ public:
|
||||||
return instance().m_failed_count;
|
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()
|
static void increment_passed_count()
|
||||||
{
|
{
|
||||||
++instance().m_pasesed_count;
|
++instance().m_pasesed_count;
|
||||||
|
@ -71,7 +96,9 @@ private:
|
||||||
return registry;
|
return registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<void (*)()> m_suites;
|
std::vector<SuiteFunction> m_suites;
|
||||||
|
|
||||||
|
FuzzFunction m_fuzz_harness {};
|
||||||
|
|
||||||
int32_t m_pasesed_count {};
|
int32_t m_pasesed_count {};
|
||||||
int32_t m_failed_count {};
|
int32_t m_failed_count {};
|
||||||
|
@ -85,7 +112,7 @@ struct Case
|
||||||
auto operator=(std::invocable auto test) -> void // NOLINT
|
auto operator=(std::invocable auto test) -> void // NOLINT
|
||||||
{
|
{
|
||||||
std::cout << "[Running-----------] --> ";
|
std::cout << "[Running-----------] --> ";
|
||||||
std::cout << name << '\n';
|
std::cout << name << '\n';
|
||||||
|
|
||||||
try
|
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 Suite = const TestSuite;
|
||||||
|
using FuzzHarness = const TestFuzzHarness;
|
||||||
|
|
||||||
} // namespace lt::test
|
} // namespace lt::test
|
||||||
|
|
|
@ -113,6 +113,44 @@ function (add_test_module target_lib_name)
|
||||||
)
|
)
|
||||||
endfunction ()
|
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)
|
function (add_option option help)
|
||||||
option(${option} ${help})
|
option(${option} ${help})
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue