aboutsummaryrefslogtreecommitdiffhomepage
path: root/tdutils
diff options
context:
space:
mode:
authorlevlam <levlam@telegram.org>2025-03-20 16:18:25 +0300
committerlevlam <levlam@telegram.org>2025-03-20 16:18:25 +0300
commit82689a2c6a53c7ee368da03b313944beb18af0c2 (patch)
treeada2d4005edff1428b297bd35d6339effaf48c23 /tdutils
parentb4aa471cd245169967613b8331a8d9eb7e1895e5 (diff)
Add simple_tests.h.
Diffstat (limited to 'tdutils')
-rw-r--r--tdutils/CMakeLists.txt1
-rw-r--r--tdutils/td/utils/simple_tests.h164
2 files changed, 165 insertions, 0 deletions
diff --git a/tdutils/CMakeLists.txt b/tdutils/CMakeLists.txt
index 026ee2a56..5576fed87 100644
--- a/tdutils/CMakeLists.txt
+++ b/tdutils/CMakeLists.txt
@@ -265,6 +265,7 @@ set(TDUTILS_SOURCE
td/utils/SetNode.h
td/utils/SharedObjectPool.h
td/utils/SharedSlice.h
+ td/utils/simple_tests.h
td/utils/Slice-decl.h
td/utils/Slice.h
td/utils/SliceBuilder.h
diff --git a/tdutils/td/utils/simple_tests.h b/tdutils/td/utils/simple_tests.h
new file mode 100644
index 000000000..ba390389d
--- /dev/null
+++ b/tdutils/td/utils/simple_tests.h
@@ -0,0 +1,164 @@
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/PathView.h"
+#include "td/utils/port/thread_local.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tests.h"
+
+#include <utility>
+
+namespace td {
+namespace simple_test {
+
+class DebugContext {
+ public:
+ static DebugContext &instance() {
+ static TD_THREAD_LOCAL DebugContext *instance{};
+ init_thread_local<DebugContext>(instance);
+ return *instance;
+ }
+ template <typename T>
+ void add(Slice name, const T &value, CSlice file = {}, int line = 0) {
+ StringBuilder sb;
+ sb << name << "=" << value;
+
+ if (file.empty()) {
+ sb << " (at " << PathView(file).file_name() << ":" << line << ")";
+ }
+
+ values_.emplace_back(name.str(), sb.as_cslice().str());
+ }
+
+ void pop_back() {
+ if (!values_.empty()) {
+ values_.pop_back();
+ }
+ }
+
+ friend StringBuilder &operator<<(StringBuilder &sb, const DebugContext &ctx) {
+ if (ctx.values_.empty()) {
+ return sb;
+ }
+
+ sb << "\nDebug context:";
+ for (const auto &pair : ctx.values_) {
+ sb << "\n " << pair.second;
+ }
+ return sb;
+ }
+
+ private:
+ vector<std::pair<string, string>> values_;
+};
+
+template <typename T>
+class ScopedDebugValue {
+ public:
+ ScopedDebugValue(Slice name, const T &value, CSlice file = {}, int line = 0) : name_(name.str()) {
+ DebugContext::instance().add(name_, value, file, line);
+ }
+
+ ~ScopedDebugValue() {
+ DebugContext::instance().pop_back();
+ }
+
+ private:
+ string name_;
+};
+
+inline Status format_error(StringBuilder &sb, const char *file, int line) {
+ sb << "\n\tat " << PathView(Slice(file)).file_name() << ":" << line << DebugContext::instance();
+ return Status::Error(sb.as_cslice());
+}
+
+} // namespace simple_test
+
+class StatusTest : public Test {
+ public:
+ ~StatusTest() override = default;
+
+ virtual Status run_test() = 0;
+ virtual string get_test_name() const = 0;
+
+ void run() override {
+ auto status = run_test();
+ if (status.is_ok()) {
+ LOG(INFO) << "Test " << get_test_name() << " PASSED";
+ } else {
+ // Include debug context in error message if available
+ LOG(ERROR) << "Test " << get_test_name() << " FAILED: " << status.message()
+ << simple_test::DebugContext::instance();
+ }
+ }
+};
+
+// Macro for defining status-based tests
+#define S_TEST(test_case_name, test_name) \
+ class TD_CONCAT(StatusTest_, TD_CONCAT(test_case_name, TD_CONCAT(_, test_name))) final : public ::td::StatusTest { \
+ public: \
+ ::td::Status run_test() override; \
+ ::td::string get_test_name() const override { \
+ return #test_case_name "." #test_name; \
+ } \
+ }; \
+ ::td::RegisterTest<TD_CONCAT(StatusTest_, TD_CONCAT(test_case_name, TD_CONCAT(_, test_name)))> TD_CONCAT( \
+ status_test_instance_, \
+ TD_CONCAT(TD_CONCAT(test_case_name, TD_CONCAT(_, test_name)), __LINE__))(#test_case_name "." #test_name); \
+ ::td::Status TD_CONCAT(StatusTest_, TD_CONCAT(test_case_name, TD_CONCAT(_, test_name)))::run_test()
+
+// Add a debug value within current scope with file/line info
+#define TEST_DEBUG_VALUE(name, value) \
+ ::td::simple_test::ScopedDebugValue<decltype(value)> TD_CONCAT(debug_value_, __LINE__)(#name, value, __FILE__, \
+ __LINE__)
+
+#define TEST_TRY_RESULT(name, expr) \
+ auto TD_CONCAT(r_, __LINE__) = (expr); \
+ if (TD_CONCAT(r_, __LINE__).is_error()) { \
+ auto error = TD_CONCAT(r_, __LINE__).move_as_error(); \
+ ::td::StringBuilder sb; \
+ sb << "\nFailed to execute " << #expr << ":\n\t" << error.message(); \
+ return ::td::simple_test::format_error(sb, __FILE__, __LINE__); \
+ } \
+ auto name = TD_CONCAT(r_, __LINE__).move_as_ok(); \
+ TEST_DEBUG_VALUE(name, name)
+
+#define TEST_TRY_STATUS(expr) \
+ { \
+ auto result = (expr); \
+ if (result.is_error()) { \
+ ::td::StringBuilder sb; \
+ sb << "\nFailed to execute " << #expr << ":\n\t" << result.error().message(); \
+ return ::td::simple_test::format_error(sb, __FILE__, __LINE__); \
+ } \
+ }
+
+#define TEST_ASSERT(condition, message) \
+ if (!(condition)) { \
+ ::td::StringBuilder sb; \
+ sb << "\nAssertion failed: " << #condition << " -\n\t" << message; \
+ return ::td::simple_test::format_error(sb, __FILE__, __LINE__); \
+ }
+
+#define TEST_ASSERT_EQ(expected_, received_, message) \
+ { \
+ const auto &expected = (expected_); \
+ const auto &received = (received_); \
+ if (!(expected == received)) { \
+ ::td::StringBuilder sb; \
+ sb << "\n" \
+ << #received_ << " != " << #expected_ << " - " << message << "\n\texpected: " << expected \
+ << "\n\treceived:" << received; \
+ return ::td::simple_test::format_error(sb, __FILE__, __LINE__); \
+ } \
+ }
+
+} // namespace td