From 3f42dddd194dd3dba9942f1d0a266b981ffa2688 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Sat, 14 Dec 2024 16:34:27 +0100 Subject: [PATCH] tests: Improve unit tests for includes and patterns --- includes/type/magic.pat | 24 +++++----- tests/includes/CMakeLists.txt | 1 + tests/includes/source/main.cpp | 55 ++++++++++++++++++++-- tests/magic/CMakeLists.txt | 7 +-- tests/patterns/CMakeLists.txt | 2 + tests/patterns/source/main.cpp | 84 ++++++++++++++++++++++------------ 6 files changed, 125 insertions(+), 48 deletions(-) diff --git a/includes/type/magic.pat b/includes/type/magic.pat index 191d5b7..d8a2451 100644 --- a/includes/type/magic.pat +++ b/includes/type/magic.pat @@ -9,8 +9,13 @@ import std.ctype; namespace auto type { - fn fm(ref auto value) - { + + /** + Escapes all bytes in a string to only contain printable characters. All non-printable bytes will be transformed to sequences in the form form \xFF + @param value Byte array to escape + @return Escaped string + */ + fn escape_bytes(ref auto value) { str result; for (u32 i = 0, i < sizeof(value), i += 1) { @@ -21,23 +26,20 @@ namespace auto type else result += std::format("\\x{:02X}", u8(c)); } - return std::format("\"{}\"", result); + return result; }; /** A Magic number. Throws an error if the magic number does not match the expected value @tparam ExpectedValue A string representing the expected value */ - struct Magic - { + struct Magic { char value[std::string::length(ExpectedValue)]; - std::assert_warn(value == ExpectedValue, std::format("Invalid magic value! Expected {}, got {} at position 0x{:X}", type::fm(ExpectedValue), type::fm(value), $ - std::string::length(ExpectedValue))); + std::assert(value == ExpectedValue, std::format("Invalid magic value! Expected \"{}\", got \"{}\" at position 0x{:X}", type::escape_bytes(ExpectedValue), type::escape_bytes(value), $ - std::string::length(ExpectedValue))); } [[sealed, format("type::impl::format_magic")]]; - namespace impl - { - fn format_magic(ref auto magic) - { - return type::fm(magic.value); + namespace impl { + fn format_magic(ref auto magic) { + return std::format("\"{}\"", type::escape_bytes(magic.value)); }; } } \ No newline at end of file diff --git a/tests/includes/CMakeLists.txt b/tests/includes/CMakeLists.txt index 01971fd..ce65fc7 100644 --- a/tests/includes/CMakeLists.txt +++ b/tests/includes/CMakeLists.txt @@ -19,6 +19,7 @@ target_link_libraries(includes_test PRIVATE libpl fmt::fmt-header-only) set_target_properties(includes_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +message(STATUS "Adding Include Tests") foreach (include IN LISTS INCLUDES) file(RELATIVE_PATH INCLUDE_NAME ${PATTERN_INCLUDES} ${include}) diff --git a/tests/includes/source/main.cpp b/tests/includes/source/main.cpp index 244f0f4..c0a0ed1 100644 --- a/tests/includes/source/main.cpp +++ b/tests/includes/source/main.cpp @@ -5,6 +5,8 @@ #include #include +#include "../../cmake-build-release/_deps/pattern_language-src/lib/include/pl/core/ast/ast_node_function_definition.hpp" + #define EXIT_SKIP 77 int main(int argc, char **argv) { @@ -61,15 +63,58 @@ int main(int argc, char **argv) { }); } - // Execute pattern - if (!runtime.executeString(patternFile.readString(), "")) { - fmt::print("Error during execution!\n"); + // Parse pattern + const auto ast = runtime.parseString(patternFile.readString(), ""); + if (!ast.has_value()) { + fmt::println("Error when parsing include file!"); - if (const auto &hardError = runtime.getEvalError(); hardError.has_value()) - fmt::print("Hard error: {}:{} - {}\n\n", hardError->line, hardError->column, hardError->message); + if (const auto &compileErrors = runtime.getCompileErrors(); !compileErrors.empty()) { + for (const auto &error : compileErrors) { + fmt::println("{}", error.format()); + } + } else if (const auto &evalError = runtime.getEvalError(); evalError.has_value()) { + fmt::println("{}:{} {}", evalError->line, evalError->column, evalError->message); + } return EXIT_FAILURE; } + bool missingComments = false; + for (const std::shared_ptr &node : *ast) { + if (auto typeDecl = dynamic_cast(node.get())) { + if (typeDecl->getDocComment().empty() && !pl::hlp::containsIgnoreCase(typeDecl->getName(), "impl")) { + fmt::println("Type {} has no doc comment!", typeDecl->getName()); + missingComments = true; + } + } else if (auto functionDef = dynamic_cast(node.get())) { + if (functionDef->getDocComment().empty() && !pl::hlp::containsIgnoreCase(functionDef->getName(), "impl")) { + fmt::println("Function {} has no doc comment!", functionDef->getName()); + missingComments = true; + } + } + } + + if (missingComments) + return EXIT_FAILURE; + + if (!runtime.executeString(patternFile.readString(), "")) { + if (const auto &evalError = runtime.getEvalError(); evalError.has_value()) { + fmt::println("{}:{} {}", evalError->line, evalError->column, evalError->message); + } + + return EXIT_FAILURE; + } + + if (!runtime.getPatterns().empty()) { + fmt::println("Library created patterns!"); + return EXIT_FAILURE; + } + + if (!runtime.getSections().empty()) { + fmt::println("Library created sections!"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/tests/magic/CMakeLists.txt b/tests/magic/CMakeLists.txt index 8ead52d..50c7327 100644 --- a/tests/magic/CMakeLists.txt +++ b/tests/magic/CMakeLists.txt @@ -14,13 +14,14 @@ add_executable(magic_test ) find_package(PkgConfig REQUIRED) -pkg_search_module(MAGIC libmagic>=5.39) +pkg_search_module(MAGIC libmagic>=5.39 REQUIRED) -target_include_directories(magic_test PRIVATE include) -target_link_libraries(magic_test PRIVATE libpl magic fmt::fmt-header-only) +target_include_directories(magic_test PRIVATE include ${MAGIC_INCLUDE_DIRS}) +target_link_libraries(magic_test PRIVATE libpl ${MAGIC_LINK_LIBRARIES} fmt::fmt-header-only) set_target_properties(magic_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +message(STATUS "Adding Magic Tests") foreach (magic_file IN LISTS MAGIC_FILES) file(RELATIVE_PATH MAGIC_NAME ${MAGIC_FOLDER} ${magic_file}) diff --git a/tests/patterns/CMakeLists.txt b/tests/patterns/CMakeLists.txt index 9acb493..bdb423e 100644 --- a/tests/patterns/CMakeLists.txt +++ b/tests/patterns/CMakeLists.txt @@ -19,6 +19,7 @@ target_link_libraries(patterns_tests PRIVATE libpl fmt::fmt-header-only) set_target_properties(patterns_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +message(STATUS "Adding Pattern Tests") foreach (pattern IN LISTS PATTERNS) get_filename_component(PATTERN_NAME ${pattern} NAME) @@ -39,5 +40,6 @@ foreach (pattern IN LISTS PATTERNS) set(TEST_NAME "Patterns/${PATTERN_NAME}") add_test(NAME ${TEST_NAME} COMMAND patterns_tests "${PATTERN_NAME}" "${pattern}" "${PATTERN_INCLUDES}" WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_tests_properties(${TEST_NAME} PROPERTIES SKIP_RETURN_CODE 77) + message(STATUS " No test file available for ${PATTERN_NAME} pattern") endif () endforeach () diff --git a/tests/patterns/source/main.cpp b/tests/patterns/source/main.cpp index 295e3f1..217d822 100644 --- a/tests/patterns/source/main.cpp +++ b/tests/patterns/source/main.cpp @@ -3,40 +3,29 @@ #include #include +#include #include #define EXIT_SKIP 77 int main(int argc, char **argv) { - // If only 4 arguments have been provided, then no test file was provided. Skip the test in that case - if (argc == 4) - return EXIT_SKIP; - // Any number of arguments other than 5 are invalid - if (argc != 5) + if (argc != 4 && argc != 5) return EXIT_FAILURE; + bool hasTestFile = argc == 5; + // Extract command line arguments const std::string patternName = argv[1]; const std::fs::path patternFilePath = argv[2]; const std::fs::path includePath = argv[3]; - const std::fs::path testFilePath = argv[4]; - - if (testFilePath.empty()) - return EXIT_SKIP; - - fmt::print("Running test {} on test file {}\n", patternName, testFilePath.stem().string()); + const std::fs::path testFilePath = hasTestFile ? argv[4] : ""; // Open pattern file wolv::io::File patternFile(patternFilePath, wolv::io::File::Mode::Read); if (!patternFile.isValid()) return EXIT_FAILURE; - // Open test file - wolv::io::File testFile(testFilePath, wolv::io::File::Mode::Read); - if (!testFile.isValid()) - return EXIT_FAILURE; - // Setup Pattern Language Runtime pl::PatternLanguage runtime; bool hasDescription = false; @@ -47,12 +36,20 @@ int main(int argc, char **argv) { return true; }; - runtime.setDataSource(0x00, testFile.getSize(), - [&](pl::u64 address, pl::u8 *data, size_t size) { - testFile.seek(address); - testFile.readBuffer(data, size); - } - ); + if (hasTestFile) { + // Open test file + static wolv::io::File testFile(testFilePath, wolv::io::File::Mode::Read); + if (!testFile.isValid()) + return EXIT_FAILURE; + + runtime.setDataSource(0x00, testFile.getSize(), + [&](pl::u64 address, pl::u8 *data, size_t size) { + testFile.seek(address); + testFile.readBuffer(data, size); + } + ); + } + runtime.setDangerousFunctionCallHandler([]{ return true; }); runtime.setIncludePaths({ includePath }); runtime.addPragma("MIME", DummyPragmaHandler); @@ -66,21 +63,50 @@ int main(int argc, char **argv) { case Info: fmt::print(" [INFO] "); break; case Warning: fmt::print(" [WARN] "); break; case Error: fmt::print(" [ERROR] "); break; + default: break; } - fmt::print("{}\n", message); + fmt::println("{}", message); }); } + if (hasTestFile) { + // Execute pattern + fmt::println("Executing pattern {} using test file {}", patternName, wolv::util::toUTF8String(testFilePath.filename())); - // Execute pattern - if (!runtime.executeString(patternFile.readString(), "")) { - fmt::print("Error during execution!\n"); + if (!runtime.executeString(patternFile.readString(), "")) { + fmt::println("Error when executing pattern!"); - if (const auto &hardError = runtime.getEvalError(); hardError.has_value()) - fmt::print("Hard error: {}:{} - {}\n\n", hardError->line, hardError->column, hardError->message); + if (const auto &compileErrors = runtime.getCompileErrors(); !compileErrors.empty()) { + for (const auto &error : compileErrors) { + fmt::println("{}", error.format()); + } + } else if (const auto &evalError = runtime.getEvalError(); evalError.has_value()) { + fmt::println("{}:{} {}", evalError->line, evalError->column, evalError->message); + } - return EXIT_FAILURE; + return EXIT_FAILURE; + } + } else { + // Parse the file + fmt::println("Parsing pattern {} without executing it", patternName); + const auto ast = runtime.parseString(patternFile.readString(), ""); + if (!ast.has_value()) { + fmt::println("Error when parsing pattern!"); + + if (const auto &compileErrors = runtime.getCompileErrors(); !compileErrors.empty()) { + for (const auto &error : compileErrors) { + fmt::println("{}", error.format()); + } + } + + return EXIT_FAILURE; + } + + if (ast->empty()) { + fmt::println("Pattern {} produced no AST", patternName); + return EXIT_FAILURE; + } } if (!hasDescription) {