1 /// Copyright: Copyright (c) 2017-2019 Andrey Penechko.
2 /// License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
3 /// Authors: Andrey Penechko.
4 module tester;
5 
6 import vox.all;
7 import tests.aggregates;
8 import tests.ctfe;
9 import tests.passing;
10 import tests.failing;
11 import tests.exe;
12 import tests.reg_alloc;
13 public import vox.all : HostSymbol, Slice, SliceString, TargetOs;
14 
15 public import std.format : formattedWrite, format;
16 import std.string : stripLeft, strip;
17 
18 // test entry point when compiling with -unittest or dub test
19 unittest
20 {
21 	import tester;
22 	import tests.amd64asm;
23 
24 	int numFailedTests = 0;
25 	numFailedTests += runAllTests(StopOnFirstFail.no);
26 	numFailedTests += testAmd64Asm();
27 
28 	assert(numFailedTests == 0);
29 }
30 
31 int runDevTests()
32 {
33 	Test test = makeTest!(test227_1);
34 
35 	Driver driver;
36 	driver.initialize(jitPasses);
37 	driver.context.buildType = BuildType.jit;
38 	driver.context.validateIr = true;
39 	driver.context.printTraceOnError = true;
40 	driver.context.printTodos = true;
41 	//driver.context.disableDCE = true;
42 	//driver.context.runTesters = false;
43 	//driver.context.debugRegAlloc = true;
44 	//driver.context.buildType = BuildType.exe;
45 	//driver.passes = exePasses;
46 	//driver.context.windowsSubsystem = WindowsSubsystem.WINDOWS_GUI;
47 
48 	//test.targetOs = TargetOs.linux;
49 	//test.printFilter = "i32_to_str";
50 
51 	scope(exit) {
52 		//driver.context.printMemSize;
53 		driver.releaseMemory;
54 	}
55 
56 	FuncDumpSettings dumpSettings;
57 	dumpSettings.printBlockFlags = true;
58 	//dumpSettings.printBlockOuts = true;
59 
60 	//driver.context.printSource = true;
61 	//driver.context.printLexemes = true;
62 	//driver.context.printAstFresh = true;
63 	//driver.context.printAstSema = true;
64 	//driver.context.printIr = true;
65 	//driver.context.printIrOptEach = true;
66 	//driver.context.printIrOpt = true;
67 	//driver.context.printIrLowerEach = true;
68 	//driver.context.printIrLower = true;
69 	//driver.context.printLir = true;
70 	//driver.context.printLiveIntervals = true;
71 	//driver.context.printLirRA = true;
72 	//driver.context.printStackLayout = true;
73 	//driver.context.printStaticData = true;
74 	//driver.context.printCodeHex = true;
75 	//driver.context.printSymbols = true;
76 	//driver.context.printTimings = true;
77 
78 	TestResult res = tryRunSingleTest(driver, dumpSettings, DumpTest.yes, test);
79 	//writefln("%s", driver.context.numCtfeRuns);
80 
81 	return cast(int)(res == TestResult.failure);
82 }
83 
84 enum StopOnFirstFail : bool { no = false, yes = true }
85 
86 // Returns number of failed tests
87 int runAllTests(StopOnFirstFail stopOnFirstFail)
88 {
89 	auto startInitTime = currTime;
90 	Driver driver;
91 	driver.initialize(jitPasses);
92 	driver.context.buildType = BuildType.jit;
93 	driver.context.buildDebug = false;
94 	driver.context.validateIr = true;
95 	//driver.context.debugRegAlloc = true;
96 	//driver.context.printIr = true;
97 
98 	//driver.context.printAstSema = true;
99 	// Is slow when doing failing tests
100 	//driver.context.printTraceOnError = true;
101 	auto endInitTime = currTime;
102 
103 	FuncDumpSettings dumpSettings;
104 	dumpSettings.printBlockFlags = true;
105 
106 	Test[] jitTests =
107 		tests.passing.passingTests ~
108 		tests.aggregates.aggregatesTests ~
109 		tests.ctfe.ctfeTests ~
110 		tests.failing.failingTests;
111 	Test[] regAllocTests = tests.reg_alloc.regAllocTests;
112 	Test[] exeTests = tests.exe.exeTests;
113 
114 	size_t numTests = jitTests.length + regAllocTests.length;
115 	version(Windows) numTests += exeTests.length;
116 	size_t numSuccessfulTests;
117 	writefln("Running %s tests", numTests);
118 
119 	size_t indexOffset = 0;
120 	bool failed = false;
121 
122 	void runTests(Test[] tests)
123 	{
124 		foreach(size_t i, ref Test test; tests)
125 		{
126 			if (stopOnFirstFail && failed) break;
127 
128 			TestResult res = tryRunSingleTest(driver, dumpSettings, DumpTest.no, test);
129 
130 			if (res == TestResult.failure)
131 			{
132 				writefln("%s/%s test `%s` %s", indexOffset+i+1, numTests, test.testName, res);
133 				failed = true;
134 				if (stopOnFirstFail) writeln("Stopping on first fail");
135 				writeln;
136 			}
137 			else
138 			{
139 				//writefln("%s `%s` success", indexOffset+i+1, test.testName);
140 				++numSuccessfulTests;
141 			}
142 		}
143 		indexOffset += tests.length;
144 	}
145 
146 	auto time1 = currTime;
147 
148 	runTests(jitTests);
149 	auto time2 = currTime;
150 
151 	driver.context.debugRegAlloc = true;
152 	runTests(regAllocTests);
153 	driver.context.debugRegAlloc = false;
154 	auto time3 = currTime;
155 
156 	driver.context.buildType = BuildType.exe;
157 	driver.passes = exePasses;
158 	version(Windows) runTests(exeTests);
159 	auto time4 = currTime;
160 
161 	Duration durationJit = time2-time1;
162 	Duration durationRA = time3-time2;
163 	Duration durationExe = time4-time3;
164 	Duration durationTotal = time4-time1;
165 
166 	auto startReleaseTime = currTime;
167 	driver.releaseMemory;
168 	auto endReleaseTime = currTime;
169 
170 	writefln("jit(%s tests) %ss",
171 		jitTests.length,
172 		scaledNumberFmt(durationJit),
173 		);
174 	writefln("RA(%s tests) %ss",
175 		regAllocTests.length,
176 		scaledNumberFmt(durationRA),
177 		);
178 	writefln("exe(%s tests) %ss",
179 		exeTests.length,
180 		scaledNumberFmt(durationExe),
181 		);
182 	writefln("Done %s/%s successful in %ss, init %ss, release %ss",
183 		numSuccessfulTests,
184 		numTests,
185 		scaledNumberFmt(durationTotal),
186 		scaledNumberFmt(endInitTime-startInitTime),
187 		scaledNumberFmt(endReleaseTime-startReleaseTime),
188 		);
189 
190 	return cast(int)(numTests - numSuccessfulTests);
191 }
192 
193 enum DumpTest : bool { no = false, yes = true }
194 enum TestResult { failure, success }
195 struct Test
196 {
197 	string testName;
198 	string harData;
199 	void function(ref TestContext) tester;
200 	HostSymbol[] hostSymbols;
201 	TargetOs targetOs = CompilationContext.hostOS;
202 	string printFilter;
203 }
204 
205 Test makeTest(alias test)() {
206 	static assert (__traits(getAttributes, test).length > 0);
207 	TestInfo info;
208 	TargetOs target;
209 	foreach (attr; __traits(getAttributes, test)) {
210 		static if (is (typeof(attr) == TestInfo)) {
211 			info = attr;
212 		} else static if (is (typeof(attr) == TargetOs)) {
213 			target = attr;
214 		}
215 	}
216 	return Test(__traits(identifier, test), test, info.tester, info.hostSymbols, target);
217 }
218 
219 Test[] collectTests(alias M)()
220 {
221 	Test[] tests;
222 	import std.traits : hasUDA;
223 	foreach(m; __traits(allMembers, M))
224 	{
225 		alias member = __traits(getMember, M, m);
226 		static if (hasUDA!(member, TestInfo)) {
227 			static if (is(typeof(member) == immutable string)) {
228 				tests ~= makeTest!member;
229 			}
230 		}
231 	}
232 	return tests;
233 }
234 
235 struct TestInfo
236 {
237 	void function(ref TestContext) tester;
238 	HostSymbol[] hostSymbols;
239 }
240 
241 struct TestContext
242 {
243 	Driver* driver;
244 
245 	auto getFunctionPtr(ResultType, ParamTypes...)(string funcName) {
246 		Identifier funcId = driver.context.idMap.getOrRegFqn(&driver.context, funcName);
247 		FunctionDeclNode* func = driver.context.findUniquelyNamedFunction(funcId);
248 		return driver.context.getFunctionPtr!(ResultType, ParamTypes)(func);
249 	}
250 }
251 
252 /// Global test output for jit tests
253 /// Cleared automatically after each test
254 TextSink testSink;
255 
256 extern(C) void external_noop() {}
257 
258 TestResult tryRunSingleTest(ref Driver driver, ref FuncDumpSettings dumpSettings, DumpTest dumpTest, Test curTest)
259 {
260 	scope(exit) testSink.clear;
261 	try
262 	{
263 		TestResult res = runSingleTest(driver, dumpSettings, dumpTest, curTest);
264 		return res;
265 	}
266 	catch(CompilationException e) {
267 		writeln(driver.context.sink.text);
268 		if (e.isICE)
269 			writeln(e);
270 		return TestResult.failure;
271 	}
272 	catch(Throwable t) {
273 		writeln(driver.context.sink.text);
274 		writeln(t);
275 		return TestResult.failure;
276 	}
277 }
278 
279 TestResult runSingleTest(ref Driver driver, ref FuncDumpSettings dumpSettings, DumpTest dumpTest, Test curTest)
280 {
281 	import std.string : lineSplitter;
282 	import std.algorithm : joiner, equal;
283 	bool isFailingTest;
284 	string expectedError;
285 
286 	enum NUM_ITERS = 1;
287 	auto time1 = currTime;
288 
289 		// setup modules
290 		driver.beginCompilation();
291 
292 		if (curTest.printFilter.length) driver.context.setDumpFilter(curTest.printFilter);
293 
294 		driver.context.targetOs = curTest.targetOs;
295 
296 		driver.addHostSymbols(curTest.hostSymbols);
297 
298 		void onHarFile(SourceFileInfo fileInfo)
299 		{
300 			if (fileInfo.name == "<error>") {
301 				assert(!isFailingTest, format("Multiple <error> files in test `%s`", curTest.testName));
302 				isFailingTest = true;
303 				expectedError = cast(string)fileInfo.content.strip;
304 			} else {
305 				driver.addModule(fileInfo);
306 			}
307 		}
308 
309 		// We strip beginning to allow newline between `q{` and `--- <filename>` for better readability
310 		string strippedHar = curTest.harData.stripLeft;
311 		parseHar(driver.context, "test.har", strippedHar, &onHarFile);
312 
313 		// Splitting lines gets rid of \r\n on windows, which will cause mismatch with error message
314 		auto expectedErrorRange = expectedError.lineSplitter.joiner("\n");
315 
316 		// compile
317 		try
318 		{
319 			driver.compile();
320 		}
321 		catch(CompilationException e)
322 		{
323 			if (!isFailingTest) throw e;
324 			if (e.isICE) throw e;
325 
326 			// successfully matched the error message(s)
327 			if (equal(driver.context.errorSink.text, expectedErrorRange)) {
328 				return TestResult.success;
329 			}
330 
331 			writefln("Test `%s` failed", curTest.testName);
332 			writefln("Expected error:");
333 			writeln(expectedErrorRange);
334 			writefln("Received error:");
335 			writeln(driver.context.errorSink.text);
336 			writefln("Stack trace:");
337 			writeln(driver.context.sink.text);
338 			writeln(e.info);
339 			return TestResult.failure;
340 		}
341 
342 		// Compiled successfully, but expected to fail
343 		if (isFailingTest) {
344 			writefln("Test `%s` compiled successfully, but expected to fail", curTest.testName);
345 			writefln("Expected error:");
346 			writeln(expectedErrorRange);
347 			return TestResult.failure;
348 		}
349 
350 		// finalize code pages
351 		driver.markCodeAsExecutable();
352 
353 	auto time2 = currTime;
354 	if (dumpTest && driver.context.printTimings)
355 		writefln("Compiled in %ss", scaledNumberFmt(time2-time1));
356 
357 	if (!driver.context.runTesters) return TestResult.success;
358 	if (driver.context.targetOs != driver.context.hostOS) return TestResult.success;
359 
360 	final switch (driver.context.buildType)
361 	{
362 		case BuildType.jit:
363 			if (curTest.tester) {
364 				auto testContext = TestContext(&driver);
365 				if (dumpTest) writefln("Running: %s tester", curTest.testName);
366 				auto time3 = currTime;
367 				curTest.tester(testContext);
368 				auto time4 = currTime;
369 				if (dumpTest && driver.context.printTimings)
370 					writefln("Run in %ss", scaledNumberFmt(time4-time3));
371 			}
372 			break;
373 
374 		case BuildType.exe:
375 			import std.process;
376 			import std.file : exists;
377 			import std.path;
378 			if(exists(driver.context.outputFilename))
379 			{
380 				if (dumpTest) writef("Running: %s", driver.context.outputFilename.absolutePath);
381 				auto time3 = currTime;
382 				auto result = execute(driver.context.outputFilename);
383 				auto time4 = currTime;
384 				if (dumpTest && driver.context.printTimings)
385 					writefln("Run in %ss", scaledNumberFmt(time4-time3));
386 				if (dumpTest) writefln(", status %s, output '%s'", result.status, result.output);
387 			}
388 			else
389 			{
390 				writefln("No executable produced '%s'", driver.context.outputFilename);
391 			}
392 			break;
393 	}
394 
395 	return TestResult.success;
396 }