10#include <unordered_set>
23enum class TaskCategory
46 void add (
const std::string& name,
const std::string& tags,
const std::string& file,
int line, TaskCategory testCategory,
void (*func)())
48 std::unordered_set<std::string>& registeredNamesContainer =
49 testCategory == TaskCategory::test
51 : registeredGeneratorNames;
53 const auto insertResult = registeredNamesContainer.insert (name);
54 const bool isDuplicate = ! insertResult.second;
59 std::vector<TaskInfo>& tasks =
60 testCategory == TaskCategory::test
64 tasks.emplace_back (TaskInfo {name, tags, file, line, func});
75 std::vector<TaskInfo>& taskPool =
79 std::vector<TaskInfo> tasks;
81 std::unordered_set<std::string> requestedTags;
83 if (requestedTagsUnparsed.empty())
85 tasks = std::move (taskPool);
89 requestedTags = parseTags (requestedTagsUnparsed);
91 for (TaskInfo& task : taskPool)
93 if (task.tags.empty())
96 std::unordered_set<std::string> taskTags = parseTags (task.tags);
98 if (tagsMatch (requestedTags, taskTags))
99 tasks.emplace_back (std::move (task));
103 if (tasks.size() == 0)
105 std::cout <<
"Nothing to run!" << std::endl;
110 shuffleTasks (tasks);
112 for (
const TaskInfo& task : tasks)
115 std::cout << std::endl;
116 std::cout <<
"[ PASSED ] " << tasksPassed <<
'/' << tasks.size() << std::endl;
119 std::cout <<
"[ FAILED ] " << tasksFailed <<
'/' << tasks.size() << std::endl;
122 std::cout << std::endl << resultAsciiArt << std::endl;
123 return (
int) (tasksFailed != 0);
136 TestRegistry() =
default;
137 std::vector<TaskInfo> tests;
138 std::vector<TaskInfo> generators;
139 std::unordered_set<std::string> registeredTestNames;
140 std::unordered_set<std::string> registeredGeneratorNames;
142 size_t tasksPassed = 0;
143 size_t tasksFailed = 0;
145 void runTask (
const TaskInfo& task)
147 std::cout <<
"[ ... ] Running " << task.name;
148 bool assertionFailed =
false;
149 std::string assertionFailMessage;
150 ExpectationFailureMessages::clear();
152 const auto timestampStart = std::chrono::high_resolution_clock::now();
160 assertionFailMessage = e.what();
161 assertionFailed =
true;
165 assertionFailMessage = e.what();
166 assertionFailed =
true;
169 const auto timestampFinish = std::chrono::high_resolution_clock::now();
170 const auto testDuration = timestampFinish - timestampStart;
173 const bool expectationsFailed = ExpectationFailureMessages::get().size() > 0;
174 const std::string testDurationLabel = formatDuration (testDuration);
176 if (assertionFailed || expectationsFailed)
179 constexpr char separator[] =
"-------------------------------------------";
181 const std::string taskSignature =
183 ? (task.tags.empty() ?
"HART_GENERATE (\"" + task.name +
"\")" :
"HART_GENERATE_WITH_TAGS (\"" + task.name +
"\", " + task.tags +
"\")")
184 : (task.tags.empty() ?
"HART_TEST (\"" + task.name +
"\")" :
"HART_TEST_WITH_TAGS (\"" + task.name +
"\", " + task.tags +
"\")");
187 <<
"[ </3 ] " << testDurationLabel << task.name <<
" - failed" << std::endl
188 << separator << std::endl
189 << task.file <<
':' << task.line << std::endl
190 << taskSignature << std::endl;
194 std::cout << separator << std::endl << assertionFailMessage << std::endl;
197 for (
const std::string& expectationFailureMessage : ExpectationFailureMessages::get())
199 std::cout << separator << std::endl << expectationFailureMessage << std::endl;
202 std::cout << separator << std::endl;
207 std::cout <<
"[ <3 ] " << testDurationLabel << task.name <<
" - passed" << std::endl;
212 static void shuffleTasks (std::vector<TaskInfo>& tasks)
215 std::shuffle (tasks.begin(), tasks.end(), rng);
218 static std::string formatDuration (std::chrono::high_resolution_clock::duration duration)
220 using std::chrono::duration_cast;
221 using std::chrono::microseconds;
222 const long long int durationUs = duration_cast<microseconds> (duration).count();
223 constexpr int targetWidth = 7;
224 std::ostringstream oss;
227 if (durationUs >= 1000000)
229 const double durationSeconds = durationUs / 1.0e6;
230 oss << std::setw (targetWidth - 2) << std::right
231 << std::fixed << std::setprecision (2)
232 << durationSeconds <<
" s] ";
234 else if (durationUs >= 1000)
236 const long long int durationMs = durationUs / 1000;
237 oss << std::setw (targetWidth - 3) << std::right
238 << durationMs <<
" ms] ";
242 oss << std::setw (targetWidth - 3) << std::right
243 << durationUs <<
" us] ";
249 std::unordered_set<std::string> parseTags (
const std::string& tagString)
251 std::unordered_set<std::string> tags;
255 while ((start = tagString.find (
'[', end)) != std::string::npos)
257 end = tagString.find (
']', start + 1);
259 if (end != std::string::npos)
260 tags.insert (tagString.substr (start + 1, end - start - 1));
266 bool tagsMatch (
const std::unordered_set<std::string>& requestedTags,
const std::unordered_set<std::string>& testTags)
268 for (
const std::string& tag : testTags)
269 if (requestedTags.find (tag) != requestedTags.end())
Thrown when the test runner is misconfigured.
Thrown by test asserts like HART_ASSERT_TRUE() and AudioTestBuilder::assertFalse()
static TestRegistry & getInstance()
Gets the singleton instance.
int runAll()
Runs all tests or generators.
Thrown when an inappropriate value is encountered.
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message and returns otherwise.
static const char * hartAsciiArt
static const char * passAsciiArt
static const char * failAsciiArt
Holds values set by the user via CLI interface.
uint_fast32_t getRandomSeed()
Gets random seed set by a "`--seed`/`-s`" argument.
bool shouldRunGenerators()
bool shouldShuffleTasks()
static CLIConfig & getInstance()
Get the singleton instance.