HART  0.1.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_test_registry.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm> // shuffle()
4#include <iostream>
5#include <random>
6#include <string>
7#include <unordered_set>
8#include <vector>
9
14
15namespace hart
16{
17
18/// @brief Determines whether the task is a test or a generator
19/// @private
20enum class TaskCategory
21{
22 test,
23 generate
24};
25
26/// @brief Runs the test cases
27/// @details For internal use by HART. You're not supposed to interact with it directly, only through the macros
28/// such as @ref HART_RUN_ALL_TESTS(), @ref HART_TEST(), @ref HART_TEST_WITH_TAGS(), @ref HART_GENERATE(), @ref HART_GENERATE_WITH_TAGS()
29/// @ingroup TestRunner
31public:
32
33 /// @brief Gets the singleton instance
35 {
36 static TestRegistry reg;
37 return reg;
38 }
39
40 /// @brief Adds a task (test or generator)
41 /// @details Gets called when a test case is declared with a macro like @ref HART_TEST()
42 /// @private
43 void add (const std::string& name, const std::string& tags, TaskCategory testCategory, void (*func)())
44 {
45 std::unordered_set<std::string>& registeredNamesContainer =
46 testCategory == TaskCategory::test
47 ? registeredTestNames
48 : registeredGeneratorNames;
49
50 const auto insertResult = registeredNamesContainer.insert (name);
51 const bool isDuplicate = ! insertResult.second;
52
53 if (isDuplicate)
54 HART_THROW_OR_RETURN_VOID (hart::ValueError, std::string ("Duplicate test case name found: ") + name);
55
56 std::vector<TaskInfo>& tasks =
57 testCategory == TaskCategory::test
58 ? tests
59 : generators;
60
61 tasks.emplace_back (TaskInfo {name, tags, func});
62 }
63
64 /// @brief Runs all tests or generators
65 int runAll()
66 {
67 // TODO: Optional shuffle before running
68 // TODO: Make data root dir if set, but doesn't exist
69 // TODO: Add support for runnings tasks in a thread pool
70
71 std::cout << hartAsciiArt << std::endl;
72
73 std::vector<TaskInfo>& tasks =
75 ? generators
76 : tests;
77
78 if (tasks.size() == 0)
79 {
80 std::cout << "Nothing to run!" << std::endl;
81 return 0;
82 }
83
85 shuffleTasks (tasks);
86
87 for (const TaskInfo& task : tasks)
88 runTask (task);
89
90 std::cout << std::endl;
91 std::cout << "[ PASSED ] " << tasksPassed << '/' << tasks.size() << std::endl;
92
93 if (tasksFailed > 0)
94 std::cout << "[ FAILED ] " << tasksFailed << '/' << tasks.size() << std::endl;
95
96 const char* resultAsciiArt = tasksFailed > 0 ? failAsciiArt : passAsciiArt;
97 std::cout << std::endl << resultAsciiArt << std::endl;
98 return (int) (tasksFailed != 0);
99 }
100
101private:
102 struct TaskInfo
103 {
104 std::string name;
105 std::string tags;
106 void (*func)();
107 };
108
109 TestRegistry() = default; // Private ctor for singleton
110 std::vector<TaskInfo> tests;
111 std::vector<TaskInfo> generators;
112 std::unordered_set<std::string> registeredTestNames;
113 std::unordered_set<std::string> registeredGeneratorNames;
114
115 size_t tasksPassed = 0;
116 size_t tasksFailed = 0;
117
118 void runTask (const TaskInfo& task)
119 {
120 std::cout << "[ ... ] Running " << task.name;
121 bool assertionFailed = false;
122 std::string assertionFailMessage;
124
125 try
126 {
127 task.func();
128 }
129 catch (const hart::TestAssertException& e)
130 {
131 assertionFailMessage = e.what();
132 assertionFailed = true;
133 }
134 catch (const hart::ConfigurationError& e)
135 {
136 assertionFailMessage = e.what();
137 assertionFailed = true;
138 }
139
140 // TODO: Output test durations
141
142 std::cout << '\r';
143 const bool expectationsFailed = ExpectationFailureMessages::get().size() > 0;
144
145 if (assertionFailed || expectationsFailed)
146 {
147 constexpr char separator[] = "-------------------------------------------";
148 std::cout << "[ </3 ] " << task.name << " - failed" << std::endl;
149
150 if (assertionFailed)
151 {
152 std::cout << separator << std::endl << assertionFailMessage << std::endl;
153 }
154
155 for (const std::string& expectationFailureMessage : ExpectationFailureMessages::get())
156 {
157 std::cout << separator << std::endl << expectationFailureMessage << std::endl;
158 }
159
160 std::cout << separator << std::endl;
161 ++tasksFailed;
162 }
163 else
164 {
165 std::cout << "[ <3 ] " << task.name << " - passed" << std::endl;
166 ++tasksPassed;
167 }
168 }
169
170 static void shuffleTasks (std::vector<TaskInfo>& tasks)
171 {
173 std::shuffle (tasks.begin(), tasks.end(), rng);
174 }
175};
176
177} // namespace hart
static std::vector< std::string > & get()
Runs the test cases.
static TestRegistry & getInstance()
Gets the singleton instance.
int runAll()
Runs all tests or generators.
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)
static const char * hartAsciiArt
static const char * passAsciiArt
static const char * failAsciiArt
uint_fast32_t getRandomSeed()
static CLIConfig & getInstance()