#include "elna/tester.hpp" #include #include #include namespace elna { std::uint32_t test_results::total() const noexcept { return m_total; } std::uint32_t test_results::passed() const noexcept { return m_passed; } std::uint32_t test_results::failed() const noexcept { return m_total - m_passed; } int test_results::exit_code() const noexcept { return m_total == m_passed ? EXIT_SUCCESS : EXIT_FAILURE; } void test_results::add_exit_code(const test_status status) noexcept { ++m_total; if (status == test_status::successful) { ++m_passed; } } static std::filesystem::path in_build_directory() { return "build/riscv"; } static std::string in_build_directory(const std::filesystem::path& path) { return in_build_directory() / path; } boost::process::v2::process_stdio get_output_streams(const std::uint8_t stream_number, boost::asio::readable_pipe& read_pipe) { if (stream_number == 1) { return boost::process::v2::process_stdio{ nullptr, read_pipe }; } if (stream_number == 2) { return boost::process::v2::process_stdio{ nullptr, {}, read_pipe }; } return boost::process::v2::process_stdio{ nullptr, read_pipe, read_pipe }; } test_result run_for_output(boost::asio::io_context& context, const std::uint8_t stream_number, const std::filesystem::path& binary, std::initializer_list arguments) { boost::asio::readable_pipe read_pipe{ context }; test_result result{}; std::string output; boost::asio::dynamic_string_buffer buffer = boost::asio::dynamic_buffer(output); boost::system::error_code ec; boost::process::v2::process_stdio process_stdio; boost::process::v2::process elna_child(context, binary, arguments, get_output_streams(stream_number, read_pipe)); do { std::size_t transferred = read_pipe.read_some(buffer.prepare(512), ec); if (transferred == 0) { break; } buffer.commit(transferred); result.output.append(boost::asio::buffer_cast(buffer.data()), buffer.size()); buffer.consume(transferred); } while (ec == boost::system::errc::success); result.code = elna_child.wait(); return result; } std::string find_object(const std::vector& environment, const std::string& object) { auto variable = std::find(environment.cbegin(), environment.cend(), object); for (const auto& variable : environment) { auto full_path = variable / object; if (std::filesystem::exists(full_path)) { return full_path.native(); } } return object; } test_result build_test(boost::asio::io_context& context, const std::filesystem::directory_entry& test_entry) { const std::filesystem::path test_filename = test_entry.path().filename(); std::filesystem::path test_binary = in_build_directory(test_filename); std::filesystem::path test_object = test_binary; test_binary.replace_extension(); test_object.replace_extension(".o"); std::filesystem::remove(test_binary); std::filesystem::remove(test_object); test_result result = run_for_output(context, 2, "./build/bin/elna", { "-o", test_object.string(), test_entry.path().string() }); if (result.code != 0) { result.status = test_status::compile_failed; return result; } std::vector environment; std::vector linker_arguments = { "-o", test_binary.string() }; for (const auto segment : boost::this_process::environment()["LIBRARY_PATH"].to_vector()) { linker_arguments.push_back("-L" + segment); environment.push_back(std::filesystem::path(segment)); } linker_arguments.push_back(find_object(environment, "crt0.o")); linker_arguments.push_back(find_object(environment, "crtbegin.o")); linker_arguments.push_back(test_object.string()); linker_arguments.insert(linker_arguments.cend(), { "--start-group", "-lgcc", "-lc", "-lgloss", "--end-group" }); linker_arguments.push_back(find_object(environment, "crtend.o")); boost::process::v2::execute(boost::process::v2::process(context, boost::process::search_path("ld"), linker_arguments )); return test_result{}; } static test_result check_expectation(const std::filesystem::directory_entry& test_entry, const test_result& run) { std::filesystem::path test_filename = test_entry.path().filename(); test_filename.replace_extension(".txt"); const std::filesystem::path expectation_path = test_entry.path().parent_path() / "expectations" / test_filename; const std::filesystem::path failures_path = test_entry.path().parent_path() / "failures" / test_filename; std::string expected_result_path; if (std::filesystem::exists(expectation_path)) { expected_result_path = expectation_path.string(); } else if (std::filesystem::exists(failures_path)) { expected_result_path = failures_path.string(); } else { return test_result{ test_status::expectation_not_found, run.code, run.output }; } boost::process::opstream pipe_stream; boost::process::child diff( boost::process::search_path("diff"), "-Nur", "--color", expected_result_path, "-", boost::process::std_in < pipe_stream ); pipe_stream << run.output; pipe_stream.flush(); pipe_stream.pipe().close(); diff.wait(); if (diff.exit_code() == 0) { return test_result{ test_status::successful, run.code, run.output }; } return test_result{ test_status::expectation_failed, run.code, run.output }; } test_result run_test(boost::asio::io_context& context, const std::filesystem::directory_entry& test_entry) { const std::filesystem::path test_filename = test_entry.path().filename(); std::filesystem::path test_binary = in_build_directory(test_filename); test_binary.replace_extension(); return run_for_output(context, 1, boost::process::search_path("qemu-riscv32"), { test_binary.string() }); } static test_results run_in_path(const std::filesystem::path test_directory) { test_results results; boost::asio::io_context context; for (const auto& test_entry : std::filesystem::directory_iterator(test_directory)) { if (test_entry.path().extension() != ".eln" || !test_entry.is_regular_file()) { continue; } test_result result; std::cout << "Running " << test_entry << std::endl; try { result = build_test(context, test_entry); if (result.status == test_status::successful) { result = run_test(context, test_entry); } result = check_expectation(test_entry, result); } catch (const boost::process::process_error& exception) { result.status = test_status::build_failed; result.output = exception.what(); std::cout << result.output << std::endl; } print_result(test_entry, result); results.add_exit_code(result.status); } return results; } void print_result(const std::filesystem::directory_entry& test_entry, const test_result& result) { if (result.status != test_status::successful) { std::cout << test_entry << " failed." << std::endl; } } }; int main() { std::filesystem::create_directory(elna::in_build_directory()); std::cout << "Run all tests and check the results" << std::endl; std::filesystem::path test_directory{ "tests" }; const auto results = elna::run_in_path(test_directory); std::cout << std::endl; std::cout << results.total() << " tests run, " << results.passed() << " passed, " << results.failed() << " failed." << std::endl; return results.exit_code(); }