Browse Source

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.
Timothy Flynn 11 tháng trước cách đây
mục cha
commit
f7fcde7f60
1 tập tin đã thay đổi với 127 bổ sung85 xóa
  1. 127 85
      Userland/Libraries/LibTest/Macros.h

+ 127 - 85
Userland/Libraries/LibTest/Macros.h

@@ -10,16 +10,13 @@
 #include <AK/Assertions.h>
 #include <AK/CheckedFormatString.h>
 #include <AK/Math.h>
+#include <AK/SourceLocation.h>
 #include <LibTest/CrashTest.h>
 #include <LibTest/Randomized/RandomnessSource.h>
 #include <LibTest/TestResult.h>
 
-namespace AK {
-template<typename... Parameters>
-void warnln(CheckedFormatString<Parameters...>&& 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<typename T>
+void expect(T const& expression, StringView expression_string, SourceLocation location = SourceLocation::current())
+{
+    if (!static_cast<bool>(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);
+    }
+}
+
+template<typename LHS, typename RHS>
+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);
+    }
 }
 
-#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<typename LHS, typename RHS>
+void expect_truthy_equality(LHS const& lhs, RHS const& rhs, StringView lhs_string, StringView rhs_string, SourceLocation location = SourceLocation::current())
+{
+    if (static_cast<bool>(lhs) != static_cast<bool>(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<bool>(lhs),
+                FormatIfSupported { rhs }, static_cast<bool>(rhs));
+        }
+
+        set_current_test_result(TestResult::Failed);
+    }
+}
+
+template<typename LHS, typename RHS>
+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<typename LHS, typename RHS>
+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<FloatingPoint LHS, FloatingPoint RHS>
+void expect_approximate(LHS lhs, RHS rhs, StringView lhs_string, StringView rhs_string, double tolerance, SourceLocation location = SourceLocation::current())
+{
+    auto diff = static_cast<double>(lhs) - static_cast<double>(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<typename T>
+bool assume(T const& expression, StringView expression_string, SourceLocation location = SourceLocation::current())
+{
+    if (!static_cast<bool>(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<bool>(lhs);                                                                                 \
-        bool rtruth = static_cast<bool>(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)
 
-// 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_TRUTH(a, b)                                 \
+    do {                                                      \
+        ::Test::expect_truthy_equality(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);                                      \
-        }                                                                                                     \
+// 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 {                                                                   \
+        ::Test::expect_equality_with_forced_logging(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);                                 \
-        }                                                                                                \
+#define EXPECT_NE(a, b)                                  \
+    do {                                                 \
+        ::Test::expect_inequality(a, b, #a##sv, #b##sv); \
     } 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<double>(expect_close_lhs) - static_cast<double>(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)                                                                      \