From f59657bfe143a0df5197a1a4862ffd3195e17742 Mon Sep 17 00:00:00 2001
From: lymslive <403708621@qq.com>
Date: Sat, 27 Apr 2024 00:33:04 +0800
Subject: [PATCH] feat: support unit test setup and teardown once #10

---
 CMakeLists.txt          |  11 ++-
 include/unittast.hpp    | 151 ++++++++++++++++++++++++++++++++++
 utest/test-unittast.cpp | 176 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 334 insertions(+), 4 deletions(-)
 create mode 100644 include/unittast.hpp
 create mode 100644 utest/test-unittast.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4cea691..1eafd72 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -50,6 +50,7 @@ add_executable(utCoutTast
     utest/test-tinytast.cpp
     utest/test-file-compare.cpp
     utest/test-classtast.cpp
+    utest/test-unittast.cpp
     utest/test-systemtast.cpp
 )
 
@@ -65,6 +66,7 @@ target_link_libraries(utCoutTast
 add_executable(utCxx98Tast
     utest/test-tinytast.cpp
     utest/test-classtast.cpp
+    utest/test-unittast.cpp
 )
 
 target_compile_definitions(utCxx98Tast PRIVATE USE_TINY_MAIN)
@@ -91,15 +93,16 @@ endif()
 set(COUTTAST_HEADER
     include/tinytast.hpp
     include/couttast.hpp
-    include/tinymain.hpp
+    include/unittast.hpp
+    include/classtast.hpp
+    include/tastargv.hpp
     include/coutstd.hpp
     include/extra-macros.hpp
     include/gtest-macros.hpp
+    include/tinymain.hpp
+    include/coutdebug.hpp
     include/logprint.hpp
     include/private-access.hpp
-    include/classtast.hpp
-    include/tastargv.hpp
-    include/coutdebug.hpp
     include/couttast.h
     include/tinyini.h
     include/systemtast.h
diff --git a/include/unittast.hpp b/include/unittast.hpp
new file mode 100644
index 0000000..69c43ef
--- /dev/null
+++ b/include/unittast.hpp
@@ -0,0 +1,151 @@
+/**
+ * @file classtast.hpp
+ * @author lymslive
+ * @date 2024-04-25
+ * @brief Support group test suite based on file unit or namespace in the file.
+ * @details A testing source file or compile unit is considered as a test
+ *  module. The test cases defined in one unit may have some relation and share some
+ *  expensive resource, such as global(better static) varibles. So an unit
+ *  usually need run a `setup()` function once before run any test case, and
+ *  it is better to have another opposite `teardown()` function to run after
+ *  all tast case.
+ * @note The setup and teardown function handle auto tast(like from `DEF_TAST`) 
+ *  and manual tool(like from `DEF_TOOL`) differently. So we use `tast` and
+ *  `tool` word for each type, and `test` for both.
+ * @note Thought it is no encouraged, for large source file, you can have
+ *  multiple namespace in one file, and each namespace is can become seperate
+ *  test unit. But it is not easy to share test unit in the same namespace
+ *  across different source file.
+ * */
+#pragma once
+#ifndef UNITTAST_HPP__
+#define UNITTAST_HPP__
+
+#include "tinytast.hpp"
+
+namespace tast
+{
+
+/// The state of current unit.
+struct CUnitState
+{
+    int tastCount;      //< Count of tast(but not tool) in this unit defined.
+    int runCount;       //< Count of tast finished run.
+    voidfun_t setup;    //< Function to run once before the first test/tool.
+    voidfun_t teardown; //< Function to run after the last tast.
+    bool ready;         //< The setup function has been called.
+
+    CUnitState(voidfun_t fn = NULL)
+        : tastCount(0), runCount(0), setup(fn), teardown(NULL), ready(false)
+    {}
+
+    /// Add count of tast in the unit.
+    struct AddTast
+    {
+        AddTast(CUnitState& unit, bool autoRun)
+        {
+            if (autoRun)
+            {
+                unit.tastCount++;
+            }
+        }
+    };
+
+    /// Define teardown function in the unit.
+    struct Defer
+    {
+        Defer(CUnitState& unit, voidfun_t fn)
+        {
+            unit.teardown = fn;
+        }
+    };
+
+    /// Check to run setup function once before any tast/tool.
+    void CheckSetupFirst()
+    {
+        if (!ready && setup != NULL)
+        {
+            setup();
+            ready = true;
+        }
+    }
+
+    /// Check to run teardown function after all tast.
+    void CheckTeardownLast()
+    {
+        if (teardown == NULL)
+        {
+            return;
+        }
+        if (G_TASTMGR->GetTastRun()->m_autoRun == false)
+        {
+            return;
+        }
+        runCount++;
+        if (runCount >= tastCount)
+        {
+            teardown();
+            ready = false;
+        }
+    }
+};
+
+/// The base class wrapper for test in current refered unit.
+struct CUnitTast
+{
+    CUnitState& unit;
+
+    CUnitTast(CUnitState& refer) : unit(refer)
+    {}
+};
+
+/// The running entrance function for each test.
+template <typename testT>
+void run_unit_tast()
+{
+    testT self;
+    self.unit.CheckSetupFirst();
+    self.run();
+    self.unit.CheckTeardownLast();
+}
+
+} /* tast:: */ 
+
+/// Setup current unit for following test cases.
+/// Must use this macro the eable test unit tracking.
+#define SETUP() \
+    static void s_setup();                    \
+    static tast::CUnitState s_unit(s_setup); \
+    void s_setup()
+
+/// Define optional teardown function for current test unit.
+#define TEARDOWN() \
+    static void s_teardown(); \
+    static tast::CUnitState::Defer s_unitDefer(s_unit, s_teardown); \
+    void s_teardown()
+
+
+/// Helper macros to simply the unit test case definition.
+#define UTAST_COUNTER(name) tast_##name##_counter
+#define UTAST_CLASS(name) CUnitTast_ ## name
+#define UTAST_RUNNER(name) tast::run_unit_tast<UTAST_CLASS(name)>
+
+/// Define a test case in current unit.
+#define DEU_TAST_IMPL(name, autoRun, ...) \
+    static void TAST_RUNNER(name)(); \
+    struct UTAST_CLASS(name) : public tast::CUnitTast \
+    { \
+        UTAST_CLASS(name)() : tast::CUnitTast(s_unit) {} \
+        void run(); \
+    }; \
+    static tast::CTastBuilder TAST_BUILDER(name)(UTAST_RUNNER(name), #name, __FILE__, __LINE__, autoRun, ## __VA_ARGS__); \
+    static tast::CUnitState::AddTast UTAST_COUNTER(name)(s_unit, autoRun); \
+    void UTAST_CLASS(name)::run()
+
+/// Define an auto tast case. Must use after SETUP unit.
+#define DEU_TAST(name, ...) DEU_TAST_IMPL(name, true, ## __VA_ARGS__)
+
+/// Define a manual tool case. Must use after SETUP unit.
+#define DEU_TOOL(name, ...) DEU_TAST_IMPL(name, false, ## __VA_ARGS__)
+
+#endif /* end of include guard: UNITTAST_HPP__ */
diff --git a/utest/test-unittast.cpp b/utest/test-unittast.cpp
new file mode 100644
index 0000000..437b485
--- /dev/null
+++ b/utest/test-unittast.cpp
@@ -0,0 +1,176 @@
+#include "unittast.hpp"
+
+int* s_pBuffer = NULL;
+void cout_counter();
+
+SETUP()
+{
+    s_pBuffer = new int[8];
+    DESC("file SETUP called");
+    cout_counter();
+    s_pBuffer[0] = s_unit.tastCount;
+}
+
+TEARDOWN()
+{
+    delete[] s_pBuffer;
+    s_pBuffer = NULL;
+    DESC("file TEARDOWN called");
+    cout_counter();
+}
+
+void cout_counter()
+{
+    COUT(s_pBuffer);
+    COUT(s_unit.tastCount);
+    COUT(s_unit.runCount);
+}
+
+DEF_TAST(unit_normal, "normal test not in uint")
+{
+    cout_counter();
+    // s_pBuffer may not inited.
+    // COUT(s_pBuffer[0], s_unit.tastCount);
+}
+
+DEU_TAST(unit_auto1, "test in uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+    int* pBuffer = new int[16];
+    pBuffer[15] = 1;
+    COUT(pBuffer);
+    delete[] pBuffer;
+}
+
+DEU_TAST(unit_auto2, "test in uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+}
+
+DEU_TOOL(unit_tool1, "tool in uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+}
+
+DEU_TOOL(unit_tool2, "tool in uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+}
+
+namespace unest
+{
+
+double* s_pBuffer = NULL;
+void cout_counter();
+
+SETUP()
+{
+    s_pBuffer = new double[4];
+    DESC("unest::SETUP called");
+    cout_counter();
+    s_pBuffer[0] = 2.0;
+}
+
+TEARDOWN()
+{
+    delete[] s_pBuffer;
+    s_pBuffer = NULL;
+    DESC("unest::TEARDOWN called");
+    cout_counter();
+}
+
+void cout_counter()
+{
+    COUT(s_pBuffer);
+    COUT(s_unit.tastCount);
+    COUT(s_unit.runCount);
+}
+
+DEU_TAST(unest_auto1, "test in namespace uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+    int* pBuffer = new int[16];
+    pBuffer[15] = 1;
+    COUT(pBuffer);
+    delete[] pBuffer;
+}
+
+DEU_TAST(unest_auto2, "test in namespace uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+}
+
+} // endof unest::
+
+namespace umore
+{
+
+double* s_pBuffer = NULL;
+void cout_counter();
+
+SETUP()
+{
+    s_pBuffer = new double[4];
+    DESC("umore::SETUP called");
+    cout_counter();
+    s_pBuffer[0] = 3.0;
+}
+
+TEARDOWN()
+{
+    delete[] s_pBuffer;
+    s_pBuffer = NULL;
+    DESC("umore::TEARDOWN called");
+    cout_counter();
+}
+
+void cout_counter()
+{
+    COUT(s_pBuffer);
+    COUT(s_unit.tastCount);
+    COUT(s_unit.runCount);
+}
+
+// yes, the test name is the same as above unest_auto1
+DEU_TAST(unest_auto1, "test in namespace uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+}
+
+DEU_TAST(unest_auto2, "test in namespace uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+}
+
+DEU_TAST(unest_auto3, "test in namespace uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+}
+
+} // end of umore::
+
+namespace unot
+{
+
+DEU_TAST(unit_auto2, "test in uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+}
+
+DEU_TAST(unit_auto3, "test in uint")
+{
+    cout_counter();
+    COUT(s_pBuffer[0], s_unit.tastCount);
+}
+
+} // end of umore