From f7fcde7f606ea75304fdb94c48e67aee88a08774 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 5 Aug 2024 02:29:55 -0400 Subject: [PATCH] LibTest: Define test expectation macros without copying the input values Currently, the following test case will actually copy both `a` and `b` when the test macro is expanded: ByteBuffer a = { some large buffer }; ByteBuffer b = { some other buffer }; EXPECT_EQ(a, b); This patch redefines the expectation macros to avoid copying. --- Userland/Libraries/LibTest/Macros.h | 212 +++++++++++++++++----------- 1 file changed, 127 insertions(+), 85 deletions(-) diff --git a/Userland/Libraries/LibTest/Macros.h b/Userland/Libraries/LibTest/Macros.h index 23d136f9f46..e62ef832ec3 100644 --- a/Userland/Libraries/LibTest/Macros.h +++ b/Userland/Libraries/LibTest/Macros.h @@ -10,16 +10,13 @@ #include #include #include +#include #include #include #include -namespace AK { -template -void warnln(CheckedFormatString&& fmtstr, Parameters const&...); -} - namespace Test { + // Declare helpers so that we can call them from VERIFY in included headers // the setter for TestResult is already declared in TestResult.h TestResult current_test_result(); @@ -32,102 +29,147 @@ void enable_reporting(); void disable_reporting(); u64 randomized_runs(); + +template +void expect(T const& expression, StringView expression_string, SourceLocation location = SourceLocation::current()) +{ + if (!static_cast(expression)) { + if (is_reporting_enabled()) + warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT({}) failed", location.filename(), location.line_number(), expression_string); + + set_current_test_result(TestResult::Failed); + } } -#define EXPECT_EQ(a, b) \ - do { \ - auto lhs = (a); \ - auto rhs = (b); \ - if (lhs != rhs) { \ - if (::Test::is_reporting_enabled()) \ - ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_EQ({}, {}) failed with lhs={} and rhs={}", \ - __FILE__, __LINE__, #a, #b, FormatIfSupported { lhs }, FormatIfSupported { rhs }); \ - ::Test::set_current_test_result(::Test::TestResult::Failed); \ - } \ +template +void expect_equality(LHS const& lhs, RHS const& rhs, StringView lhs_string, StringView rhs_string, SourceLocation location = SourceLocation::current()) +{ + if (lhs != rhs) { + if (is_reporting_enabled()) { + warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_EQ({}, {}) failed with lhs={} and rhs={}", + location.filename(), location.line_number(), lhs_string, rhs_string, + FormatIfSupported { lhs }, FormatIfSupported { rhs }); + } + + set_current_test_result(TestResult::Failed); + } +} + +template +void expect_truthy_equality(LHS const& lhs, RHS const& rhs, StringView lhs_string, StringView rhs_string, SourceLocation location = SourceLocation::current()) +{ + if (static_cast(lhs) != static_cast(rhs)) { + if (is_reporting_enabled()) { + warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_EQ_TRUTH({}, {}) failed with lhs={} ({}) and rhs={} ({})", + location.filename(), location.line_number(), lhs_string, rhs_string, + FormatIfSupported { lhs }, static_cast(lhs), + FormatIfSupported { rhs }, static_cast(rhs)); + } + + set_current_test_result(TestResult::Failed); + } +} + +template +void expect_equality_with_forced_logging(LHS const& lhs, RHS const& rhs, StringView lhs_string, StringView rhs_string, SourceLocation location = SourceLocation::current()) +{ + if (lhs != rhs) { + if (is_reporting_enabled()) { + warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_EQ({}, {}) failed with lhs={} and rhs={}", + location.filename(), location.line_number(), lhs_string, rhs_string, + lhs, rhs); + } + + set_current_test_result(TestResult::Failed); + } +} + +template +void expect_inequality(LHS const& lhs, RHS const& rhs, StringView lhs_string, StringView rhs_string, SourceLocation location = SourceLocation::current()) +{ + if (lhs == rhs) { + if (is_reporting_enabled()) { + warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_NE({}, {}) failed with lhs={} and rhs={}", + location.filename(), location.line_number(), lhs_string, rhs_string, + FormatIfSupported { lhs }, FormatIfSupported { rhs }); + } + + set_current_test_result(TestResult::Failed); + } +} + +template +void expect_approximate(LHS lhs, RHS rhs, StringView lhs_string, StringView rhs_string, double tolerance, SourceLocation location = SourceLocation::current()) +{ + auto diff = static_cast(lhs) - static_cast(rhs); + + if (AK::fabs(diff) > tolerance) { + if (is_reporting_enabled()) { + warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_APPROXIMATE({}, {}) failed with lhs={} and rhs={}, (lhs-rhs)={}", + location.filename(), location.line_number(), lhs_string, rhs_string, + lhs, rhs, diff); + } + + set_current_test_result(TestResult::Failed); + } +} + +template +bool assume(T const& expression, StringView expression_string, SourceLocation location = SourceLocation::current()) +{ + if (!static_cast(expression)) { + if (is_reporting_enabled()) { + warnln("\033[31;1mREJECTED\033[0m: {}:{}: Couldn't generate random value satisfying ASSUME({})", + location.filename(), location.line_number(), expression_string); + } + + set_current_test_result(TestResult::Rejected); + return false; + } + + return true; +} + +} + +#define EXPECT(x) \ + do { \ + ::Test::expect(x, #x##sv); \ } while (false) -#define EXPECT_EQ_TRUTH(a, b) \ - do { \ - auto lhs = (a); \ - auto rhs = (b); \ - bool ltruth = static_cast(lhs); \ - bool rtruth = static_cast(rhs); \ - if (ltruth != rtruth) { \ - if (::Test::is_reporting_enabled()) \ - ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_EQ_TRUTH({}, {}) failed with lhs={} ({}) and rhs={} ({})", \ - __FILE__, __LINE__, #a, #b, FormatIfSupported { lhs }, ltruth, FormatIfSupported { rhs }, rtruth); \ - ::Test::set_current_test_result(::Test::TestResult::Failed); \ - } \ +#define EXPECT_EQ(a, b) \ + do { \ + ::Test::expect_equality(a, b, #a##sv, #b##sv); \ + } while (false) + +#define EXPECT_EQ_TRUTH(a, b) \ + do { \ + ::Test::expect_truthy_equality(a, b, #a##sv, #b##sv); \ } 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) { \ - if (::Test::is_reporting_enabled()) \ - ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_EQ({}, {}) failed with lhs={} and rhs={}", \ - __FILE__, __LINE__, #a, #b, lhs, rhs); \ - ::Test::set_current_test_result(::Test::TestResult::Failed); \ - } \ +#define EXPECT_EQ_FORCE(a, b) \ + do { \ + ::Test::expect_equality_with_forced_logging(a, b, #a##sv, #b##sv); \ } while (false) -#define EXPECT_NE(a, b) \ - do { \ - auto lhs = (a); \ - auto rhs = (b); \ - if (lhs == rhs) { \ - if (::Test::is_reporting_enabled()) \ - ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT_NE({}, {}) failed with lhs={} and rhs={}", \ - __FILE__, __LINE__, #a, #b, FormatIfSupported { lhs }, FormatIfSupported { rhs }); \ - ::Test::set_current_test_result(::Test::TestResult::Failed); \ - } \ +#define EXPECT_NE(a, b) \ + do { \ + ::Test::expect_inequality(a, b, #a##sv, #b##sv); \ } while (false) -#define EXPECT(x) \ - do { \ - if (!(x)) { \ - if (::Test::is_reporting_enabled()) \ - ::AK::warnln("\033[31;1mFAIL\033[0m: {}:{}: EXPECT({}) failed", __FILE__, __LINE__, #x); \ - ::Test::set_current_test_result(::Test::TestResult::Failed); \ - } \ - } while (false) - -#define EXPECT_APPROXIMATE_WITH_ERROR(a, b, err) \ - do { \ - auto expect_close_lhs = a; \ - auto expect_close_rhs = b; \ - auto expect_close_diff = static_cast(expect_close_lhs) - static_cast(expect_close_rhs); \ - if (AK::fabs(expect_close_diff) > (err)) { \ - if (::Test::is_reporting_enabled()) \ - ::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::set_current_test_result(::Test::TestResult::Failed); \ - } \ +#define EXPECT_APPROXIMATE_WITH_ERROR(a, b, err) \ + do { \ + ::Test::expect_approximate(a, b, #a##sv, #b##sv, err); \ } while (false) #define EXPECT_APPROXIMATE(a, b) EXPECT_APPROXIMATE_WITH_ERROR(a, b, 0.0000005) -#define REJECT(message) \ - do { \ - if (::Test::is_reporting_enabled()) \ - ::AK::warnln("\033[31;1mREJECTED\033[0m: {}:{}: {}", \ - __FILE__, __LINE__, #message); \ - ::Test::set_current_test_result(::Test::TestResult::Rejected); \ - } while (false) - -#define ASSUME(x) \ - do { \ - if (!(x)) { \ - if (::Test::is_reporting_enabled()) \ - ::AK::warnln("\033[31;1mREJECTED\033[0m: {}:{}: Couldn't generate random value satisfying ASSUME({})", \ - __FILE__, __LINE__, #x); \ - ::Test::set_current_test_result(::Test::TestResult::Rejected); \ - return; \ - } \ +#define ASSUME(x) \ + do { \ + if (!::Test::assume(x, #x##sv)) \ + return; \ } while (false) #define FAIL(message) \