diff options
author | Andrew Kaster <andrewdkaster@gmail.com> | 2021-04-24 23:53:23 -0600 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-04-25 09:36:49 +0200 |
commit | 35c0a6c54d4e67f0d600044ed8eae0ae5e5adfba (patch) | |
tree | bf10d283d58b3e37c0db7aef4398dff6b1e07cd3 /Userland | |
parent | 89ee38fe5cf6f62821dabc98f4dfbc109d0874fd (diff) | |
download | serenity-35c0a6c54d4e67f0d600044ed8eae0ae5e5adfba.zip |
AK+Userland: Move AK/TestSuite.h into LibTest and rework Tests' CMake
As many macros as possible are moved to Macros.h, while the
macros to create a test case are moved to TestCase.h. TestCase is now
the only user-facing header for creating a test case. TestSuite and its
helpers have moved into a .cpp file. Instead of requiring a TEST_MAIN
macro to be instantiated into the test file, a TestMain.cpp file is
provided instead that will be linked against each test. This has the
side effect that, if we wanted to have test cases split across multiple
files, it's as simple as adding them all to the same executable.
The test main should be portable to kernel mode as well, so if
there's a set of tests that should be run in self-test mode in kernel
space, we can accomodate that.
A new serenity_test CMake function streamlines adding a new test with
arguments for the test source file, subdirectory under /usr/Tests to
install the test application and an optional list of libraries to link
against the test application. To accomodate future test where the
provided TestMain.cpp is not suitable (e.g. test-js), a CUSTOM_MAIN
parameter can be passed to the function to not link against the
boilerplate main function.
Diffstat (limited to 'Userland')
31 files changed, 427 insertions, 70 deletions
diff --git a/Userland/Applications/Spreadsheet/CMakeLists.txt b/Userland/Applications/Spreadsheet/CMakeLists.txt index 15a9f6c020..73945a9a5f 100644 --- a/Userland/Applications/Spreadsheet/CMakeLists.txt +++ b/Userland/Applications/Spreadsheet/CMakeLists.txt @@ -37,3 +37,7 @@ set(GENERATED_SOURCES serenity_app(Spreadsheet ICON app-spreadsheet) target_link_libraries(Spreadsheet LibGUI LibJS LibWeb) + +# FIXME: build these tests +#serenity_test(Writers/Test/TestXSVWriter.cpp Spreadsheet) +#serenity_test(Readers/Test/TestXSV.cpp Spreadsheet) diff --git a/Userland/Applications/Spreadsheet/Readers/Test/TestXSV.cpp b/Userland/Applications/Spreadsheet/Readers/Test/TestXSV.cpp index 196a496c55..0155912fb2 100644 --- a/Userland/Applications/Spreadsheet/Readers/Test/TestXSV.cpp +++ b/Userland/Applications/Spreadsheet/Readers/Test/TestXSV.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include "../CSV.h" #include "../XSV.h" @@ -86,5 +86,3 @@ BENCHMARK_CASE(fairly_big_data) EXPECT(!csv.has_error()); EXPECT_EQ(csv.size(), 100000u); } - -TEST_MAIN(XSV) diff --git a/Userland/Applications/Spreadsheet/Writers/Test/TestXSVWriter.cpp b/Userland/Applications/Spreadsheet/Writers/Test/TestXSVWriter.cpp index 5dad1dcfdd..8f0348bf8d 100644 --- a/Userland/Applications/Spreadsheet/Writers/Test/TestXSVWriter.cpp +++ b/Userland/Applications/Spreadsheet/Writers/Test/TestXSVWriter.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include "../CSV.h" #include "../XSV.h" @@ -72,5 +72,3 @@ We"ll,"Hello,", Friends EXPECT_EQ(StringView { stream.bytes() }, expected_output); } - -TEST_MAIN(XSV) diff --git a/Userland/Libraries/LibC/Tests/CMakeLists.txt b/Userland/Libraries/LibC/Tests/CMakeLists.txt index 768d984227..5dff11b9a3 100644 --- a/Userland/Libraries/LibC/Tests/CMakeLists.txt +++ b/Userland/Libraries/LibC/Tests/CMakeLists.txt @@ -1,8 +1,5 @@ file(GLOB TEST_SOURCES CONFIGURE_DEPENDS "*.cpp") foreach(source ${TEST_SOURCES}) - get_filename_component(name ${source} NAME_WE) - add_executable(${name} ${source}) - target_link_libraries(${name} LibC LibCore) - install(TARGETS ${name} RUNTIME DESTINATION usr/Tests/LibC) + serenity_test(${source} LibC) endforeach() diff --git a/Userland/Libraries/LibC/Tests/TestLibCTime.cpp b/Userland/Libraries/LibC/Tests/TestLibCTime.cpp index 4e4c3dd303..fcf2327639 100644 --- a/Userland/Libraries/LibC/Tests/TestLibCTime.cpp +++ b/Userland/Libraries/LibC/Tests/TestLibCTime.cpp @@ -5,7 +5,7 @@ */ #include <AK/StringView.h> -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <time.h> const auto expected_epoch = "Thu Jan 1 00:00:00 1970\n"sv; @@ -41,5 +41,3 @@ TEST_CASE(ctime_r) EXPECT_EQ(expected_epoch, StringView(result)); } - -TEST_MAIN(LibCTime) diff --git a/Userland/Libraries/LibCompress/Tests/CMakeLists.txt b/Userland/Libraries/LibCompress/Tests/CMakeLists.txt index 8b15789971..bfd8880d1d 100644 --- a/Userland/Libraries/LibCompress/Tests/CMakeLists.txt +++ b/Userland/Libraries/LibCompress/Tests/CMakeLists.txt @@ -1,8 +1,5 @@ file(GLOB TEST_SOURCES CONFIGURE_DEPENDS "*.cpp") foreach(source ${TEST_SOURCES}) - get_filename_component(name ${source} NAME_WE) - add_executable(${name} ${source}) - target_link_libraries(${name} LibCore LibCompress) - install(TARGETS ${name} RUNTIME DESTINATION usr/Tests/LibCompress) + serenity_test(${source} LibCompress LIBS LibCompress) endforeach() diff --git a/Userland/Libraries/LibCompress/Tests/TestDeflate.cpp b/Userland/Libraries/LibCompress/Tests/TestDeflate.cpp index af88c9f180..7b992d8fee 100644 --- a/Userland/Libraries/LibCompress/Tests/TestDeflate.cpp +++ b/Userland/Libraries/LibCompress/Tests/TestDeflate.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <AK/Array.h> #include <AK/MemoryStream.h> @@ -155,5 +155,3 @@ TEST_CASE(deflate_compress_literals) auto compressed = Compress::DeflateCompressor::compress_all(test, Compress::DeflateCompressor::CompressionLevel::GOOD); EXPECT(compressed.has_value()); } - -TEST_MAIN(Deflate) diff --git a/Userland/Libraries/LibCompress/Tests/TestGzip.cpp b/Userland/Libraries/LibCompress/Tests/TestGzip.cpp index 8d04b238a5..189d5b52c9 100644 --- a/Userland/Libraries/LibCompress/Tests/TestGzip.cpp +++ b/Userland/Libraries/LibCompress/Tests/TestGzip.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <AK/Array.h> #include <AK/Random.h> @@ -95,5 +95,3 @@ TEST_CASE(gzip_round_trip) EXPECT(uncompressed.has_value()); EXPECT(uncompressed.value() == original); } - -TEST_MAIN(Gzip) diff --git a/Userland/Libraries/LibCompress/Tests/TestZlib.cpp b/Userland/Libraries/LibCompress/Tests/TestZlib.cpp index 1d6b0a0408..8ab5b81a95 100644 --- a/Userland/Libraries/LibCompress/Tests/TestZlib.cpp +++ b/Userland/Libraries/LibCompress/Tests/TestZlib.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <AK/Array.h> #include <LibCompress/Zlib.h> @@ -23,5 +23,3 @@ TEST_CASE(zlib_decompress_simple) const auto decompressed = Compress::Zlib::decompress_all(compressed); EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); } - -TEST_MAIN(Zlib) diff --git a/Userland/Libraries/LibRegex/Tests/Benchmark.cpp b/Userland/Libraries/LibRegex/Tests/Benchmark.cpp index 8e13cce111..b51316fdb5 100644 --- a/Userland/Libraries/LibRegex/Tests/Benchmark.cpp +++ b/Userland/Libraries/LibRegex/Tests/Benchmark.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> // import first, to prevent warning of VERIFY* redefinition +#include <LibTest/TestCase.h> // import first, to prevent warning of VERIFY* redefinition #include <LibRegex/Regex.h> #include <stdio.h> @@ -967,5 +967,3 @@ BENCHMARK_CASE(simple_notbol_noteol_benchmark_reference_stdcpp) # endif #endif - -TEST_MAIN(Regex) diff --git a/Userland/Libraries/LibRegex/Tests/CMakeLists.txt b/Userland/Libraries/LibRegex/Tests/CMakeLists.txt index 66478f6ad6..a9ccade427 100644 --- a/Userland/Libraries/LibRegex/Tests/CMakeLists.txt +++ b/Userland/Libraries/LibRegex/Tests/CMakeLists.txt @@ -2,8 +2,5 @@ file(GLOB TEST_SOURCES CONFIGURE_DEPENDS "*.cpp") file(GLOB REGEX_SOURCES CONFIGURE_DEPENDS "../*.cpp" "../C/*.cpp") foreach(source ${TEST_SOURCES}) - get_filename_component(name ${source} NAME_WE) - add_executable(${name} ${source} ${REGEX_SOURCES}) - target_link_libraries(${name} LibCore) - install(TARGETS ${name} RUNTIME DESTINATION usr/Tests/LibRegex) + serenity_test(${source} LibRegex LIBS LibRegex) endforeach() diff --git a/Userland/Libraries/LibRegex/Tests/Regex.cpp b/Userland/Libraries/LibRegex/Tests/Regex.cpp index 5aeb4ccbf8..054c54485b 100644 --- a/Userland/Libraries/LibRegex/Tests/Regex.cpp +++ b/Userland/Libraries/LibRegex/Tests/Regex.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> // import first, to prevent warning of VERIFY* redefinition +#include <LibTest/TestCase.h> // import first, to prevent warning of VERIFY* redefinition #include <AK/StringBuilder.h> #include <LibRegex/Regex.h> @@ -595,5 +595,3 @@ TEST_CASE(replace) EXPECT_EQ(re.replace(test.subject, test.replacement), test.expected); } } - -TEST_MAIN(Regex) diff --git a/Userland/Libraries/LibRegex/Tests/RegexLibC.cpp b/Userland/Libraries/LibRegex/Tests/RegexLibC.cpp index 7e001f4b65..bea5e5984c 100644 --- a/Userland/Libraries/LibRegex/Tests/RegexLibC.cpp +++ b/Userland/Libraries/LibRegex/Tests/RegexLibC.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <AK/StringBuilder.h> #include <LibC/regex.h> @@ -1116,5 +1116,3 @@ TEST_CASE(simple_notbol_noteol) regfree(®ex); } - -TEST_MAIN(Regex) diff --git a/Userland/Libraries/LibSQL/Tests/CMakeLists.txt b/Userland/Libraries/LibSQL/Tests/CMakeLists.txt index b028333c61..f002a1e864 100644 --- a/Userland/Libraries/LibSQL/Tests/CMakeLists.txt +++ b/Userland/Libraries/LibSQL/Tests/CMakeLists.txt @@ -1,8 +1,5 @@ file(GLOB TEST_SOURCES CONFIGURE_DEPENDS "*.cpp") foreach(source ${TEST_SOURCES}) - get_filename_component(name ${source} NAME_WE) - add_executable(${name} ${source}) - target_link_libraries(${name} LibSQL) - install(TARGETS ${name} RUNTIME DESTINATION usr/Tests/LibSQL) + serenity_test(${source} LibSQL LIBS LibSQL) endforeach() diff --git a/Userland/Libraries/LibSQL/Tests/TestSqlExpressionParser.cpp b/Userland/Libraries/LibSQL/Tests/TestSqlExpressionParser.cpp index a3d2e6d32f..c2a6b3bfd7 100644 --- a/Userland/Libraries/LibSQL/Tests/TestSqlExpressionParser.cpp +++ b/Userland/Libraries/LibSQL/Tests/TestSqlExpressionParser.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <AK/HashMap.h> #include <AK/Result.h> @@ -602,5 +602,3 @@ TEST_CASE(in_selection_expression) validate("15 IN (SELECT * FROM table)", false); validate("15 NOT IN (SELECT * FROM table)", true); } - -TEST_MAIN(SqlExpressionParser) diff --git a/Userland/Libraries/LibSQL/Tests/TestSqlStatementParser.cpp b/Userland/Libraries/LibSQL/Tests/TestSqlStatementParser.cpp index 7438311bdc..0126a85e4e 100644 --- a/Userland/Libraries/LibSQL/Tests/TestSqlStatementParser.cpp +++ b/Userland/Libraries/LibSQL/Tests/TestSqlStatementParser.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <AK/Optional.h> #include <AK/Result.h> @@ -735,5 +735,3 @@ TEST_CASE(common_table_expression) validate("WITH table (column1, column2) AS (SELECT * FROM table) DELETE FROM table;", { false, { { "table", { "column1", "column2" } } } }); validate("WITH RECURSIVE table AS (SELECT * FROM table) DELETE FROM table;", { true, { { "table", {} } } }); } - -TEST_MAIN(SqlStatementParser) diff --git a/Userland/Libraries/LibTest/CMakeLists.txt b/Userland/Libraries/LibTest/CMakeLists.txt index a924ea3e3f..7816fdac71 100644 --- a/Userland/Libraries/LibTest/CMakeLists.txt +++ b/Userland/Libraries/LibTest/CMakeLists.txt @@ -1 +1,8 @@ serenity_install_sources("Userland/Libraries/LibTest") + +set(SOURCES + TestSuite.cpp +) + +serenity_lib(LibTest test) +target_link_libraries(LibTest LibC) diff --git a/Userland/Libraries/LibTest/Macros.h b/Userland/Libraries/LibTest/Macros.h new file mode 100644 index 0000000000..b4704508e2 --- /dev/null +++ b/Userland/Libraries/LibTest/Macros.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Andrew Kaster <akaster@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Assertions.h> +#include <AK/CheckedFormatString.h> + +namespace AK { +template<typename... Parameters> +void warnln(CheckedFormatString<Parameters...>&& fmtstr, const Parameters&...); +} + +namespace Test { +// Declare a helper so that we can call it from VERIFY in included headers +void current_test_case_did_fail(); +} + +#undef VERIFY +#define VERIFY(x) \ + do { \ + if (!(x)) { \ + ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: VERIFY({}) failed", __FILE__, __LINE__, #x); \ + ::Test::current_test_case_did_fail(); \ + } \ + } while (false) + +#undef VERIFY_NOT_REACHED +#define VERIFY_NOT_REACHED() \ + do { \ + ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: VERIFY_NOT_REACHED() called", __FILE__, __LINE__); \ + ::abort(); \ + } while (false) + +#undef TODO +#define TODO() \ + do { \ + ::AK::warnln(stderr, "\033[31;1mFAIL\033[0m: {}:{}: TODO() called", __FILE__, __LINE__); \ + ::abort(); \ + } while (false) + +#define EXPECT_EQ(a, b) \ + do { \ + auto lhs = (a); \ + auto rhs = (b); \ + if (lhs != rhs) { \ + ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_EQ({}, {}) failed with lhs={} and rhs={}", __FILE__, __LINE__, #a, #b, FormatIfSupported { lhs }, FormatIfSupported { rhs }); \ + ::Test::current_test_case_did_fail(); \ + } \ + } while (false) + +// If you're stuck and `EXPECT_EQ` seems to refuse to print anything useful, +// try this: It'll spit out a nice compiler error telling you why it doesn't print. +#define EXPECT_EQ_FORCE(a, b) \ + do { \ + auto lhs = (a); \ + auto rhs = (b); \ + if (lhs != rhs) { \ + ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_EQ({}, {}) failed with lhs={} and rhs={}", __FILE__, __LINE__, #a, #b, lhs, rhs); \ + ::Test::current_test_case_did_fail(); \ + } \ + } while (false) + +#define EXPECT(x) \ + do { \ + if (!(x)) { \ + ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT({}) failed", __FILE__, __LINE__, #x); \ + ::Test::current_test_case_did_fail(); \ + } \ + } while (false) + +#define EXPECT_APPROXIMATE(a, b) \ + do { \ + auto expect_close_lhs = a; \ + auto expect_close_rhs = b; \ + auto expect_close_diff = static_cast<double>(expect_close_lhs) - static_cast<double>(expect_close_rhs); \ + if (fabs(expect_close_diff) > 0.0000005) { \ + ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_APPROXIMATE({}, {})" \ + " failed with lhs={}, rhs={}, (lhs-rhs)={}", \ + __FILE__, __LINE__, #a, #b, expect_close_lhs, expect_close_rhs, expect_close_diff); \ + ::Test::current_test_case_did_fail(); \ + } \ + } while (false) diff --git a/Userland/Libraries/LibTest/TestCase.h b/Userland/Libraries/LibTest/TestCase.h new file mode 100644 index 0000000000..d0fb713567 --- /dev/null +++ b/Userland/Libraries/LibTest/TestCase.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Andrew Kaster <akaster@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibTest/Macros.h> // intentionally first -- we redefine VERIFY and friends in here + +#include <AK/Function.h> +#include <AK/NonnullRefPtr.h> +#include <AK/RefCounted.h> +#include <AK/String.h> + +namespace Test { + +using TestFunction = Function<void()>; + +class TestCase : public RefCounted<TestCase> { +public: + TestCase(const String& name, TestFunction&& fn, bool is_benchmark) + : m_name(name) + , m_function(move(fn)) + , m_is_benchmark(is_benchmark) + { + } + + bool is_benchmark() const { return m_is_benchmark; } + const String& name() const { return m_name; } + const TestFunction& func() const { return m_function; } + +private: + String m_name; + TestFunction m_function; + bool m_is_benchmark; +}; + +// Helper to hide implementation of TestSuite from users +void add_test_case_to_suite(const NonnullRefPtr<TestCase>& test_case); + +} + +#define __TESTCASE_FUNC(x) __test_##x +#define __TESTCASE_TYPE(x) __TestCase_##x + +#define TEST_CASE(x) \ + static void __TESTCASE_FUNC(x)(); \ + struct __TESTCASE_TYPE(x) { \ + __TESTCASE_TYPE(x) \ + () { add_test_case_to_suite(adopt_ref(*new ::Test::TestCase(#x, __TESTCASE_FUNC(x), false))); } \ + }; \ + static struct __TESTCASE_TYPE(x) __TESTCASE_TYPE(x); \ + static void __TESTCASE_FUNC(x)() + +#define __BENCHMARK_FUNC(x) __benchmark_##x +#define __BENCHMARK_TYPE(x) __BenchmarkCase_##x + +#define BENCHMARK_CASE(x) \ + static void __BENCHMARK_FUNC(x)(); \ + struct __BENCHMARK_TYPE(x) { \ + __BENCHMARK_TYPE(x) \ + () { add_test_case_to_suite(adopt_ref(*new ::Test::TestCase(#x, __BENCHMARK_FUNC(x), true))); } \ + }; \ + static struct __BENCHMARK_TYPE(x) __BENCHMARK_TYPE(x); \ + static void __BENCHMARK_FUNC(x)() diff --git a/Userland/Libraries/LibTest/TestMain.cpp b/Userland/Libraries/LibTest/TestMain.cpp new file mode 100644 index 0000000000..6a232e1618 --- /dev/null +++ b/Userland/Libraries/LibTest/TestMain.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Andrew Kaster <akaster@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibTest/TestCase.h> + +#include <AK/Format.h> +#include <LibTest/TestSuite.h> + +#ifdef KERNEL +# define TEST_MAIN test_main +#else +# define TEST_MAIN main +#endif + +int TEST_MAIN(int argc, char** argv) +{ + if (argc < 1 || !argv[0] || '\0' == *argv[0]) { + warnln("Test main does not have a valid test name!"); + return 1; + } + int ret = ::Test::TestSuite::the().main(argv[0], argc, argv); + ::Test::TestSuite::release(); + return ret; +} diff --git a/Userland/Libraries/LibTest/TestSuite.cpp b/Userland/Libraries/LibTest/TestSuite.cpp new file mode 100644 index 0000000000..ac2b93b7de --- /dev/null +++ b/Userland/Libraries/LibTest/TestSuite.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Andrew Kaster <akaster@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibTest/Macros.h> // intentionally first -- we redefine VERIFY and friends in here + +#include <LibCore/ArgsParser.h> +#include <LibTest/TestSuite.h> +#include <stdlib.h> +#include <sys/time.h> + +namespace Test { + +TestSuite* TestSuite::s_global = nullptr; + +class TestElapsedTimer { +public: + TestElapsedTimer() { restart(); } + + void restart() { gettimeofday(&m_started, nullptr); } + + u64 elapsed_milliseconds() + { + struct timeval now = {}; + gettimeofday(&now, nullptr); + + struct timeval delta = {}; + timersub(&now, &m_started, &delta); + + return delta.tv_sec * 1000 + delta.tv_usec / 1000; + } + +private: + struct timeval m_started = {}; +}; + +// Declared in Macros.h +void current_test_case_did_fail() +{ + TestSuite::the().current_test_case_did_fail(); +} + +// Declared in TestCase.h +void add_test_case_to_suite(const NonnullRefPtr<TestCase>& test_case) +{ + TestSuite::the().add_case(test_case); +} + +int TestSuite::main(const String& suite_name, int argc, char** argv) +{ + m_suite_name = suite_name; + + Core::ArgsParser args_parser; + + bool do_tests_only = getenv("TESTS_ONLY") != nullptr; + bool do_benchmarks_only = false; + bool do_list_cases = false; + const char* search_string = "*"; + + args_parser.add_option(do_tests_only, "Only run tests.", "tests", 0); + args_parser.add_option(do_benchmarks_only, "Only run benchmarks.", "bench", 0); + args_parser.add_option(do_list_cases, "List available test cases.", "list", 0); + args_parser.add_positional_argument(search_string, "Only run matching cases.", "pattern", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + const auto& matching_tests = find_cases(search_string, !do_benchmarks_only, !do_tests_only); + + if (do_list_cases) { + outln("Available cases for {}:", suite_name); + for (const auto& test : matching_tests) { + outln(" {}", test.name()); + } + return 0; + } + + outln("Running {} cases out of {}.", matching_tests.size(), m_cases.size()); + + return run(matching_tests); +} + +NonnullRefPtrVector<TestCase> TestSuite::find_cases(const String& search, bool find_tests, bool find_benchmarks) +{ + NonnullRefPtrVector<TestCase> matches; + for (const auto& t : m_cases) { + if (!search.is_empty() && !t.name().matches(search, CaseSensitivity::CaseInsensitive)) { + continue; + } + + if (!find_tests && !t.is_benchmark()) { + continue; + } + if (!find_benchmarks && t.is_benchmark()) { + continue; + } + + matches.append(t); + } + return matches; +} + +int TestSuite::run(const NonnullRefPtrVector<TestCase>& tests) +{ + size_t test_count = 0; + size_t test_failed_count = 0; + size_t benchmark_count = 0; + TestElapsedTimer global_timer; + + for (const auto& t : tests) { + const auto test_type = t.is_benchmark() ? "benchmark" : "test"; + + warnln("Running {} '{}'.", test_type, t.name()); + m_current_test_case_passed = true; + + TestElapsedTimer timer; + t.func()(); + const auto time = timer.elapsed_milliseconds(); + + dbgln("{} {} '{}' in {}ms", m_current_test_case_passed ? "Completed" : "Failed", test_type, t.name(), time); + + if (t.is_benchmark()) { + m_benchtime += time; + benchmark_count++; + } else { + m_testtime += time; + test_count++; + } + + if (!m_current_test_case_passed) { + test_failed_count++; + } + } + + dbgln("Finished {} tests and {} benchmarks in {}ms ({}ms tests, {}ms benchmarks, {}ms other).", + test_count, + benchmark_count, + global_timer.elapsed_milliseconds(), + m_testtime, + m_benchtime, + global_timer.elapsed_milliseconds() - (m_testtime + m_benchtime)); + dbgln("Out of {} tests, {} passed and {} failed.", test_count, test_count - test_failed_count, test_failed_count); + + return (int)test_failed_count; +} + +} diff --git a/Userland/Libraries/LibTest/TestSuite.h b/Userland/Libraries/LibTest/TestSuite.h new file mode 100644 index 0000000000..694b6d1a69 --- /dev/null +++ b/Userland/Libraries/LibTest/TestSuite.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Andrew Kaster <akaster@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibTest/Macros.h> // intentionally first -- we redefine VERIFY and friends in here + +#include <AK/Format.h> +#include <AK/Function.h> +#include <AK/NonnullRefPtrVector.h> +#include <AK/String.h> +#include <LibTest/TestCase.h> + +namespace Test { + +class TestSuite { +public: + static TestSuite& the() + { + if (s_global == nullptr) + s_global = new TestSuite(); + return *s_global; + } + + static void release() + { + if (s_global) + delete s_global; + s_global = nullptr; + } + + int run(const NonnullRefPtrVector<TestCase>&); + int main(const String& suite_name, int argc, char** argv); + NonnullRefPtrVector<TestCase> find_cases(const String& search, bool find_tests, bool find_benchmarks); + void add_case(const NonnullRefPtr<TestCase>& test_case) + { + m_cases.append(test_case); + } + + void current_test_case_did_fail() { m_current_test_case_passed = false; } + +private: + static TestSuite* s_global; + NonnullRefPtrVector<TestCase> m_cases; + u64 m_testtime = 0; + u64 m_benchtime = 0; + String m_suite_name; + bool m_current_test_case_passed = true; +}; + +} diff --git a/Userland/Tests/Kernel/CMakeLists.txt b/Userland/Tests/Kernel/CMakeLists.txt index cd9438dc4a..408d82163c 100644 --- a/Userland/Tests/Kernel/CMakeLists.txt +++ b/Userland/Tests/Kernel/CMakeLists.txt @@ -1,5 +1,6 @@ file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp") +# FIXME: These tests do not use LibTest foreach(CMD_SRC ${CMD_SOURCES}) get_filename_component(CMD_NAME ${CMD_SRC} NAME_WE) add_executable(${CMD_NAME} ${CMD_SRC}) diff --git a/Userland/Tests/LibC/CMakeLists.txt b/Userland/Tests/LibC/CMakeLists.txt index 799dfa9ff4..a42c42b47e 100644 --- a/Userland/Tests/LibC/CMakeLists.txt +++ b/Userland/Tests/LibC/CMakeLists.txt @@ -1,5 +1,8 @@ file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp") +list(REMOVE_ITEM CMD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/snprintf-correctness.cpp) +list(REMOVE_ITEM CMD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/strlcpy-correctness.cpp) +# FIXME: These tests do not use LibTest foreach(CMD_SRC ${CMD_SOURCES}) get_filename_component(CMD_NAME ${CMD_SRC} NAME_WE) add_executable(${CMD_NAME} ${CMD_SRC}) @@ -7,4 +10,5 @@ foreach(CMD_SRC ${CMD_SOURCES}) install(TARGETS ${CMD_NAME} RUNTIME DESTINATION usr/Tests/LibC) endforeach() -#target_link_libraries(foobar LibPthread) +serenity_test(snprintf-correctness.cpp LibC) +serenity_test(strlcpy-correctness.cpp LibC) diff --git a/Userland/Tests/LibC/snprintf-correctness.cpp b/Userland/Tests/LibC/snprintf-correctness.cpp index 5b8200db4f..0df2a5cc30 100644 --- a/Userland/Tests/LibC/snprintf-correctness.cpp +++ b/Userland/Tests/LibC/snprintf-correctness.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <AK/ByteBuffer.h> #include <AK/Random.h> @@ -141,5 +141,3 @@ TEST_CASE(special_cases) EXPECT(test_single({ LITERAL("x"), "whf", POISON, 3, LITERAL("\0") })); EXPECT(test_single({ LITERAL("xx"), "whf", POISON, 3, LITERAL("w\0") })); } - -TEST_MAIN(Sprintf) diff --git a/Userland/Tests/LibC/strlcpy-correctness.cpp b/Userland/Tests/LibC/strlcpy-correctness.cpp index c7e4cb0401..e23765cd29 100644 --- a/Userland/Tests/LibC/strlcpy-correctness.cpp +++ b/Userland/Tests/LibC/strlcpy-correctness.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <AK/ByteBuffer.h> #include <AK/Random.h> @@ -162,5 +162,3 @@ TEST_CASE(to_nullptr) EXPECT(test_single({ LITERAL("Hello World!\0\0\0"), LITERAL("Hello Friend!"), LITERAL("Hello Friend!\0\0") })); EXPECT(test_single({ LITERAL("aaaaaaaaaa"), LITERAL("whf"), LITERAL("whf\0aaaaaa") })); } - -TEST_MAIN(Sprintf) diff --git a/Userland/Tests/LibGfx/CMakeLists.txt b/Userland/Tests/LibGfx/CMakeLists.txt index 1c2bc2f17e..f0f5cecab9 100644 --- a/Userland/Tests/LibGfx/CMakeLists.txt +++ b/Userland/Tests/LibGfx/CMakeLists.txt @@ -1,12 +1,12 @@ file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp") +list(REMOVE_ITEM CMD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/painter.cpp) +# FIXME These tests do not use LibTest foreach(CMD_SRC ${CMD_SOURCES}) get_filename_component(CMD_NAME ${CMD_SRC} NAME_WE) add_executable(${CMD_NAME} ${CMD_SRC}) - target_link_libraries(${CMD_NAME} LibCore) + target_link_libraries(${CMD_NAME} LibCore LibGUI) install(TARGETS ${CMD_NAME} RUNTIME DESTINATION usr/Tests/LibGfx) endforeach() -target_link_libraries(font LibGUI LibCore) -target_link_libraries(image-decoder LibGUI LibCore) -target_link_libraries(painter LibGUI LibCore) +serenity_test(painter.cpp LibGfx LIBS LibGUI) diff --git a/Userland/Tests/LibGfx/painter.cpp b/Userland/Tests/LibGfx/painter.cpp index 50210f51a7..ca4fef41cf 100644 --- a/Userland/Tests/LibGfx/painter.cpp +++ b/Userland/Tests/LibGfx/painter.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <LibGfx/Bitmap.h> #include <LibGfx/Painter.h> @@ -51,5 +51,3 @@ BENCHMARK_CASE(fill_with_gradient) painter.fill_rect_with_gradient(bitmap->rect(), Color::Blue, Color::Red); } } - -TEST_MAIN(Painter) diff --git a/Userland/Tests/LibM/CMakeLists.txt b/Userland/Tests/LibM/CMakeLists.txt index de7c217201..bf1c0cc927 100644 --- a/Userland/Tests/LibM/CMakeLists.txt +++ b/Userland/Tests/LibM/CMakeLists.txt @@ -1,8 +1,5 @@ file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp") foreach(CMD_SRC ${CMD_SOURCES}) - get_filename_component(CMD_NAME ${CMD_SRC} NAME_WE) - add_executable(${CMD_NAME} ${CMD_SRC}) - target_link_libraries(${CMD_NAME} LibCore) - install(TARGETS ${CMD_NAME} RUNTIME DESTINATION usr/Tests/LibM) + serenity_test(${CMD_SRC} LibM) endforeach() diff --git a/Userland/Tests/LibM/test-math.cpp b/Userland/Tests/LibM/test-math.cpp index cdc5f35173..e48a5955e2 100644 --- a/Userland/Tests/LibM/test-math.cpp +++ b/Userland/Tests/LibM/test-math.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/TestSuite.h> +#include <LibTest/TestCase.h> #include <float.h> #include <math.h> @@ -250,5 +250,3 @@ TEST_CASE(fmax_and_fmin) EXPECT(fmin(0, NAN) == 0); EXPECT(isnan(fmin(NAN, NAN))); } - -TEST_MAIN(Math) diff --git a/Userland/Tests/UserspaceEmulator/CMakeLists.txt b/Userland/Tests/UserspaceEmulator/CMakeLists.txt index f39e141ee6..3fbbbc4120 100644 --- a/Userland/Tests/UserspaceEmulator/CMakeLists.txt +++ b/Userland/Tests/UserspaceEmulator/CMakeLists.txt @@ -1,5 +1,6 @@ file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp") +# FIXME: These tests do not use LibTest foreach(CMD_SRC ${CMD_SOURCES}) get_filename_component(CMD_NAME ${CMD_SRC} NAME_WE) add_executable(${CMD_NAME} ${CMD_SRC}) |