GCC Code Coverage Report


Directory: ../
File: test/TestRunner.cpp
Date: 2025-08-26 18:26: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,
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) == 384, "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 #ifdef ARCH_X86_64
151 for (uint8_t i = 1; i <= 5; i++) {
152 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_OPT_IR[i - 1], [&] {
153 cliOptions.optLevel = static_cast<OptLevel>(i);
154
155 if (cliOptions.useLTO) {
156 mainSourceFile->runPreLinkIROptimizer();
157 mainSourceFile->runBitcodeLinker();
158 mainSourceFile->runPostLinkIROptimizer();
159 } else {
160 mainSourceFile->runDefaultIROptimizer();
161 }
162
163 return mainSourceFile->compilerOutput.irOptString;
164 });
165 }
166 #endif
167
168 // Link the bitcode if not happened yet
169 if (cliOptions.useLTO && cliOptions.optLevel == O0)
170 mainSourceFile->runBitcodeLinker();
171
172 // Check assembly code (only when not running test on GitHub Actions)
173 bool objectFilesEmitted = false;
174 if (!testDriverCliOptions.isGitHubActions) {
175 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_ASM, [&] {
176 mainSourceFile->runObjectEmitter();
177 objectFilesEmitted = true;
178
179 return mainSourceFile->compilerOutput.asmString;
180 });
181 }
182
183 // Check warnings
184 mainSourceFile->collectAndPrintWarnings();
185 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_WARNING_OUTPUT, [&] {
186 std::stringstream actualWarningString;
187 for (const CompilerWarning &warning : mainSourceFile->compilerOutput.warnings)
188 actualWarningString << warning.warningMessage << "\n";
189 return actualWarningString.str();
190 });
191
192 // Do linking and conclude compilation
193 const bool needsNormalRun = TestUtil::doesRefExist(testCase.testPath / REF_NAME_EXECUTION_OUTPUT);
194 const bool needsDebuggerRun = TestUtil::doesRefExist(testCase.testPath / REF_NAME_GDB_OUTPUT);
195 if (needsNormalRun || needsDebuggerRun) {
196 // Prepare linker
197 resourceManager.linker.outputPath = TestUtil::getDefaultExecutableName();
198
199 // Parse linker flags
200 const std::filesystem::path linkerFlagsFile = testCase.testPath / INPUT_NAME_LINKER_FLAGS;
201 if (exists(linkerFlagsFile))
202 for (const std::string &linkerFlag : TestUtil::getFileContentLinesVector(linkerFlagsFile))
203 resourceManager.linker.addLinkerFlag(linkerFlag);
204
205 // Emit main source file object if not done already
206 if (!objectFilesEmitted)
207 mainSourceFile->runObjectEmitter();
208
209 // Conclude the compilation
210 mainSourceFile->concludeCompilation();
211
212 // Prepare and run linker
213 resourceManager.linker.prepare();
214 resourceManager.linker.link();
215 }
216
217 // Check type registry output
218 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_TYPE_REGISTRY, [&] { return TypeRegistry::dump(); });
219
220 // Check cache stats output
221 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_CACHE_STATS, [&] {
222 std::stringstream cacheStats;
223 cacheStats << FunctionManager::dumpLookupCacheStatistics() << std::endl;
224 cacheStats << StructManager::dumpLookupCacheStatistics() << std::endl;
225 cacheStats << InterfaceManager::dumpLookupCacheStatistics() << std::endl;
226 return cacheStats.str();
227 });
228
229 // Check if the execution output matches the expected output
230 TestUtil::checkRefMatch(testCase.testPath / REF_NAME_EXECUTION_OUTPUT, [&] {
231 const std::filesystem::path cliFlagsFile = testCase.testPath / INPUT_NAME_CLI_FLAGS;
232 // Execute binary
233 std::stringstream cmd;
234 if (testDriverCliOptions.enableLeakDetection)
235 cmd << "valgrind -q --leak-check=full --num-callers=100 --error-exitcode=1 ";
236 cmd << TestUtil::getDefaultExecutableName();
237 if (exists(cliFlagsFile))
238 cmd << " " << TestUtil::getFileContentLinesVector(cliFlagsFile).at(0);
239 const auto [output, exitCode] = FileUtil::exec(cmd.str(), true);
240
241 #if not OS_WINDOWS // Windows does not give us the exit code, so we cannot check it on Windows
242 // Check if the exit code matches the expected one
243 // If no exit code ref file exists, check against 0
244 if (TestUtil::checkRefMatch(testCase.testPath / REF_NAME_EXIT_CODE, [&] { return std::to_string(exitCode); })) {
245 EXPECT_NE(0, exitCode) << "Program exited with zero exit code, but expected erronous exit code";
246 } else {
247 EXPECT_EQ(0, exitCode) << "Program exited with non-zero exit code";
248 }
249 #endif
250
251 return output;
252 });
253
254 // Check if the debugger output matches the expected output
255 if (!testDriverCliOptions.isGitHubActions) { // GDB tests are currently not support on GH actions
256 TestUtil::checkRefMatch(
257 testCase.testPath / REF_NAME_GDB_OUTPUT,
258 [&] {
259 // Execute debugger script
260 std::filesystem::path gdbScriptPath = testCase.testPath / CTL_DEBUG_SCRIPT;
261 EXPECT_TRUE(std::filesystem::exists(gdbScriptPath)) << "Debug output requested, but debug script not found";
262 gdbScriptPath.make_preferred();
263 const std::string cmd = "gdb -x " + gdbScriptPath.string() + " " + TestUtil::getDefaultExecutableName();
264 const auto [output, exitCode] = FileUtil::exec(cmd);
265
266 #if not OS_WINDOWS // Windows does not give us the exit code, so we cannot check it on Windows
267 EXPECT_EQ(0, exitCode) << "GDB exited with non-zero exit code when running debug script";
268 #endif
269
270 return output;
271 },
272 [&](std::string &expectedOutput, std::string &actualOutput) {
273 // Do not compare against the GDB header
274 TestUtil::eraseGDBHeader(expectedOutput);
275 TestUtil::eraseGDBHeader(actualOutput);
276 });
277 }
278 } catch (LexerError &error) {
279 TestUtil::handleError(testCase, error);
280 } catch (ParserError &error) {
281 TestUtil::handleError(testCase, error);
282 } catch (SemanticError &error) {
283 TestUtil::handleError(testCase, error);
284 } catch (CompilerError &error) {
285 TestUtil::handleError(testCase, error);
286 } catch (LinkerError &error) {
287 TestUtil::handleError(testCase, error);
288 } catch (std::exception &error) {
289 TestUtil::handleError(testCase, error);
290 }
291
292 SUCCEED();
293 }
294
295 class CommonTests : public ::testing::TestWithParam<TestCase> {};
296 TEST_P(CommonTests, ) { execTestCase(GetParam()); }
297 INSTANTIATE_TEST_SUITE_P(, CommonTests, ::testing::ValuesIn(TestUtil::collectTestCases("common", false)),
298 TestUtil::NameResolver());
299
300 class LexerTests : public ::testing::TestWithParam<TestCase> {};
301 TEST_P(LexerTests, ) { execTestCase(GetParam()); }
302 INSTANTIATE_TEST_SUITE_P(, LexerTests, ::testing::ValuesIn(TestUtil::collectTestCases("lexer", false)), TestUtil::NameResolver());
303
304 class ParserTests : public ::testing::TestWithParam<TestCase> {};
305 TEST_P(ParserTests, ) { execTestCase(GetParam()); }
306 INSTANTIATE_TEST_SUITE_P(, ParserTests, ::testing::ValuesIn(TestUtil::collectTestCases("parser", false)),
307 TestUtil::NameResolver());
308
309 class SymbolTableBuilderTests : public ::testing::TestWithParam<TestCase> {};
310 TEST_P(SymbolTableBuilderTests, ) { execTestCase(GetParam()); }
311 INSTANTIATE_TEST_SUITE_P(, SymbolTableBuilderTests, ::testing::ValuesIn(TestUtil::collectTestCases("symboltablebuilder", true)),
312 TestUtil::NameResolver());
313
314 class TypeCheckerTests : public ::testing::TestWithParam<TestCase> {};
315 TEST_P(TypeCheckerTests, ) { execTestCase(GetParam()); }
316 INSTANTIATE_TEST_SUITE_P(, TypeCheckerTests, ::testing::ValuesIn(TestUtil::collectTestCases("typechecker", true)),
317 TestUtil::NameResolver());
318
319 class IRGeneratorTests : public ::testing::TestWithParam<TestCase> {};
320 TEST_P(IRGeneratorTests, ) { execTestCase(GetParam()); }
321 INSTANTIATE_TEST_SUITE_P(, IRGeneratorTests, ::testing::ValuesIn(TestUtil::collectTestCases("irgenerator", true)),
322 TestUtil::NameResolver());
323
324 class StdTests : public ::testing::TestWithParam<TestCase> {};
325 TEST_P(StdTests, ) { execTestCase(GetParam()); }
326 INSTANTIATE_TEST_SUITE_P(, StdTests, ::testing::ValuesIn(TestUtil::collectTestCases("std", true)), TestUtil::NameResolver());
327
328 class BenchmarkTests : public ::testing::TestWithParam<TestCase> {};
329 TEST_P(BenchmarkTests, ) { execTestCase(GetParam()); }
330 INSTANTIATE_TEST_SUITE_P(, BenchmarkTests, ::testing::ValuesIn(TestUtil::collectTestCases("benchmark", false)),
331 TestUtil::NameResolver());
332
333 class ExampleTests : public ::testing::TestWithParam<TestCase> {};
334 TEST_P(ExampleTests, ) { execTestCase(GetParam()); }
335 INSTANTIATE_TEST_SUITE_P(, ExampleTests, ::testing::ValuesIn(TestUtil::collectTestCases("examples", false)),
336 TestUtil::NameResolver());
337
338 class BootstrapCompilerTests : public ::testing::TestWithParam<TestCase> {};
339 TEST_P(BootstrapCompilerTests, ) { execTestCase(GetParam()); }
340 INSTANTIATE_TEST_SUITE_P(, BootstrapCompilerTests, ::testing::ValuesIn(TestUtil::collectTestCases("bootstrap-compiler", false)),
341 TestUtil::NameResolver());
342
343 } // namespace spice::testing
344
345 // GCOV_EXCL_STOP
346