trial.executor.parallel 125/135(92%) 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
307
310
320
330
347
350
360
370
380
390
400
410
420
430
440
450
460
470
480
490
500
510
520
530
540
550
560
570
580
590
600
610
620
630
640
650
660
670
680
690
700
710
720
730
740
750
760
770
780
790
809
818
820
830
840
850
860
870
880
890
900
910
920
930
940
950
960
970
980
990
1000
1010
1020
1030
1040
1050
1060
1070
1080
1090
1100
1110
1120
1130
114872584
1150
1160
1170
11815
1195
1205
1215
1220
1235
1240
1255
1260
1270
12824
1298
1300
1310
13224
1338
1348
1350
1360
1371308111
138436037
1390
1400
14121
1427
1430
1440
14521
1467
1470
1480
1493
1501
1510
1520
1531309533
1540
1550
1560
1570
1580
1590
1600
1610
162436511
1630
164436511
165436511
166436511
1670
168436511
1690
1700
1710
1720
1730
1740
1758
1768
1778
1780
1790
1800
1810
1820
1830
1840
1850
1860
1870
1880
1895
1905
1910
1925
1930
1944
1950
1960
1970
1980
1990
2000
2010
2020
2030
2040
2050
2060
2070
2080
2096
2106
2110
2126
2130
2140
2150
2166
2176
2180
2196
2200
2210
2220
2238
2240
2258
2266
2276
2280
2290
2308
2310
2320
23311
2340
2350
2368
2378
2388
2390
2408
2418
2420
2430
2440
2458
2460
2478
2480
2490
25011
2510
2520
2538
25416
2558
2560
2578
2580
2598
2608
2610
2620
2630
2647
2657
2667
2677
2680
2697
2707
2710
2727
2730
2740
2750
2767
2770
2787
2797
2807
2810
2827
2830
2840
2850
286436511
2870
288436511
2890
2901309557
2918
2920
2930
2941309575
29514
2967
2970
2980
29914
3007
3010
3020
3030
3041309557
3058
3060
3078
3081
3090
3100
3118
3120
3130
3142619639
315873404
3166
3170
3180
3190
320436511
3210
3220
3230
3245
3250
3260
327482
328482
329482
330482
3310
3320
3330
3340
3350
3360
3378
3380
3398
3408
3410
3428
3430
3448
3458
3460
3470
3488
3490
3501
3510
3520
3530
3548
3550
356872066
357436029
358436029
3590
3600
3618
3620
3630
3640
36539
3668
3678
36812
3690
3700
3718
3720
3730
3745
3755
3760
3770
3780
3795
3800
38133
3826
3830
3840
3850
3860
3875
3880
38911
3906
3910
3920
3935
3940
3950
/++ A module containing the parallel test runner 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.executor.parallel; public import trial.interfaces; import std.datetime; import std.exception; import std.algorithm; import std.array; import core.thread; version(unittest) { version(Have_fluent_asserts) { import fluent.asserts; } } /// The Lifecycle listener used to send data from the tests threads to /// the main thread class ThreadLifeCycleListener : LifeCycleListeners { static string currentTest; override { void begin(string suite, string test, ref StepResult step) { ThreadProxy.instance.beginStep(currentTest, step.name, step.begin); } void end(string suite, string test, ref StepResult step) { ThreadProxy.instance.endStep(currentTest, step.name, step.end); } void end(string, ref TestResult test) { assert(false, "You can not call `end` outside of the main thread"); } void begin(string, ref TestResult test) { assert(false, "You can not call `begin` outside of the main thread"); } void add(T)(T listener) { assert(false, "You can not call `add` outside of the main thread"); } void begin(ulong) { assert(false, "You can not call `begin` outside of the main thread"); } void end(SuiteResult[] result) { assert(false, "You can not call `end` outside of the main thread"); } void begin(ref SuiteResult suite) { assert(false, "You can not call `begin` outside of the main thread"); } void end(ref SuiteResult suite) { assert(false, "You can not call `end` outside of the main thread"); } SuiteResult[] execute(ref const(TestCase)) { assert(false, "You can not call `execute` outside of the main thread"); } SuiteResult[] beginExecution(ref const(TestCase)[]) { assert(false, "You can not call `beginExecution` outside of the main thread"); } SuiteResult[] endExecution() { assert(false, "You can not call `endExecution` outside of the main thread"); } } } static ~this() { if(ThreadLifeCycleListener.currentTest != "") { ThreadProxy.instance.end(ThreadLifeCycleListener.currentTest); } } private { import core.atomic; struct StepAction { enum Type { begin, end } string test; string name; SysTime time; Type type; } synchronized class ThreadProxy { private shared static ThreadProxy _instance = new shared ThreadProxy; shared { private { string[] beginTests; string[] endTests; StepAction[] steps; Throwable[string] failures; ulong testCount; } static { shared(ThreadProxy) instance() { return _instance; } } void reset() { beginTests = []; endTests = []; steps = []; failures = typeof(failures).init; testCount = 0; } void begin(string name) { beginTests ~= name; } void end(string name) { core.atomic.atomicOp!"+="(this.testCount, 1); endTests ~= name; } auto getTestCount() { return testCount; } void beginStep(shared(string) testName, string stepName, SysTime begin) { steps ~= StepAction(testName, stepName, begin, StepAction.Type.begin); } void endStep(shared(string) testName, string stepName, SysTime end) { steps ~= StepAction(testName, stepName, end, StepAction.Type.end); } void setFailure(string key, shared(Throwable) t) { failures[key] = t; } auto getStatus() { struct Status { string[] begin; StepAction[] steps; string[] end; Throwable[string] failures; ulong testCount; } auto status = shared Status(beginTests.dup, steps.dup, endTests.dup, failures, testCount); beginTests = []; steps = []; endTests = []; return status; } } } } private void testThreadSetup(string testName) { ThreadLifeCycleListener.currentTest = testName; LifeCycleListeners.instance = new ThreadLifeCycleListener; ThreadProxy.instance.begin(testName); } /// The parallel executors runs tests in a sepparate thread class ParallelExecutor : ITestExecutor { struct SuiteStats { SuiteResult result; ulong testsFinished; bool isDone; } this(uint maxTestCount = 0) { this.maxTestCount = maxTestCount; if(this.maxTestCount <= 0) { import core.cpuid : threadsPerCPU; this.maxTestCount = threadsPerCPU; } } private { ulong testCount; uint maxTestCount; string currentSuite = ""; SuiteStats[string] suiteStats; TestCase[string] testCases; StepResult[][string] stepStack; void addSuiteResult(string name) { suiteStats[name].result.begin = Clock.currTime; suiteStats[name].result.end = Clock.currTime; LifeCycleListeners.instance.begin(suiteStats[name].result); } void endSuiteResult(string name) { suiteStats[name].result.end = Clock.currTime; suiteStats[name].isDone = true; LifeCycleListeners.instance.end(suiteStats[name].result); } void addTestResult(string key) { auto testCase = testCases[key]; if(currentSuite != testCase.suiteName) { addSuiteResult(testCase.suiteName); currentSuite = testCase.suiteName; } auto testResult = suiteStats[testCase.suiteName] .result .tests .filter!(a => a.name == testCase.name) .front; testResult.begin = Clock.currTime; testResult.end = Clock.currTime; testResult.status = TestResult.Status.started; LifeCycleListeners.instance.begin(testCase.suiteName, testResult); stepStack[key] = [ testResult ]; } void endTestResult(string key, Throwable t) { auto testCase = testCases[key]; auto testResult = suiteStats[testCase.suiteName] .result .tests .filter!(a => a.name == testCase.name) .front; testResult.end = Clock.currTime; testResult.status = t is null ? TestResult.Status.success : TestResult.Status.failure; testResult.throwable = t; suiteStats[testCases[key].suiteName].testsFinished++; LifeCycleListeners.instance.end(testCases[key].suiteName, testResult); stepStack.remove(key); } void addStep(string key, string name, SysTime time) { auto step = new StepResult; step.name = name; step.begin = time; step.end = time; stepStack[key][stepStack[key].length - 1].steps ~= step; stepStack[key] ~= step; LifeCycleListeners.instance.begin(testCases[key].suiteName, testCases[key].name, step); } void endStep(string key, string name, SysTime time) { auto step = stepStack[key][stepStack[key].length - 1]; enforce(step.name == name, "unexpected step name"); step.end = time; stepStack[key] ~= stepStack[key][0..$-1]; LifeCycleListeners.instance.end(testCases[key].suiteName, testCases[key].name, step); } auto processEvents() { LifeCycleListeners.instance.update; auto status = ThreadProxy.instance.getStatus; foreach(beginKey; status.begin) { addTestResult(beginKey); } foreach(step; status.steps) { if(step.type == StepAction.Type.begin) { addStep(step.test, step.name, step.time); } if(step.type == StepAction.Type.end) { endStep(step.test, step.name, step.time); } } foreach(endKey; status.end) { Throwable failure = null; if(endKey in status.failures) { failure = cast() status.failures[endKey]; } endTestResult(endKey, failure); } foreach(ref index, ref stat; suiteStats.values) { if(!stat.isDone && stat.result.tests.length == stat.testsFinished) { endSuiteResult(stat.result.name); } } return status.testCount; } void wait() { ulong executedTestCount; do { LifeCycleListeners.instance.update(); executedTestCount = processEvents; Thread.sleep(1.msecs); } while(executedTestCount < testCount); } } SuiteResult[] execute(ref const(TestCase) testCase) { import std.parallelism; SuiteResult[] result; auto key = testCase.suiteName ~ "|" ~ testCase.name; testCases[key] = TestCase(testCase); testCount++; task({ testThreadSetup(key); try { testCase.func(); } catch(Throwable t) { ThreadProxy.instance.setFailure(key, cast(shared)t); } }).executeInNewThread(); auto runningTests = testCount - ThreadProxy.instance.getTestCount; while(maxTestCount <= runningTests && runningTests > 0) { processEvents; runningTests = testCount - ThreadProxy.instance.getTestCount; } return result; } SuiteResult[] beginExecution(ref const(TestCase)[] tests) { foreach(test; tests) { auto const suite = test.suiteName; if(suite !in suiteStats) { suiteStats[suite] = SuiteStats(SuiteResult(suite)); } suiteStats[suite].result.tests ~= new TestResult(test.name); } ThreadProxy.instance.reset(); return []; } SuiteResult[] endExecution() { wait; foreach(stat; suiteStats.values) { if(!stat.isDone) { endSuiteResult(stat.result.name); } } SuiteResult[] results; foreach(stat; suiteStats) { results ~= stat.result; } return results; } }