GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 93.7% 133 / 23 / 165
Functions: 100.0% 15 / 1 / 16
Branches: 53.1% 188 / 48 / 402

test/util/TestUtil.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "TestUtil.h"
4
5 #include <dirent.h>
6 #if OS_UNIX
7 #include <cstring> // Required by builds on Unix
8 #endif
9
10 #include <gtest/gtest.h>
11
12 #include <util/CommonUtil.h>
13 #include <util/FileUtil.h>
14
15 #include "../driver/TestDriver.h"
16
17 namespace spice::testing {
18
19 using namespace spice::compiler;
20
21 extern TestDriverCliOptions testDriverCliOptions;
22
23 1939 void TestUtil::parseTestArgs(const std::filesystem::path &sourceCodePath, std::vector<std::string> &args) {
24
3/4
✓ Branch 2 → 3 taken 1939 times.
✗ Branch 2 → 79 not taken.
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 1936 times.
1939 if (!exists(sourceCodePath))
25 1860 return;
26
27
1/2
✓ Branch 5 → 6 taken 1936 times.
✗ Branch 5 → 79 not taken.
1936 std::ifstream file(sourceCodePath);
28
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1936 times.
1936 assert(file.is_open());
29
30 1936 std::string firstLine;
31
4/6
✓ Branch 10 → 11 taken 1936 times.
✗ Branch 10 → 75 not taken.
✓ Branch 11 → 12 taken 1936 times.
✗ Branch 11 → 75 not taken.
✓ Branch 12 → 13 taken 3 times.
✓ Branch 12 → 14 taken 1933 times.
1936 if (!std::getline(file, firstLine))
32 3 return;
33
1/2
✓ Branch 14 → 15 taken 1933 times.
✗ Branch 14 → 63 not taken.
1933 firstLine = CommonUtil::trim(firstLine);
34
35 // Only allow "// TEST: " as prefix
36
2/4
✓ Branch 17 → 18 taken 1933 times.
✗ Branch 17 → 75 not taken.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 1933 times.
1933 assert(firstLine.rfind("//TEST:", 0) != 0);
37 if (firstLine.rfind("// TEST: ", 0) != 0) // GCOV_EXCL_LINE
38 return; // GCOV_EXCL_LINE
39
40 79 const size_t colonPos = firstLine.find(':');
41
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 79 times.
79 if (colonPos == std::string::npos)
42 return;
43
44
2/4
✓ Branch 26 → 27 taken 79 times.
✗ Branch 26 → 66 not taken.
✓ Branch 27 → 28 taken 79 times.
✗ Branch 27 → 64 not taken.
79 const std::string argString = CommonUtil::trim(firstLine.substr(colonPos + 1));
45
3/4
✓ Branch 29 → 30 taken 79 times.
✗ Branch 29 → 72 not taken.
✓ Branch 48 → 32 taken 175 times.
✓ Branch 48 → 49 taken 79 times.
333 for (const std::string &arg : CommonUtil::split(argString)) {
46
1/2
✓ Branch 34 → 35 taken 175 times.
✗ Branch 34 → 69 not taken.
175 const std::string trimmedArg = CommonUtil::trim(arg);
47
1/2
✓ Branch 36 → 37 taken 175 times.
✗ Branch 36 → 38 not taken.
175 if (!trimmedArg.empty())
48
1/2
✓ Branch 37 → 38 taken 175 times.
✗ Branch 37 → 67 not taken.
175 args.push_back(trimmedArg);
49 175 }
50
4/4
✓ Branch 53 → 54 taken 79 times.
✓ Branch 53 → 55 taken 1857 times.
✓ Branch 58 → 59 taken 79 times.
✓ Branch 58 → 61 taken 1857 times.
3872 }
51
52 /**
53 * Collect the test cases in a particular test suite
54 *
55 * @param suiteName Name of the test suite
56 * @param useSubDirs Use subdirectories as test cases
57 * @return Vector of tests cases
58 */
59 22 std::vector<TestCase> TestUtil::collectTestCases(const char *suiteName, bool useSubDirs) {
60
3/6
✓ Branch 2 → 3 taken 22 times.
✗ Branch 2 → 111 not taken.
✓ Branch 3 → 4 taken 22 times.
✗ Branch 3 → 108 not taken.
✓ Branch 4 → 5 taken 22 times.
✗ Branch 4 → 106 not taken.
22 const std::filesystem::path suitePath = std::filesystem::path(PATH_TEST_FILES) / suiteName;
61
62 22 std::vector<TestCase> testCases;
63
1/2
✓ Branch 7 → 8 taken 22 times.
✗ Branch 7 → 171 not taken.
22 testCases.reserve(EXPECTED_NUMBER_OF_TESTS);
64
65
2/2
✓ Branch 8 → 9 taken 8 times.
✓ Branch 8 → 63 taken 14 times.
22 if (useSubDirs) {
66 // Collect subdirectories of the given suite
67
1/2
✓ Branch 9 → 10 taken 8 times.
✗ Branch 9 → 144 not taken.
8 const std::vector<std::string> testGroupDirs = getSubdirs(suitePath);
68
69 // Convert them to test cases
70
2/2
✓ Branch 60 → 12 taken 184 times.
✓ Branch 60 → 61 taken 8 times.
200 for (const std::string &groupDirName : testGroupDirs) {
71
2/4
✓ Branch 14 → 15 taken 184 times.
✗ Branch 14 → 114 not taken.
✓ Branch 15 → 16 taken 184 times.
✗ Branch 15 → 112 not taken.
184 const std::filesystem::path groupPath = suitePath / groupDirName;
72
3/4
✓ Branch 17 → 18 taken 184 times.
✗ Branch 17 → 137 not taken.
✓ Branch 48 → 20 taken 1184 times.
✓ Branch 48 → 49 taken 184 times.
1552 for (const std::string &caseDirName : getSubdirs(groupPath)) {
73
2/4
✓ Branch 22 → 23 taken 1184 times.
✗ Branch 22 → 117 not taken.
✓ Branch 23 → 24 taken 1184 times.
✗ Branch 23 → 115 not taken.
1184 const std::filesystem::path testPath = groupPath / caseDirName;
74
7/18
✓ Branch 25 → 26 taken 1184 times.
✗ Branch 25 → 129 not taken.
✓ Branch 26 → 27 taken 1184 times.
✗ Branch 26 → 127 not taken.
✓ Branch 27 → 28 taken 1184 times.
✗ Branch 27 → 123 not taken.
✓ Branch 28 → 29 taken 1184 times.
✗ Branch 28 → 121 not taken.
✓ Branch 29 → 30 taken 1184 times.
✗ Branch 29 → 118 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 1184 times.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 1184 times.
✗ Branch 118 → 119 not taken.
✗ Branch 118 → 120 not taken.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 126 not taken.
1184 const TestCase tc = {toCamelCase(groupDirName), toCamelCase(caseDirName), testPath};
75
1/2
✓ Branch 36 → 37 taken 1184 times.
✗ Branch 36 → 130 not taken.
1184 testCases.push_back(tc);
76 1184 }
77 184 }
78 8 } else {
79 // Collect test cases
80
3/4
✓ Branch 63 → 64 taken 14 times.
✗ Branch 63 → 170 not taken.
✓ Branch 97 → 66 taken 132 times.
✓ Branch 97 → 98 taken 14 times.
160 for (const std::string &caseDirName : getSubdirs(suitePath)) {
81
2/4
✓ Branch 68 → 69 taken 132 times.
✗ Branch 68 → 147 not taken.
✓ Branch 69 → 70 taken 132 times.
✗ Branch 69 → 145 not taken.
132 const std::filesystem::path testPath = suitePath / caseDirName;
82
7/18
✓ Branch 73 → 74 taken 132 times.
✗ Branch 73 → 159 not taken.
✓ Branch 74 → 75 taken 132 times.
✗ Branch 74 → 157 not taken.
✓ Branch 75 → 76 taken 132 times.
✗ Branch 75 → 153 not taken.
✓ Branch 76 → 77 taken 132 times.
✗ Branch 76 → 151 not taken.
✓ Branch 77 → 78 taken 132 times.
✗ Branch 77 → 148 not taken.
✗ Branch 78 → 79 not taken.
✓ Branch 78 → 80 taken 132 times.
✗ Branch 81 → 82 not taken.
✓ Branch 81 → 83 taken 132 times.
✗ Branch 148 → 149 not taken.
✗ Branch 148 → 150 not taken.
✗ Branch 154 → 155 not taken.
✗ Branch 154 → 156 not taken.
264 const TestCase tc = {toCamelCase(suiteName), toCamelCase(caseDirName), testPath};
83
1/2
✓ Branch 85 → 86 taken 132 times.
✗ Branch 85 → 163 not taken.
132 testCases.push_back(tc);
84 132 }
85 }
86
87
1/2
✗ Branch 101 → 102 not taken.
✓ Branch 101 → 103 taken 22 times.
22 assert(testCases.size() <= EXPECTED_NUMBER_OF_TESTS);
88 22 return testCases;
89 22 }
90
91 /**
92 * Check if the expected output matches the actual output
93 *
94 * @param originalRefPath Path to the reference file
95 * @param getActualOutput Callback to execute the required steps to get the actual test output
96 * @param modifyOutputFct Callback to modify the output before comparing it with the reference
97 * @param x86Only Only compare/update ref file on x86_64
98 *
99 * @return True, if the ref file was found
100 */
101 13854 bool TestUtil::checkRefMatch(const std::filesystem::path &originalRefPath, GetOutputFct getActualOutput,
102 ModifyOutputFct modifyOutputFct, [[maybe_unused]] bool x86Only) {
103
3/4
✓ Branch 2 → 3 taken 13854 times.
✗ Branch 2 → 65 not taken.
✓ Branch 41 → 4 taken 41532 times.
✓ Branch 41 → 42 taken 12117 times.
53649 for (const std::filesystem::path &refPath : expandRefPaths(originalRefPath)) {
104 if (testDriverCliOptions.isVerbose) // GCOV_EXCL_LINE
105 std::cout << "Checking for ref file: " << refPath << " - "; // GCOV_EXCL_LINE
106
3/4
✓ Branch 8 → 9 taken 41532 times.
✗ Branch 8 → 62 not taken.
✓ Branch 9 → 10 taken 39795 times.
✓ Branch 9 → 14 taken 1737 times.
41532 if (!exists(refPath)) {
107 if (testDriverCliOptions.isVerbose) // GCOV_EXCL_LINE
108 std::cout << "not found" << std::endl; // GCOV_EXCL_LINE
109 39795 continue;
110 }
111 if (testDriverCliOptions.isVerbose) // GCOV_EXCL_LINE
112 std::cout << "ok" << std::endl; // GCOV_EXCL_LINE
113
114 // Get actual output
115
1/2
✓ Branch 17 → 18 taken 1737 times.
✗ Branch 17 → 62 not taken.
1737 std::string actualOutput = getActualOutput();
116
117 #ifndef ARCH_X86_64
118 // Cancel early, before comparing or updating the refs
119 if (x86Only && refPath == originalRefPath)
120 return true;
121 #endif
122
123 if (testDriverCliOptions.updateRefs) { // GCOV_EXCL_LINE
124 FileUtil::writeToFile(refPath, actualOutput); // GCOV_EXCL_LINE
125
2/2
✓ Branch 20 → 21 taken 878 times.
✓ Branch 20 → 38 taken 859 times.
1737 } else if (!testDriverCliOptions.enableCoverage) {
126 // In coverage mode, debug info and coverage counters change the generated output, so comparing it against the
127 // reference would fail spuriously. Still call getActualOutput() above though, to drive the pipeline stage that
128 // produces it (e.g. running the optimizer for the opt levels that have a reference file).
129
1/2
✓ Branch 21 → 22 taken 878 times.
✗ Branch 21 → 59 not taken.
878 std::string expectedOutput = FileUtil::getFileContent(refPath);
130
1/2
✓ Branch 22 → 23 taken 878 times.
✗ Branch 22 → 57 not taken.
878 modifyOutputFct(expectedOutput, actualOutput);
131
2/14
✓ Branch 23 → 24 taken 878 times.
✗ Branch 23 → 56 not taken.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 35 taken 878 times.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 53 not taken.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 51 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 51 not taken.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 50 not taken.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 48 not taken.
878 EXPECT_EQ(expectedOutput, actualOutput) << "Output does not match the reference file: " << refPath;
132 878 }
133 1737 return true;
134 1737 }
135 12117 return false;
136 }
137
138 /**
139 * Check if a variant of the requested ref file was found
140 *
141 * @param originalRefPath Path to the reference file
142 * @return True, if the ref file was found
143 */
144 7271 bool TestUtil::doesRefExist(const std::filesystem::path &originalRefPath) {
145
1/2
✓ Branch 2 → 3 taken 7271 times.
✗ Branch 2 → 10 not taken.
7271 const std::array<std::filesystem::path, 3> refPaths = expandRefPaths(originalRefPath);
146
1/2
✓ Branch 3 → 4 taken 7271 times.
✗ Branch 3 → 8 not taken.
36321 return std::ranges::any_of(refPaths, [](const std::filesystem::path &refPath) { return exists(refPath); });
147 7271 }
148
149 /**
150 * Handle a test error
151 *
152 * @param testCase Testcase which has produced the error
153 * @param error Exception with error message
154 */
155 428 void TestUtil::handleError(const TestCase &testCase, const std::exception &error) {
156
1/2
✓ Branch 5 → 6 taken 428 times.
✗ Branch 5 → 49 not taken.
1284 std::string errorWhat = error.what();
157
3/6
✓ Branch 9 → 10 taken 428 times.
✗ Branch 9 → 60 not taken.
✓ Branch 12 → 13 taken 428 times.
✗ Branch 12 → 54 not taken.
✓ Branch 13 → 14 taken 428 times.
✗ Branch 13 → 52 not taken.
1284 CommonUtil::replaceAll(errorWhat, "\\", "/");
158
159 // Fail if no ref file exists
160
2/4
✓ Branch 18 → 19 taken 428 times.
✗ Branch 18 → 66 not taken.
✓ Branch 19 → 20 taken 428 times.
✗ Branch 19 → 64 not taken.
428 const std::filesystem::path refPath = testCase.testPath / REF_NAME_ERROR_OUTPUT;
161 if (!doesRefExist(refPath)) // LCOV_EXCL_LINE
162 FAIL() << "Expected no error, but got: " + errorWhat; // LCOV_EXCL_LINE
163
164 // Check if the exception message matches the expected output
165
1/2
✓ Branch 34 → 35 taken 428 times.
✗ Branch 34 → 76 not taken.
856 checkRefMatch(refPath, [&] { return errorWhat; });
166
2/4
✓ Branch 39 → 40 taken 428 times.
✗ Branch 39 → 41 not taken.
✓ Branch 44 → 45 taken 428 times.
✗ Branch 44 → 47 not taken.
856 }
167
168 /**
169 * Get subdirectories of the given path
170 *
171 * @param basePath Path to a directory
172 * @return Vector of subdirs
173 */
174 206 std::vector<std::string> TestUtil::getSubdirs(const std::filesystem::path &basePath) {
175 206 std::vector<std::string> subdirs;
176
3/6
✓ Branch 2 → 3 taken 206 times.
✗ Branch 2 → 19 not taken.
✓ Branch 4 → 5 taken 206 times.
✗ Branch 4 → 17 not taken.
✓ Branch 6 → 7 taken 206 times.
✗ Branch 6 → 15 not taken.
206 if (DIR *dir = opendir(basePath.string().c_str()); dir != nullptr) {
177 dirent *ent;
178
3/4
✓ Branch 12 → 13 taken 2118 times.
✗ Branch 12 → 20 not taken.
✓ Branch 13 → 8 taken 1912 times.
✓ Branch 13 → 14 taken 206 times.
2118 while ((ent = readdir(dir)) != nullptr) {
179
4/4
✓ Branch 8 → 9 taken 1706 times.
✓ Branch 8 → 11 taken 206 times.
✓ Branch 9 → 10 taken 1500 times.
✓ Branch 9 → 11 taken 206 times.
1912 if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0)
180
1/2
✓ Branch 10 → 11 taken 1500 times.
✗ Branch 10 → 20 not taken.
1500 subdirs.emplace_back(ent->d_name);
181 }
182
1/2
✓ Branch 14 → 15 taken 206 times.
✗ Branch 14 → 20 not taken.
206 closedir(dir);
183 }
184 206 return subdirs;
185 } // LCOV_EXCL_LINE - false positive
186
187 /**
188 * Retrieve the contents of a file as a vector of line strings. Empty lines are omitted
189 *
190 * @param filePath File path
191 * @return Vector of strings which are the lines of the file
192 */
193 6 std::vector<std::string> TestUtil::getFileContentLinesVector(const std::filesystem::path &filePath) {
194 6 std::vector<std::string> lines;
195
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 23 not taken.
6 std::ifstream inputFileStream;
196
1/2
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 21 not taken.
6 inputFileStream.open(filePath);
197
4/6
✓ Branch 11 → 12 taken 12 times.
✗ Branch 11 → 18 not taken.
✓ Branch 12 → 13 taken 12 times.
✗ Branch 12 → 18 not taken.
✓ Branch 13 → 5 taken 6 times.
✓ Branch 13 → 14 taken 6 times.
18 for (std::string line; std::getline(inputFileStream, line);) {
198
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 6 times.
6 if (!line.empty())
199
1/2
✓ Branch 8 → 9 taken 6 times.
✗ Branch 8 → 18 not taken.
6 lines.push_back(line);
200 6 }
201 6 return lines;
202 6 }
203
204 /**
205 * Convert a string to camel case
206 *
207 * @param input Input string
208 * @return Camel-cased string
209 */
210 7848 std::string TestUtil::toCamelCase(std::string input) {
211
2/2
✓ Branch 31 → 3 taken 116566 times.
✓ Branch 31 → 32 taken 7848 times.
248828 for (auto it = input.begin(); it != input.end(); ++it) {
212
5/6
✓ Branch 5 → 6 taken 112846 times.
✓ Branch 5 → 9 taken 3720 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 112846 times.
✓ Branch 11 → 12 taken 3720 times.
✓ Branch 11 → 21 taken 112846 times.
229412 if (*it == '-' || *it == '_') {
213
1/2
✓ Branch 15 → 16 taken 3720 times.
✗ Branch 15 → 34 not taken.
3720 it = input.erase(it);
214 7440 *it = static_cast<char>(toupper(*it));
215 }
216 }
217 7848 return input;
218 }
219
220 /**
221 * Create a clean, per-test directory for build artifacts (object files, compiler cache and the linked executable).
222 * The directory name is derived from the unique test suite/case name, so concurrently running test processes (e.g. when the
223 * suite is driven by gtest-parallel) never write to the same files.
224 *
225 * @param testCase Test case to prepare the artifact directory for
226 * @return Path to the prepared artifact directory
227 */
228 1292 std::filesystem::path TestUtil::prepareArtifactDir(const TestCase &testCase) {
229
6/12
✓ Branch 2 → 3 taken 1292 times.
✗ Branch 2 → 37 not taken.
✓ Branch 3 → 4 taken 1292 times.
✗ Branch 3 → 35 not taken.
✓ Branch 4 → 5 taken 1292 times.
✗ Branch 4 → 30 not taken.
✓ Branch 5 → 6 taken 1292 times.
✗ Branch 5 → 28 not taken.
✓ Branch 6 → 7 taken 1292 times.
✗ Branch 6 → 26 not taken.
✓ Branch 7 → 8 taken 1292 times.
✗ Branch 7 → 24 not taken.
1292 const std::string dirName = toCamelCase(testCase.testSuite) + "_" + toCamelCase(testCase.testName);
230
3/6
✓ Branch 13 → 14 taken 1292 times.
✗ Branch 13 → 44 not taken.
✓ Branch 14 → 15 taken 1292 times.
✗ Branch 14 → 41 not taken.
✓ Branch 15 → 16 taken 1292 times.
✗ Branch 15 → 39 not taken.
1292 const std::filesystem::path artifactDir = std::filesystem::path("./test-tmp") / dirName;
231 1292 std::error_code ec;
232
1/2
✓ Branch 19 → 20 taken 1292 times.
✗ Branch 19 → 45 not taken.
1292 std::filesystem::remove_all(artifactDir, ec); // Clean up leftovers from a previous run
233
1/2
✓ Branch 20 → 21 taken 1292 times.
✗ Branch 20 → 45 not taken.
1292 std::filesystem::create_directories(artifactDir);
234 1292 return artifactDir;
235 1292 }
236
237 /**
238 * Get the path of the linked test executable inside the given per-test artifact directory.
239 *
240 * @param artifactDir Per-test artifact directory
241 * @return Path to the executable
242 */
243 1286 std::filesystem::path TestUtil::getExecutablePath(const std::filesystem::path &artifactDir) {
244 #if OS_WINDOWS
245 std::filesystem::path executablePath = artifactDir / "source.exe";
246 #else
247
2/4
✓ Branch 2 → 3 taken 1286 times.
✗ Branch 2 → 10 not taken.
✓ Branch 3 → 4 taken 1286 times.
✗ Branch 3 → 8 not taken.
1286 std::filesystem::path executablePath = artifactDir / "source";
248 #endif
249 // Normalize to native separators. The artifact dir is built from a literal "./test-tmp" prefix, so the path string still
250 // contains forward slashes. When the executable is later run through a shell, the Windows command interpreter treats '/' as
251 // an option switch and fails with "'.' is not recognized as an internal or external command".
252 1286 executablePath.make_preferred();
253 1286 return executablePath;
254 }
255
256 /**
257 * Check if the provided test case is disabled
258 *
259 * @param testCase Test case to check
260 * @return Disabled or not
261 */
262 1316 bool TestUtil::isDisabled(const TestCase &testCase) {
263
5/8
✓ Branch 2 → 3 taken 1316 times.
✗ Branch 2 → 96 not taken.
✓ Branch 3 → 4 taken 1316 times.
✗ Branch 3 → 94 not taken.
✓ Branch 4 → 5 taken 1316 times.
✗ Branch 4 → 92 not taken.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 9 taken 1314 times.
1316 if (exists(testCase.testPath / CTL_SKIP_DISABLED))
264 2 return true;
265
10/20
✓ Branch 9 → 10 taken 1314 times.
✗ Branch 9 → 15 not taken.
✓ Branch 10 → 11 taken 1314 times.
✗ Branch 10 → 98 not taken.
✓ Branch 11 → 12 taken 1314 times.
✗ Branch 11 → 98 not taken.
✓ Branch 12 → 13 taken 1314 times.
✗ Branch 12 → 98 not taken.
✓ Branch 13 → 14 taken 8 times.
✓ Branch 13 → 15 taken 1306 times.
✓ Branch 16 → 17 taken 1314 times.
✗ Branch 16 → 18 not taken.
✓ Branch 18 → 19 taken 1314 times.
✗ Branch 18 → 20 not taken.
✓ Branch 20 → 21 taken 8 times.
✓ Branch 20 → 22 taken 1306 times.
✗ Branch 98 → 99 not taken.
✗ Branch 98 → 100 not taken.
✗ Branch 102 → 103 not taken.
✗ Branch 102 → 104 not taken.
1314 if (testDriverCliOptions.isGitHubActions && exists(testCase.testPath / CTL_SKIP_GH))
266 8 return true;
267 // Sanitizer-instrumented binaries cannot run under Valgrind either (the ASan/TSan/MSan/TYSan runtime and Valgrind's
268 // instrumentation both intercept the same allocator hooks), so skip them under --leak-detection for the same reason
269 // --skip-sanitizer-tests does.
270
2/4
✓ Branch 22 → 23 taken 1306 times.
✗ Branch 22 → 24 not taken.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 52 taken 1306 times.
1306 if (testDriverCliOptions.skipSanitizerTests || testDriverCliOptions.enableLeakDetection) {
271 std::vector<std::string> testArgs;
272 parseTestArgs(testCase.testPath / REF_NAME_SOURCE, testArgs);
273 for (const std::string &arg : testArgs)
274 if (arg.starts_with("--sanitizer"))
275 return true;
276 }
277 // Code coverage instrumentation is rejected by the driver in combination with LTO or the TPDE backend, and is not tested
278 // together with the sanitizers, so skip any test that requests one of these via its `// TEST: ` header.
279
2/2
✓ Branch 52 → 53 taken 653 times.
✓ Branch 52 → 90 taken 653 times.
1306 if (testDriverCliOptions.enableCoverage) {
280 653 std::vector<std::string> testArgs;
281
3/6
✓ Branch 53 → 54 taken 653 times.
✗ Branch 53 → 119 not taken.
✓ Branch 54 → 55 taken 653 times.
✗ Branch 54 → 117 not taken.
✓ Branch 55 → 56 taken 653 times.
✗ Branch 55 → 115 not taken.
653 parseTestArgs(testCase.testPath / REF_NAME_SOURCE, testArgs);
282
2/2
✓ Branch 83 → 60 taken 59 times.
✓ Branch 83 → 84 taken 639 times.
1351 for (const std::string &arg : testArgs)
283
12/16
✓ Branch 62 → 63 taken 59 times.
✗ Branch 62 → 121 not taken.
✓ Branch 63 → 64 taken 58 times.
✓ Branch 63 → 70 taken 1 time.
✓ Branch 64 → 65 taken 58 times.
✗ Branch 64 → 121 not taken.
✓ Branch 65 → 66 taken 57 times.
✓ Branch 65 → 70 taken 1 time.
✓ Branch 66 → 67 taken 57 times.
✗ Branch 66 → 121 not taken.
✓ Branch 67 → 68 taken 57 times.
✗ Branch 67 → 70 not taken.
✓ Branch 69 → 70 taken 12 times.
✓ Branch 69 → 71 taken 45 times.
✓ Branch 72 → 73 taken 14 times.
✓ Branch 72 → 74 taken 45 times.
59 if (arg == "-lto" || arg == "--backend=tpde" || arg == "--backend=TPDE" || arg.starts_with("--sanitizer"))
284 14 return true;
285
2/2
✓ Branch 86 → 87 taken 639 times.
✓ Branch 86 → 89 taken 14 times.
653 }
286 #ifndef SPICE_ENABLE_TPDE
287 // TPDE-only tests select the backend via a `// TEST: --backend=tpde` header. Skip them when
288 // the compiler was built without the experimental TPDE support — otherwise Driver::parse
289 // would reject the flag and the test would report a false-positive CLI error.
290 std::vector<std::string> testArgs;
291 parseTestArgs(testCase.testPath / REF_NAME_SOURCE, testArgs);
292 for (const std::string &arg : testArgs)
293 if (arg == "--backend=tpde" || arg == "--backend=TPDE")
294 return true;
295 #endif
296 #ifdef OS_WINDOWS
297 if (exists(testCase.testPath / CTL_SKIP_WINDOWS))
298 return true;
299 #elifdef OS_MACOS
300 if (exists(testCase.testPath / CTL_SKIP_MACOS))
301 return true;
302 #endif
303 1292 return false;
304 }
305
306 // LCOV_EXCL_START
307 /**
308 * Removes the first n lines of the GDB output to not compare target dependent code
309 *
310 * @param gdbOutput GDB output to modify
311 */
312 void TestUtil::eraseGDBHeader(std::string &gdbOutput) {
313 // Remove the header up to and including the "Reading symbols from <path>" line. The executable lives in a per-test artifact
314 // directory, so its path is not stable across runs and must not be part of the comparison.
315 size_t pos = gdbOutput.find(GDB_READING_SYMBOLS_MESSAGE);
316 if (pos != std::string::npos) {
317 if (const size_t lineEnd = gdbOutput.find('\n', pos); lineEnd != std::string::npos)
318 gdbOutput.erase(0, lineEnd + 1);
319 else
320 gdbOutput.clear();
321 }
322
323 // Remove inferior message
324 pos = gdbOutput.find(GDB_INFERIOR_MESSAGE);
325 if (pos != std::string::npos)
326 gdbOutput.erase(pos);
327 }
328 // LCOV_EXCL_STOP
329
330 /**
331 * Remove lines, containing a certain substring to make the IR string comparable
332 *
333 * @param irCode IR code to modify
334 * @param needle Substring to search for
335 */
336 12 void TestUtil::eraseLinesBySubstring(std::string &irCode, const char *const needle) {
337 12 std::string::size_type pos = 0;
338
2/2
✓ Branch 12 → 3 taken 24 times.
✓ Branch 12 → 13 taken 12 times.
36 while ((pos = irCode.find(needle, pos)) != std::string::npos) {
339 // Find the start of the line that contains the substring
340 24 std::string::size_type lineStart = irCode.rfind('\n', pos);
341
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 24 times.
24 if (lineStart == std::string::npos)
342 lineStart = 0;
343 else
344 24 lineStart++; // move past the '\n'
345
346 // Find the end of the line that contains the substring
347 24 std::string::size_type lineEnd = irCode.find('\n', pos);
348
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 24 times.
24 if (lineEnd == std::string::npos)
349 lineEnd = irCode.length();
350
351 // Erase the line
352 24 irCode.erase(lineStart, lineEnd - lineStart);
353 }
354 12 }
355
356 21125 std::array<std::filesystem::path, 3> TestUtil::expandRefPaths(const std::filesystem::path &refPath) {
357
1/2
✓ Branch 2 → 3 taken 21125 times.
✗ Branch 2 → 83 not taken.
21125 const std::filesystem::path parent = refPath.parent_path();
358
2/4
✓ Branch 3 → 4 taken 21125 times.
✗ Branch 3 → 40 not taken.
✓ Branch 4 → 5 taken 21125 times.
✗ Branch 4 → 38 not taken.
21125 const std::string stem = refPath.stem().string();
359
2/4
✓ Branch 6 → 7 taken 21125 times.
✗ Branch 6 → 43 not taken.
✓ Branch 7 → 8 taken 21125 times.
✗ Branch 7 → 41 not taken.
21125 const std::string ext = refPath.extension().string();
360 // Construct array of files to search for
361
3/6
✓ Branch 9 → 10 taken 21125 times.
✗ Branch 9 → 48 not taken.
✓ Branch 10 → 11 taken 21125 times.
✗ Branch 10 → 46 not taken.
✓ Branch 11 → 12 taken 21125 times.
✗ Branch 11 → 44 not taken.
21125 const std::string osFileName = stem + "-" + SPICE_TARGET_OS + ext;
362
5/10
✓ Branch 14 → 15 taken 21125 times.
✗ Branch 14 → 58 not taken.
✓ Branch 15 → 16 taken 21125 times.
✗ Branch 15 → 56 not taken.
✓ Branch 16 → 17 taken 21125 times.
✗ Branch 16 → 54 not taken.
✓ Branch 17 → 18 taken 21125 times.
✗ Branch 17 → 52 not taken.
✓ Branch 18 → 19 taken 21125 times.
✗ Branch 18 → 50 not taken.
21125 const std::string osArchFileName = stem + "-" + SPICE_TARGET_OS + "-" + SPICE_TARGET_ARCH + ext;
363 42250 return {parent / osArchFileName, parent / osFileName, refPath};
364
5/14
✓ Branch 23 → 24 taken 21125 times.
✗ Branch 23 → 67 not taken.
✓ Branch 24 → 25 taken 21125 times.
✗ Branch 24 → 65 not taken.
✓ Branch 25 → 26 taken 21125 times.
✗ Branch 25 → 64 not taken.
✓ Branch 26 → 27 taken 21125 times.
✗ Branch 26 → 62 not taken.
✓ Branch 27 → 28 taken 21125 times.
✗ Branch 27 → 62 not taken.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 72 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
42250 }
365
366 } // namespace spice::testing
367