Sane C++ Libraries
C++ Platform Abstraction Libraries
Loading...
Searching...
No Matches
Testing.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4#include "../Common/CompilerMacrosExport.h"
5#ifndef SC_EXPORT_LIBRARY_TESTING
6#define SC_EXPORT_LIBRARY_TESTING 0
7#endif
8#define SC_TESTING_EXPORT SC_COMPILER_LIBRARY_EXPORT(SC_EXPORT_LIBRARY_TESTING)
9
10#include "../Common/Assert.h"
11#include "../Common/IGrowableBufferStringPath.h"
12#include "../Common/Result.h"
13#include "../Common/StringSpan.h"
14
15namespace SC
16{
17SC_DECLARE_ASSERT_PROVIDER(TestingAssert, SC_TESTING_EXPORT);
18
19#define SC_TESTING_ASSERT_RELEASE(e) SC_ASSERT_PROVIDER_RELEASE(SC::TestingAssert, e)
20#define SC_TESTING_ASSERT_DEBUG(e) SC_ASSERT_PROVIDER_DEBUG(SC::TestingAssert, e)
21#define SC_TESTING_TRUST_RESULT(expression) SC_TESTING_ASSERT_RELEASE(expression)
22
23struct TestCase;
26
29
35{
36 struct IOutput
37 {
38 virtual ~IOutput() = default;
39
40 virtual void flush() = 0;
41 virtual void printLine(StringSpan text) = 0;
42 virtual void print(StringSpan text) = 0;
43 virtual void print(StringSpan text, StringSpan p0) = 0;
44 virtual void print(StringSpan text, StringSpan p0, StringSpan p1) = 0;
45 virtual void print(StringSpan text, size_t p0) = 0;
46 virtual void print(StringSpan text, size_t p0, size_t p1) = 0;
47 };
48
49 template <typename ConsoleType>
50 struct Output final : public TestReport::IOutput
51 {
52 ConsoleType& console;
53 Output(ConsoleType& console) : console(console) {}
54
55 virtual void flush() override { console.flush(); }
56 virtual void printLine(StringSpan text) override { console.printLine(text); }
57 virtual void print(StringSpan text) override { console.print(text); }
58 virtual void print(StringSpan text, StringSpan p0) override { console.print(text, p0); }
59 virtual void print(StringSpan text, StringSpan p0, StringSpan p1) override { console.print(text, p0, p1); }
60 virtual void print(StringSpan text, size_t p0) override { console.print(text, p0); }
61 virtual void print(StringSpan text, size_t p0, size_t p1) override { console.print(text, p0, p1); }
62 };
63 IOutput& console;
64
66 StringPath executableFile;
68
69 // Options
72 bool quietMode = false;
73 bool runAllTests = false;
74
79 TestReport(IOutput& console, int argc, const char** argv);
81
84 [[nodiscard]] int getTestReturnCode() const;
85
87 [[nodiscard]] bool hasStartupFailure() const { return startupFailure; }
88
92 [[nodiscard]] uint16_t mapPort(uint16_t basePort) const;
93
96 [[nodiscard]] bool isTestExplicitlySelected(StringSpan testName) const;
97
100 [[nodiscard]] bool isTestEnabled(StringSpan testName) const;
101
102 template <typename Statistics>
103 void runGlobalMemoryReport(Statistics stats, bool reportFailure = true)
104 {
105 TestReport::MemoryStatistics memStats;
106 memStats.numAllocate = stats.numAllocate;
107 memStats.numReallocate = stats.numReallocate;
108 memStats.numRelease = stats.numRelease;
109 internalRunGlobalMemoryReport(memStats, reportFailure);
110 }
111
112 private:
113 struct MemoryStatistics
114 {
115 size_t numAllocate = 0;
116 size_t numReallocate = 0;
117 size_t numRelease = 0;
118 };
119
121 void internalRunGlobalMemoryReport(MemoryStatistics stats, bool reportFailure);
122
123 [[nodiscard]] bool isSectionEnabled(StringSpan sectionName) const;
124
125 void testCaseFinished(TestCase& testCase);
126 void printSectionResult(TestCase& testCase);
127
128 friend struct TestCase;
129 uint32_t numTestsSucceeded = 0;
130 uint32_t numTestsFailed = 0;
131 uint16_t portOffset = 0;
132 StringSpan currentSection;
133 StringSpan firstFailedTest;
134 StringSpan testToRun;
135 StringSpan sectionToRun;
136 bool startupFailure = false;
137};
138
145{
149 TestCase(TestReport& report, StringSpan testName);
150 ~TestCase();
151
157 bool recordExpectation(StringSpan expression, bool status, StringSpan detailedError = StringSpan());
158
163 bool recordExpectation(StringSpan expression, Result status);
164
165#if SC_PLATFORM_WINDOWS
166#define SC_TESTING_NO_INLINE __declspec(noinline)
167#else
168#define SC_TESTING_NO_INLINE __attribute__((noinline))
169#endif
170
173 template <typename Test>
174 static SC_TESTING_NO_INLINE bool recordLiteralExpectation(Test& test, const char* expression, size_t length,
175 bool status)
176 {
177 return test.recordExpectation(StringSpan({expression, length}, true, StringEncoding::Ascii), status);
178 }
179
181 template <typename Test>
182 static SC_TESTING_NO_INLINE bool recordLiteralExpectation(Test& test, const char* expression, size_t length,
183 Result status)
184 {
185 return test.recordExpectation(StringSpan({expression, length}, true, StringEncoding::Ascii), status);
186 }
187
188#undef SC_TESTING_NO_INLINE
189
190 enum class Execute
191 {
192 Default,
194 };
195
200 [[nodiscard]] bool test_section(StringSpan sectionName, Execute execution = Execute::Default);
201
203 private:
204 friend struct TestReport;
205 const StringSpan testName;
206
207 uint32_t numTestsSucceeded;
208 uint32_t numTestsFailed;
209 uint32_t numSectionTestsFailed;
210 bool printedSection;
211};
212
213// clang-format off
215#define SC_TEST_EXPECT(e) TestCase::recordLiteralExpectation(*this, #e, sizeof(#e) - 1, (e)) ? (void)0 : (TestCase::report.debugBreakOnFailedTest ? SC_COMPILER_DEBUG_BREAK : (void)0)
216// clang-format on
217
219} // namespace SC
A test case that can be split into multiple sections.
Definition Testing.h:145
bool recordExpectation(StringSpan expression, Result status)
Records an expectation for a given expression.
Execute
Definition Testing.h:191
@ OnlyExplicit
Test is executed only if explicitly requested with –test-section.
@ Default
Test is executed if all tests are enabled or if this specific one matches –test-section.
TestCase(TestReport &report, StringSpan testName)
Adds this TestCase to a TestReport with a name.
TestReport & report
The TestReport object passed in the constructor.
Definition Testing.h:202
bool recordExpectation(StringSpan expression, bool status, StringSpan detailedError=StringSpan())
Records an expectation for a given expression.
bool test_section(StringSpan sectionName, Execute execution=Execute::Default)
Starts a new test section.
static SC_TESTING_NO_INLINE bool recordLiteralExpectation(Test &test, const char *expression, size_t length, Result status)
Forwards a Result expectation with the same literal text and error identity.
Definition Testing.h:182
static SC_TESTING_NO_INLINE bool recordLiteralExpectation(Test &test, const char *expression, size_t length, bool status)
Shares literal-message construction while preserving the test's expectation overloads.
Definition Testing.h:174
Definition Testing.h:37
Definition Testing.h:51
Collects multiple TestCase and reports their results.
Definition Testing.h:35
StringPath applicationRootDirectory
Path to application (on macOS is different from executable path)
Definition Testing.h:67
bool hasStartupFailure() const
Returns true when startup path resolution failed before tests ran.
Definition Testing.h:87
StringPath executableFile
Path to current executable.
Definition Testing.h:66
uint16_t mapPort(uint16_t basePort) const
Applies an optional port offset configured through command line or environment.
int getTestReturnCode() const
Gets return code for this process.
bool isTestExplicitlySelected(StringSpan testName) const
Returns true only when --test explicitly matches testName.
bool isTestEnabled(StringSpan testName) const
Returns true when testName is enabled by the current test selection.
bool quietMode
If true will not print recaps at start or end of the test.
Definition Testing.h:72
bool abortOnFirstFailedTest
If true will abort after first failed test.
Definition Testing.h:70
StringPath libraryRootDirectory
Path to sources directory for library.
Definition Testing.h:65
bool debugBreakOnFailedTest
If true will issue a debugger halt when a test fails.
Definition Testing.h:71
TestReport(IOutput &console, int argc, const char **argv)
Build from a console and executable arguments.
bool runAllTests
If true will run optional/slow tests that are skipped by default.
Definition Testing.h:73