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