GCC Code Coverage Report


Directory: ../
File: test/util/TestUtil.cpp
Date: 2024-11-22 23:10:59
Exec Total Coverage
Lines: 0 0 100.0%
Functions: 0 0 -%
Branches: 0 0 -%

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