trial.runner 116/170(68%) line coverage

      
10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
160
170
180
190
200
210
220
230
240
250
260
270
280
290
300
310
320
330
3412
3512
360
370
380
390
400
411
420
431
440
451
461
470
480
491
500
510
520
5319
540
551
560
570
580
591
600
610
620
630
640
650
660
670
680
690
701
711
721
730
740
750
760
770
780
790
800
810
820
830
840
850
860
870
880
890
900
910
920
936
941
951
961
970
980
990
1000
1010
1020
1030
1040
1050
1060
1070
1080
1090
1100
1110
1120
1130
1140
1150
1160
1170
1180
1190
1200
1210
1220
1231
1241
1250
1260
1270
1281
1290
1301
1311
1321
1330
1341
1351
1361
1370
1381
1391
1401
1410
1421
1431
1441
1450
1460
1470
1480
1490
1500
1510
1520
1530
1540
1550
1560
1570
1580
1590
1600
1610
1620
1630
1640
1650
1660
1670
1681
1690
1709
1712
1720
1730
1741
1750
1760
1770
1780
1792
1800
1810
1820
1830
18413
1854
1864
1870
1880
1899
1906
1910
1920
1939
1940
1950
1960
1978
1988
1998
2008
2010
2028
2034
2046
2056
2060
2070
2080
2098
2105
2110
2126
2136
2140
2150
2160
2170
21812
2191
2200
2210
2227
2233
2240
2250
2264
2270
2280
2290
2302
2310
23218
2334
2340
2350
2362
2370
2380
2390
2400
2411
2420
2430
2440
2453
2460
2470
2480
2491
2500
2511
2521
2530
2542
2550
2560
2570
2580
2590
2600
2610
2620
2630
2640
2650
2660
2670
2680
2691
2700
2710
2720
2733
2740
2750
2760
2771
2780
2791
2801
2810
2822
2830
2840
2850
2860
2870
2880
2890
2900
2910
2920
2930
2940
2950
2960
2970
2980
2991
3000
3011
3021
3031
3040
3051
3060
3072
3082
3092
3102
3110
3120
3130
3140
31515
3160
31715
3180
31915
3200
3210
3220
32315
3240
3250
3260
32715
3280
32915
3300
331765
332240
3330
3340
33515
33615
3370
33815
3390
3400
3410
3420
343144
3440
3450
3460
3470
3480
3490
3500
3510
3520
3530
3542
3550
3560
3570
3580
3591
3601
3611
3620
3632
3640
3650
3660
3670
3681
3691
3701
3710
3722
3730
3740
3750
3760
3772
3780
3790
3800
3812
3820
3830
3840
3850
3860
3870
3880
3890
3900
3910
3920
3930
3940
3950
3960
3970
3980
3990
4000
4010
4020
4030
4040
4050
4060
40715
4080
4090
4100
4110
4120
4130
4140
4150
4160
4170
4180
4190
4200
4210
4220
4230
4240
4250
4260
4270
4280
4290
4300
4310
4320
4330
4340
4350
4360
4370
4380
4390
4400
4410
4420
4430
4440
4450
4460
4470
44815
44915
45015
45115
45215
45315
45415
4550
4560
4570
/++ The main runner logic. You can find here some LifeCycle logic and test runner initalization Copyright: © 2017 Szabo Bogdan License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. Authors: Szabo Bogdan +/ module trial.runner; import std.stdio; import std.algorithm; import std.datetime; import std.range; import std.traits; import std.string; import std.conv; import std.path; import std.getopt; import std.file; import std.path; import std.exception; import trial.settings; import trial.executor.single; import trial.executor.parallel; import trial.executor.process; version (Have_fluent_asserts) { version = Have_fluent_asserts_core; } static this() { if(LifeCycleListeners.instance is null) { LifeCycleListeners.instance = new LifeCycleListeners; } } /// setup the LifeCycle collection void setupLifecycle(Settings settings) { settings.artifactsLocation = settings.artifactsLocation.asAbsolutePath.array; Attachment.destination = buildPath(settings.artifactsLocation, "attachment"); if(!Attachment.destination.exists) { Attachment.destination.mkdirRecurse; } if(LifeCycleListeners.instance is null) { LifeCycleListeners.instance = new LifeCycleListeners; } settings.reporters.map!(a => a.toLower).each!(a => addReporter(a, settings)); addExecutor(settings.executor, settings); } void addExecutor(string name, Settings settings) { switch(name) { case "default": LifeCycleListeners.instance.add(new DefaultExecutor); break; case "parallel": LifeCycleListeners.instance.add(new ParallelExecutor(settings.maxThreads)); break; case "process": LifeCycleListeners.instance.add(new ProcessExecutor()); break; default: writeln("There is no `" ~ name ~ "` executor. Using the default."); LifeCycleListeners.instance.add(new DefaultExecutor); } } /// Adds an embeded reporter listener to the LifeCycle listeners collection void addReporter(string name, Settings settings) { import trial.reporters.spec; import trial.reporters.specprogress; import trial.reporters.specsteps; import trial.reporters.dotmatrix; import trial.reporters.landing; import trial.reporters.progress; import trial.reporters.list; import trial.reporters.html; import trial.reporters.allure; import trial.reporters.stats; import trial.reporters.result; import trial.reporters.xunit; import trial.reporters.tap; import trial.reporters.visualtrial; switch(name) { case "spec": LifeCycleListeners.instance.add(new SpecReporter(settings)); break; case "spec-progress": auto storage = statsFromFile(buildPath(settings.artifactsLocation, "stats.csv")); LifeCycleListeners.instance.add(new SpecProgressReporter(storage)); break; case "spec-steps": LifeCycleListeners.instance.add(new SpecStepsReporter(settings)); break; case "dot-matrix": LifeCycleListeners.instance.add(new DotMatrixReporter(settings.glyphs.dotMatrix)); break; case "landing": LifeCycleListeners.instance.add(new LandingReporter(settings.glyphs.landing)); break; case "list": LifeCycleListeners.instance.add(new ListReporter(settings)); break; case "progress": LifeCycleListeners.instance.add(new ProgressReporter(settings.glyphs.progress)); break; case "html": LifeCycleListeners.instance.add( new HtmlReporter(buildPath(settings.artifactsLocation, "result.html"), settings.warningTestDuration, settings.dangerTestDuration)); break; case "allure": LifeCycleListeners.instance.add(new AllureReporter(buildPath(settings.artifactsLocation, "allure"))); break; case "xunit": LifeCycleListeners.instance.add(new XUnitReporter(buildPath(settings.artifactsLocation, "xunit"))); break; case "result": LifeCycleListeners.instance.add(new ResultReporter(settings.glyphs.result)); break; case "stats": LifeCycleListeners.instance.add(new StatsReporter(buildPath(settings.artifactsLocation, "stats.csv"))); break; case "tap": LifeCycleListeners.instance.add(new TapReporter); break; case "visualtrial": LifeCycleListeners.instance.add(new VisualTrialReporter); break; default: writeln("There is no `" ~ name ~ "` reporter"); } } /// Returns an associative array of the detected tests, /// where the key is the suite name and the value is the TestCase const(TestCase)[][string] describeTests() { return describeTests(LifeCycleListeners.instance.getTestCases); } /// Returns an associative array of the detected tests, /// where the key is the suite name and the value is the TestCase const(TestCase)[][string] describeTests(const(TestCase)[] tests) { const(TestCase)[][string] groupedTests; foreach(test; tests) { groupedTests[test.suiteName] ~= test; } return groupedTests; } /// string toJSONHierarchy(T)(const(T)[][string] items) { struct Node { Node[string] nodes; const(T)[] values; void add(string[] path, const(T)[] values) { if(path.length == 0) { this.values = values; return; } if(path[0] !in nodes) { nodes[path[0]] = Node(); } nodes[path[0]].add(path[1..$], values); } string toString(int spaces = 2) { string prefix = leftJustify("", spaces); string endPrefix = leftJustify("", spaces - 2); string listValues = ""; string objectValues = ""; if(values.length > 0) { listValues = values .map!(a => a.toString) .map!(a => prefix ~ a) .join(",\n"); } if(nodes.keys.length > 0) { objectValues = nodes .byKeyValue .map!(a => `"` ~ a.key ~ `": ` ~ a.value.toString(spaces + 2)) .map!(a => prefix ~ a) .join(",\n"); } if(listValues != "" && objectValues != "") { return "{\n" ~ objectValues ~ ",\n" ~ prefix ~ "\"\": [\n" ~ listValues ~ "\n" ~ prefix ~ "]\n" ~ endPrefix ~ "}"; } if(listValues != "") { return "[\n" ~ listValues ~ "\n" ~ endPrefix ~ "]"; } return "{\n" ~ objectValues ~ "\n" ~ endPrefix ~ "}"; } } Node root; foreach(key; items.keys) { root.add(key.split("."), items[key]); } return root.toString; } /// convert an assoc array to JSON hierarchy unittest { struct Mock { string val; string toString() inout { return `"` ~ val ~ `"`; } } const(Mock)[][string] mocks; mocks["a.b"] = [ Mock("val1"), Mock("val2") ]; mocks["a.c"] = [ Mock("val3") ]; mocks.toJSONHierarchy.should.equal(`{ "a": { "b": [ "val1", "val2" ], "c": [ "val3" ] } }`); } /// it should have an empty key for items that contain both values and childs unittest { struct Mock { string val; string toString() inout { return `"` ~ val ~ `"`; } } const(Mock)[][string] mocks; mocks["a.b"] = [ Mock("val1"), Mock("val2") ]; mocks["a.b.c"] = [ Mock("val3") ]; mocks.toJSONHierarchy.should.equal(`{ "a": { "b": { "c": [ "val3" ], "": [ "val1", "val2" ] } } }`); } /// describeTests should return the tests cases serialised in json format unittest { void TestMock() @system { } TestCase[] tests; tests ~= TestCase("a.b", "some test", &TestMock, [ Label("some label", "label value") ]); tests ~= TestCase("a.c", "other test", &TestMock); auto result = describeTests(tests); result.values.length.should.equal(2); result.keys.should.containOnly([ "a.b", "a.c" ]); result["a.b"].length.should.equal(1); result["a.c"].length.should.equal(1); } /// Runs the tests and returns the results auto runTests(const(TestCase)[] tests, string testName = "", string suiteName = "") { setupSegmentationHandler!true(); const(TestCase)[] filteredTests = tests; if(testName != "") { filteredTests = tests.filter!(a => a.name.indexOf(testName) != -1).array; } if(suiteName != "") { filteredTests = filteredTests.filter!(a => a.suiteName.indexOf(suiteName) != -1).array; } LifeCycleListeners.instance.begin(filteredTests.length); SuiteResult[] results = LifeCycleListeners.instance.beginExecution(filteredTests); foreach(test; filteredTests) { results ~= LifeCycleListeners.instance.execute(test); } results ~= LifeCycleListeners.instance.endExecution; LifeCycleListeners.instance.end(results); return results; } /// Check if a suite result list is a success bool isSuccess(SuiteResult[] results) { return results.map!(a => a.tests).joiner.map!(a => a.status).all!(a => a == TestResult.Status.success || a == TestResult.Status.pending); } version(unittest) { version(Have_fluent_asserts_core) { import fluent.asserts; } } /// It should return true for an empty result unittest { [].isSuccess.should.equal(true); } /// It should return true if all the tests succeded unittest { SuiteResult[] results = [ SuiteResult("") ]; results[0].tests = [ new TestResult("") ]; results[0].tests[0].status = TestResult.Status.success; results.isSuccess.should.equal(true); } /// It should return false if one the tests failed unittest { SuiteResult[] results = [ SuiteResult("") ]; results[0].tests = [ new TestResult("") ]; results[0].tests[0].status = TestResult.Status.failure; results.isSuccess.should.equal(false); } /// It should return the name of this test unittest { if(LifeCycleListeners.instance is null || !LifeCycleListeners.instance.isRunning) { return; } LifeCycleListeners.instance.runningTest.should.equal("trial.runner.It should return the name of this test"); } void setupSegmentationHandler(bool testRunner)() { import core.runtime; // backtrace version(CRuntime_Glibc) import core.sys.linux.execinfo; else version(OSX) import core.sys.darwin.execinfo; else version(FreeBSD) import core.sys.freebsd.execinfo; else version(NetBSD) import core.sys.netbsd.execinfo; else version(Windows) import core.sys.windows.stacktrace; else version(Solaris) import core.sys.solaris.execinfo; static if( __traits( compiles, backtrace ) ) { version(Posix) { import core.sys.posix.signal; // segv handler static extern (C) void unittestSegvHandler(int signum, siginfo_t* info, void* ptr ) nothrow { import core.stdc.stdio; core.stdc.stdio.printf("\n\n"); static if(testRunner) { if(signum == SIGSEGV) { core.stdc.stdio.printf("Got a Segmentation Fault running "); } if(signum == SIGBUS) { core.stdc.stdio.printf("Got a bus error running "); } if(LifeCycleListeners.instance.runningTest != "") { core.stdc.stdio.printf("%s\n\n", LifeCycleListeners.instance.runningTest.ptr); } else { core.stdc.stdio.printf("some setup step. This is probably a Trial bug. Please create an issue on github.\n\n"); } } else { if(signum == SIGSEGV) { core.stdc.stdio.printf("Got a Segmentation Fault! "); } if(signum == SIGBUS) { core.stdc.stdio.printf("Got a bus error! "); } core.stdc.stdio.printf(" This is probably a Trial bug. Please create an issue on github.\n\n"); } static enum MAXFRAMES = 128; void*[MAXFRAMES] callstack; int numframes; numframes = backtrace( callstack.ptr, MAXFRAMES ); backtrace_symbols_fd( callstack.ptr, numframes, 2); } sigaction_t action = void; (cast(byte*) &action)[0 .. action.sizeof] = 0; sigfillset( &action.sa_mask ); // block other signals action.sa_flags = SA_SIGINFO | SA_RESETHAND; action.sa_sigaction = &unittestSegvHandler; sigaction( SIGSEGV, &action, null ); sigaction( SIGBUS, &action, null ); } } }