GCC Code Coverage Report


Directory: ../
File: test/TestRunner.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 <string>
6 #include <vector>
7
8 #include <gtest/gtest.h>
9
10 #include <llvm/TargetParser/Host.h>
11 #include <llvm/TargetParser/Triple.h>
12 #include <SourceFile.h>
13 #include <driver/Driver.h>
14 #include <exception/CompilerError.h>
15 #include <exception/LexerError.h>
16 #include <exception/LinkerError.h>
17 #include <exception/ParserError.h>
18 #include <exception/SemanticError.h>
19 #include <global/GlobalResourceManager.h>
20 #include <global/TypeRegistry.h>
21 #include <symboltablebuilder/SymbolTable.h>
22 #include <util/FileUtil.h>
23
24 #include "driver/Driver.h"
25 #include "util/TestUtil.h"
26
27 using namespace spice::compiler;
28
29 namespace spice::testing {
30
31 extern TestDriverCliOptions testDriverCliOptions;
32
33 void execTestCase(const TestCase &testCase) {
34 // Check if test is disabled
35 if (TestUtil::isDisabled(testCase))
36 GTEST_SKIP();
37
38 // Create fake cli options
39 const std::filesystem::path sourceFilePath = testCase.testPath / REF_NAME_SOURCE;
40 const llvm::Triple targetTriple(llvm::Triple::normalize(llvm::sys::getDefaultTargetTriple()));
41 CliOptions cliOptions = {
42 /* mainSourceFile= */ sourceFilePath,
43 /* targetTriple= */ targetTriple.getTriple(),
44 /* targetArch= */ std::string(targetTriple.getArchName()),
45 /* targetVendor= */ std::string(targetTriple.getVendorName()),
46 /* targetOs= */ std::string(targetTriple.getOSName()),
47 /* isNativeTarget= */ true,
48 /* useCPUFeatures*/ false, // Disabled because it makes the refs differ on different machines
49 /* execute= */ false, // If we set this to 'true', the compiler will not emit object files
50 /* cacheDir= */ "./cache",
51 /* outputDir= */ "./",
52 /* outputPath= */ "",
53 /* buildMode= */ DEBUG,
54 /* compileJobCount= */ 0,
55 /* ignoreCache */ true,
56 /* llvmArgs= */ "",
57 /* printDebugOutput= */ false,
58 CliOptions::DumpSettings{
59 /* dumpCST= */ false,
60 /* dumpAST= */ false,
61 /* dumpSymbolTables= */ false,
62 /* dumpTypes= */ false,
63 /* dumpCacheStats= */ false,
64 /* dumpDependencyGraph= */ false,
65 /* dumpIR= */ false,
66 /* dumpAssembly= */ false,
67 /* dumpObjectFile= */ false,
68 /* dumpToFiles= */ false,
69 /* abortAfterDump */ false,
70 },
71 /* namesForIRValues= */ true,
72 /* useLifetimeMarkers= */ false,
73 /* optLevel= */ O0,
74 /* useLTO= */ exists(testCase.testPath / CTL_LTO),
75 /* noEntryFct= */ exists(testCase.testPath / CTL_RUN_BUILTIN_TESTS),
76 /* generateTestMain= */ exists(testCase.testPath / CTL_RUN_BUILTIN_TESTS),
77 /* staticLinking= */ false,
78 /* debugInfo= */ exists(testCase.testPath / CTL_DEBUG_INFO),
79 /* disableVerifier= */ false,
80 /* testMode= */ true,
81 /* comparableOutput= */ true,
82 };
83 static_assert(sizeof(CliOptions::DumpSettings) == 11, "CliOptions::DumpSettings struct size changed");
84 static_assert(sizeof(CliOptions) == 360, "CliOptions struct size changed");
85
86 // Instantiate GlobalResourceManager
87 GlobalResourceManager resourceManager(cliOptions);
88
89 try {
90 // Create source file instance for main source file
91 SourceFile *mainSourceFile = resourceManager.createSourceFile(nullptr, MAIN_FILE_NAME, cliOptions.mainSourceFile, false);
92
93 // Run Lexer and Parser
94 mainSourceFile->runLexer();
95 mainSourceFile->runParser();
96
97 // Check CST
98 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_PARSE_TREE, [&] {
99 mainSourceFile->runCSTVisualizer();
100 return mainSourceFile->compilerOutput.cstString;
101 });
102
103 // Build and optimize AST
104 mainSourceFile->runASTBuilder();
105
106 // Check AST
107 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_SYNTAX_TREE, [&] {
108 mainSourceFile->runASTVisualizer();
109 return mainSourceFile->compilerOutput.astString;
110 });
111
112 // Execute import collector and semantic analysis stages
113 mainSourceFile->runImportCollector();
114 mainSourceFile->runSymbolTableBuilder();
115 mainSourceFile->runMiddleEnd(); // TypeChecker pre + post
116
117 // Check symbol table output (check happens here to include updates from type checker)
118 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_SYMBOL_TABLE,
119 [&] { return mainSourceFile->globalScope->getSymbolTableJSON().dump(/*indent=*/2); });
120
121 // Fail if an error was expected
122 if (exists(testCase.testPath / REF_NAME_ERROR_OUTPUT))
123 FAIL() << "Expected error, but got no error";
124
125 // Check dependency graph
126 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_DEP_GRAPH, [&] {
127 mainSourceFile->runDependencyGraphVisualizer();
128 return mainSourceFile->compilerOutput.depGraphString;
129 });
130
131 // Run backend for all dependencies
132 for (SourceFile *sourceFile : mainSourceFile->dependencies | std::views::values)
133 sourceFile->runBackEnd();
134
135 // Execute IR generator in normal or debug mode
136 mainSourceFile->runIRGenerator();
137
138 // Check unoptimized IR code
139 TestUtil::checkRefMatch(
140 testCase.testPath / REF_NAME_IR, [&] { return mainSourceFile->compilerOutput.irString; },
141 [&](std::string &expectedOutput, std::string &actualOutput) {
142 if (cliOptions.generateDebugInfo) {
143 // Remove the lines, containing paths on the local file system
144 TestUtil::eraseLinesBySubstring(expectedOutput, " = !DIFile(filename:");
145 TestUtil::eraseLinesBySubstring(actualOutput, " = !DIFile(filename:");
146 }
147 });
148
149 // Check optimized IR code
150 for (uint8_t i = 1; i <= 5; i++) {
151 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_OPT_IR[i - 1], [&] {
152 cliOptions.optLevel = static_cast<OptLevel>(i);
153
154 if (cliOptions.useLTO) {
155 mainSourceFile->runPreLinkIROptimizer();
156 mainSourceFile->runBitcodeLinker();
157 mainSourceFile->runPostLinkIROptimizer();
158 } else {
159 mainSourceFile->runDefaultIROptimizer();
160 }
161
162 return mainSourceFile->compilerOutput.irOptString;
163 });
164 }
165
166 // Link the bitcode if not happened yet
167 if (cliOptions.useLTO && cliOptions.optLevel == O0)
168 mainSourceFile->runBitcodeLinker();
169
170 // Check assembly code (only when not running test on GitHub Actions)
171 bool objectFilesEmitted = false;
172 if (!testDriverCliOptions.isGitHubActions) {
173 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_ASM, [&] {
174 mainSourceFile->runObjectEmitter();
175 objectFilesEmitted = true;
176
177 return mainSourceFile->compilerOutput.asmString;
178 });
179 }
180
181 // Check warnings
182 mainSourceFile->collectAndPrintWarnings();
183 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_WARNING_OUTPUT, [&] {
184 std::stringstream actualWarningString;
185 for (const CompilerWarning &warning : mainSourceFile->compilerOutput.warnings)
186 actualWarningString << warning.warningMessage << "\n";
187 return actualWarningString.str();
188 });
189
190 // Do linking and conclude compilation
191 const bool needsNormalRun = TestUtil::doesRefExist(testCase.testPath / REF_NAME_EXECUTION_OUTPUT);
192 const bool needsDebuggerRun = TestUtil::doesRefExist(testCase.testPath / REF_NAME_GDB_OUTPUT);
193 if (needsNormalRun || needsDebuggerRun) {
194 // Prepare linker
195 resourceManager.linker.outputPath = TestUtil::getDefaultExecutableName();
196
197 // Parse linker flags
198 const std::filesystem::path linkerFlagsFile = testCase.testPath / INPUT_NAME_LINKER_FLAGS;
199 if (exists(linkerFlagsFile))
200 for (const std::string &linkerFlag : TestUtil::getFileContentLinesVector(linkerFlagsFile))
201 resourceManager.linker.addLinkerFlag(linkerFlag);
202
203 // Emit main source file object if not done already
204 if (!objectFilesEmitted)
205 mainSourceFile->runObjectEmitter();
206
207 // Conclude the compilation
208 mainSourceFile->concludeCompilation();
209
210 // Prepare and run linker
211 resourceManager.linker.prepare();
212 resourceManager.linker.link();
213 }
214
215 // Check type registry output
216 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_TYPE_REGISTRY, [&] { return TypeRegistry::dump(); });
217
218 // Check cache stats output
219 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_CACHE_STATS, [&] {
220 std::stringstream cacheStats;
221 cacheStats << FunctionManager::dumpLookupCacheStatistics() << std::endl;
222 cacheStats << StructManager::dumpLookupCacheStatistics() << std::endl;
223 cacheStats << InterfaceManager::dumpLookupCacheStatistics() << std::endl;
224 return cacheStats.str();
225 });
226
227 // Check if the execution output matches the expected output
228 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_EXECUTION_OUTPUT, [&] {
229 const std::filesystem::path cliFlagsFile = testCase.testPath / INPUT_NAME_CLI_FLAGS;
230 // Execute binary
231 std::stringstream cmd;
232 if (testDriverCliOptions.enableLeakDetection)
233 cmd << "valgrind -q --leak-check=full --num-callers=100 --error-exitcode=1 ";
234 cmd << TestUtil::getDefaultExecutableName();
235 if (exists(cliFlagsFile))
236 cmd << " " << TestUtil::getFileContentLinesVector(cliFlagsFile).at(0);
237 const auto [output, exitCode] = FileUtil::exec(cmd.str(), true);
238
239 #if not OS_WINDOWS // Windows does not give us the exit code, so we cannot check it on Windows
240 // Check if the exit code matches the expected one
241 // If no exit code ref file exists, check against 0
242 if (TestUtil::checkRefMatch(testCase.testPath / REF_NAME_EXIT_CODE, [&] { return std::to_string(exitCode); })) {
243 EXPECT_NE(0, exitCode) << "Program exited with zero exit code, but expected erronous exit code";
244 } else {
245 EXPECT_EQ(0, exitCode) << "Program exited with non-zero exit code";
246 }
247 #endif
248
249 return output;
250 });
251
252 // Check if the debugger output matches the expected output
253 if (!testDriverCliOptions.isGitHubActions) { // GDB tests are currently not support on GH actions
254 TestUtil::checkRefMatch(
255 testCase.testPath / REF_NAME_GDB_OUTPUT,
256 [&] {
257 // Execute debugger script
258 std::filesystem::path gdbScriptPath = testCase.testPath / CTL_DEBUG_SCRIPT;
259 EXPECT_TRUE(std::filesystem::exists(gdbScriptPath)) << "Debug output requested, but debug script not found";
260 gdbScriptPath.make_preferred();
261 const std::string cmd = "gdb -x " + gdbScriptPath.string() + " " + TestUtil::getDefaultExecutableName();
262 const auto [output, exitCode] = FileUtil::exec(cmd);
263
264 #if not OS_WINDOWS // Windows does not give us the exit code, so we cannot check it on Windows
265 EXPECT_EQ(0, exitCode) << "GDB exited with non-zero exit code when running debug script";
266 #endif
267
268 return output;
269 },
270 [&](std::string &expectedOutput, std::string &actualOutput) {
271 // Do not compare against the GDB header
272 TestUtil::eraseGDBHeader(expectedOutput);
273 TestUtil::eraseGDBHeader(actualOutput);
274 });
275 }
276 } catch (LexerError &error) {
277 TestUtil::handleError(testCase, error);
278 } catch (ParserError &error) {
279 TestUtil::handleError(testCase, error);
280 } catch (SemanticError &error) {
281 TestUtil::handleError(testCase, error);
282 } catch (CompilerError &error) {
283 TestUtil::handleError(testCase, error);
284 } catch (LinkerError &error) {
285 TestUtil::handleError(testCase, error);
286 } catch (std::exception &error) {
287 TestUtil::handleError(testCase, error);
288 }
289
290 SUCCEED();
291 }
292
293 class CommonTests : public ::testing::TestWithParam<TestCase> {};
294 TEST_P(CommonTests, ) { execTestCase(GetParam()); }
295 INSTANTIATE_TEST_SUITE_P(, CommonTests, ::testing::ValuesIn(TestUtil::collectTestCases("common", false)),
296 TestUtil::NameResolver());
297
298 class LexerTests : public ::testing::TestWithParam<TestCase> {};
299 TEST_P(LexerTests, ) { execTestCase(GetParam()); }
300 INSTANTIATE_TEST_SUITE_P(, LexerTests, ::testing::ValuesIn(TestUtil::collectTestCases("lexer", false)), TestUtil::NameResolver());
301
302 class ParserTests : public ::testing::TestWithParam<TestCase> {};
303 TEST_P(ParserTests, ) { execTestCase(GetParam()); }
304 INSTANTIATE_TEST_SUITE_P(, ParserTests, ::testing::ValuesIn(TestUtil::collectTestCases("parser", false)),
305 TestUtil::NameResolver());
306
307 class SymbolTableBuilderTests : public ::testing::TestWithParam<TestCase> {};
308 TEST_P(SymbolTableBuilderTests, ) { execTestCase(GetParam()); }
309 INSTANTIATE_TEST_SUITE_P(, SymbolTableBuilderTests, ::testing::ValuesIn(TestUtil::collectTestCases("symboltablebuilder", true)),
310 TestUtil::NameResolver());
311
312 class TypeCheckerTests : public ::testing::TestWithParam<TestCase> {};
313 TEST_P(TypeCheckerTests, ) { execTestCase(GetParam()); }
314 INSTANTIATE_TEST_SUITE_P(, TypeCheckerTests, ::testing::ValuesIn(TestUtil::collectTestCases("typechecker", true)),
315 TestUtil::NameResolver());
316
317 class IRGeneratorTests : public ::testing::TestWithParam<TestCase> {};
318 TEST_P(IRGeneratorTests, ) { execTestCase(GetParam()); }
319 INSTANTIATE_TEST_SUITE_P(, IRGeneratorTests, ::testing::ValuesIn(TestUtil::collectTestCases("irgenerator", true)),
320 TestUtil::NameResolver());
321
322 class StdTests : public ::testing::TestWithParam<TestCase> {};
323 TEST_P(StdTests, ) { execTestCase(GetParam()); }
324 INSTANTIATE_TEST_SUITE_P(, StdTests, ::testing::ValuesIn(TestUtil::collectTestCases("std", true)), TestUtil::NameResolver());
325
326 class BenchmarkTests : public ::testing::TestWithParam<TestCase> {};
327 TEST_P(BenchmarkTests, ) { execTestCase(GetParam()); }
328 INSTANTIATE_TEST_SUITE_P(, BenchmarkTests, ::testing::ValuesIn(TestUtil::collectTestCases("benchmark", false)),
329 TestUtil::NameResolver());
330
331 class ExampleTests : public ::testing::TestWithParam<TestCase> {};
332 TEST_P(ExampleTests, ) { execTestCase(GetParam()); }
333 INSTANTIATE_TEST_SUITE_P(, ExampleTests, ::testing::ValuesIn(TestUtil::collectTestCases("examples", false)),
334 TestUtil::NameResolver());
335
336 class BootstrapCompilerTests : public ::testing::TestWithParam<TestCase> {};
337 TEST_P(BootstrapCompilerTests, ) { execTestCase(GetParam()); }
338 INSTANTIATE_TEST_SUITE_P(, BootstrapCompilerTests, ::testing::ValuesIn(TestUtil::collectTestCases("bootstrap-compiler", false)),
339 TestUtil::NameResolver());
340
341 } // namespace spice::testing
342
343 // GCOV_EXCL_STOP
344