GCC Code Coverage Report


Directory: ../
File: test/util/TestUtil.cpp
Date: 2025-03-05 01:50:32
Exec Total Coverage
Lines: 0 0 100.0%
Functions: 0 0 -%
Branches: 0 0 -%

Line Branch Exec Source
1 // Copyright (c) 2021-2025 ChilliBits. All rights reserved.
2
3 // GCOV_EXCL_START
4
5 #include "TestUtil.h"
6
7 #include <dirent.h>
8 #ifdef OS_UNIX
9 #include <cstring> // Required by builds on Linux
10 #endif
11
12 #include <gtest/gtest.h>
13
14 #include <util/CommonUtil.h>
15 #include <util/FileUtil.h>
16
17 #include "../driver/Driver.h"
18
19 namespace spice::testing {
20
21 using namespace spice::compiler;
22
23 extern TestDriverCliOptions testDriverCliOptions;
24
25 /**
26 * Collect the test cases in a particular test suite
27 *
28 * @param suiteName Name of the test suite
29 * @param useSubDirs Use subdirectories as test cases
30 * @return Vector of tests cases
31 */
32 std::vector<TestCase> TestUtil::collectTestCases(const char *suiteName, bool useSubDirs) {
33 const std::filesystem::path suitePath = std::filesystem::path(PATH_TEST_FILES) / suiteName;
34
35 std::vector<TestCase> testCases;
36 testCases.reserve(EXPECTED_NUMBER_OF_TESTS);
37
38 if (useSubDirs) {
39 // Collect subdirectories of the given suite
40 const std::vector<std::string> testGroupDirs = getSubdirs(suitePath);
41
42 // Convert them to test cases
43 for (const std::string &groupDirName : testGroupDirs) {
44 const std::filesystem::path groupPath = suitePath / groupDirName;
45 for (const std::string &caseDirName : getSubdirs(groupPath)) {
46 const std::filesystem::path testPath = groupPath / caseDirName;
47 const TestCase tc = {toCamelCase(groupDirName), toCamelCase(caseDirName), testPath};
48 testCases.push_back(tc);
49 }
50 }
51 } else {
52 // Collect test cases
53 for (const std::string &caseDirName : getSubdirs(suitePath)) {
54 const std::filesystem::path testPath = suitePath / caseDirName;
55 const TestCase tc = {toCamelCase(suiteName), toCamelCase(caseDirName), testPath};
56 testCases.push_back(tc);
57 }
58 }
59
60 return testCases;
61 }
62
63 /**
64 * Check if the expected output matches the actual output
65 *
66 * @param originalRefPath Path to the reference file
67 * @param getActualOutput Callback to execute the required steps to get the actual test output
68 * @param modifyOutputFct Callback to modify the output before comparing it with the reference
69 *
70 * @return True, if the ref file was found
71 */
72 bool TestUtil::checkRefMatch(const std::filesystem::path &originalRefPath, GetOutputFct getActualOutput,
73 ModifyOutputFct modifyOutputFct) {
74 for (const std::filesystem::path &refPath : expandRefPaths(originalRefPath)) {
75 if (testDriverCliOptions.isVerbose)
76 std::cout << "Checking for ref file: " << refPath << " - ";
77 if (!exists(refPath)) {
78 if (testDriverCliOptions.isVerbose)
79 std::cout << "not found" << std::endl;
80 continue;
81 }
82 if (testDriverCliOptions.isVerbose)
83 std::cout << "ok" << std::endl;
84
85 // Get actual output
86 std::string actualOutput = getActualOutput();
87 if (testDriverCliOptions.updateRefs) { // Update refs
88 FileUtil::writeToFile(refPath, actualOutput);
89 } else { // Check refs
90 std::string expectedOutput = FileUtil::getFileContent(refPath);
91 modifyOutputFct(expectedOutput, actualOutput);
92 EXPECT_EQ(expectedOutput, actualOutput) << "Output does not match the reference file: " << refPath;
93 }
94 return true;
95 }
96 return false;
97 }
98
99 /**
100 * Check if a variant of the requested ref file was found
101 *
102 * @param originalRefPath Path to the reference file
103 * @return True, if the ref file was found
104 */
105 bool TestUtil::doesRefExist(const std::filesystem::path &originalRefPath) {
106 const std::array<std::filesystem::path, 3> refPaths = expandRefPaths(originalRefPath);
107 return std::ranges::any_of(refPaths, [](const std::filesystem::path &refPath) { return exists(refPath); });
108 }
109
110 /**
111 * Handle a test error
112 *
113 * @param testCase Testcase which has produced the error
114 * @param error Exception with error message
115 */
116 void TestUtil::handleError(const TestCase &testCase, const std::exception &error) {
117 std::string errorWhat = error.what();
118 CommonUtil::replaceAll(errorWhat, "\\", "/");
119
120 // Fail if no ref file exists
121 const std::filesystem::path refPath = testCase.testPath / REF_NAME_ERROR_OUTPUT;
122 if (!exists(refPath))
123 FAIL() << "Expected no error, but got: " + errorWhat;
124
125 // Check if the exception message matches the expected output
126 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_ERROR_OUTPUT, [&] { return errorWhat; });
127 }
128
129 /**
130 * Get subdirectories of the given path
131 *
132 * @param basePath Path to a directory
133 * @return Vector of subdirs
134 */
135 std::vector<std::string> TestUtil::getSubdirs(const std::filesystem::path &basePath) {
136 std::vector<std::string> subdirs;
137 if (DIR *dir = opendir(basePath.string().c_str()); dir != nullptr) {
138 dirent *ent;
139 while ((ent = readdir(dir)) != nullptr) {
140 if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0)
141 subdirs.emplace_back(ent->d_name);
142 }
143 closedir(dir);
144 }
145 return subdirs;
146 }
147
148 /**
149 * Retrieve the contents of a file as a vector of line strings. Empty lines are omitted
150 *
151 * @param filePath File path
152 * @return Vector of strings which are the lines of the file
153 */
154 std::vector<std::string> TestUtil::getFileContentLinesVector(const std::filesystem::path &filePath) {
155 std::vector<std::string> lines;
156 std::ifstream inputFileStream;
157 inputFileStream.open(filePath);
158 for (std::string line; std::getline(inputFileStream, line);) {
159 if (!line.empty())
160 lines.push_back(line);
161 }
162 return lines;
163 }
164
165 /**
166 * Convert a string to camel case
167 *
168 * @param input Input string
169 * @return Camel-cased string
170 */
171 std::string TestUtil::toCamelCase(std::string input) {
172 for (auto it = input.begin(); it != input.end(); ++it) {
173 if (*it == '-' || *it == '_') {
174 it = input.erase(it);
175 *it = static_cast<char>(toupper(*it));
176 }
177 }
178 return input;
179 }
180
181 /**
182 * Check if the provided test case is disabled
183 *
184 * @param testCase Test case to check
185 * @return Disabled or not
186 */
187 bool TestUtil::isDisabled(const TestCase &testCase) {
188 if (exists(testCase.testPath / CTL_SKIP_DISABLED))
189 return true;
190 if (testDriverCliOptions.isGitHubActions && exists(testCase.testPath / CTL_SKIP_GH))
191 return true;
192 return false;
193 }
194
195 /**
196 * Removes the first n lines of the GDB output to not compare target dependent code
197 *
198 * @param gdbOutput GDB output to modify
199 */
200 void TestUtil::eraseGDBHeader(std::string &gdbOutput) {
201 // Remove header
202 size_t pos = gdbOutput.find(GDB_READING_SYMBOLS_MESSAGE);
203 if (pos != std::string::npos) {
204 if (const size_t lineStart = gdbOutput.rfind('\n', pos); lineStart != std::string::npos)
205 gdbOutput.erase(0, lineStart + 1);
206 }
207
208 // Remove inferior message
209 pos = gdbOutput.find(GDB_INFERIOR_MESSAGE);
210 if (pos != std::string::npos)
211 gdbOutput.erase(pos);
212 }
213
214 /**
215 * Remove lines, containing a certain substring to make the IR string comparable
216 *
217 * @param irCode IR code to modify
218 * @param needle Substring to search for
219 */
220 void TestUtil::eraseLinesBySubstring(std::string &irCode, const char *const needle) {
221 std::string::size_type pos = 0;
222 while ((pos = irCode.find(needle, pos)) != std::string::npos) {
223 // Find the start of the line that contains the substring
224 std::string::size_type lineStart = irCode.rfind('\n', pos);
225 if (lineStart == std::string::npos)
226 lineStart = 0;
227 else
228 lineStart++; // move past the '\n'
229
230 // Find the end of the line that contains the substring
231 std::string::size_type lineEnd = irCode.find('\n', pos);
232 if (lineEnd == std::string::npos)
233 lineEnd = irCode.length();
234
235 // Erase the line
236 irCode.erase(lineStart, lineEnd - lineStart);
237 }
238 }
239
240 std::array<std::filesystem::path, 3> TestUtil::expandRefPaths(const std::filesystem::path &refPath) {
241 const std::filesystem::path parent = refPath.parent_path();
242 const std::string stem = refPath.stem().string();
243 const std::string ext = refPath.extension().string();
244 // Construct array of files to search for
245 const std::string osFileName = stem + "-" + SPICE_TARGET_OS + ext;
246 const std::string osArchFileName = stem + "-" + SPICE_TARGET_OS + "-" + SPICE_TARGET_ARCH + ext;
247 return {parent / osArchFileName, parent / osFileName, refPath};
248 }
249
250 } // namespace spice::testing
251
252 // GCOV_EXCL_STOP
253