trial.discovery.spec 192/203(94%) 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
3019
310
320
330
340
3519
3619
370
3819
390
4019
410
4219
4319
440
4519
460
470
480
490
500
513
523
5312
540
553
563
570
580
590
600
610
620
630
6418
650
660
670
680
690
7018
710
720
730
740
750
764
774
784
790
804
8116
820
838
840
850
868
870
888
890
904
914
920
930
940
950
960
970
9850
9950
1000
101564
1020
103138
104138
1050
106138
1070
1080
1090
1100
1110
1120
11319
11419
11519
1160
11719
1180
11919
1200
12119
12231
12331
1240
12531
12631
1270
1280
12919
1300
13119
1320
1330
1340
1350
1360
1370
1380
1390
1400
1410
1420
1430
1440
1450
1460
1470
1480
1490
1500
1510
1520
1530
1540
1550
1560
1570
1582
1592
1600
1610
1620
1630
1640
1650
1660
1670
1680
1699
1700
1710
1720
1730
1740
1750
1760
1770
1780
1790
1800
1810
1820
1830
1840
18538
18638
1870
18838
1891182
19036
1910
19236
19336
19436
1950
19636
19736
1980
1990
2000
2011182
20230
20330
2040
20530
20630
20730
2080
20930
2100
2110
2120
2130
21438
2150
2160
2170
2180
2190
2202
2210
2220
2230
2240
2250
2260
2272
2280
2292
2302
2310
2322
2332
2340
2352
2360
2375030
2380
2395030
2404
2410
2424
2432
2440
2452
2462
2470
2482
2490
2500
2510
2520
2530
2540
2552
2560
2570
2580
2590
2600
26169
2620
2630
2640
26569
26668
2670
2680
2691
2700
2710
2720
2730
2740
2752
2762
2772
2780
2790
2800
2810
2820
2830
2840
2850
2860
2870
2880
2891
2901
2912
2920
2930
2940
2951
2963
2970
2983
2990
3000
3011
3022
3030
3041
3052
3060
3073
3080
3093
3100
3110
3121
3132
3140
3153
3160
3170
3180
3191
3207
3210
3223
3230
3241
3253
3260
3273
3280
3290
3301
3313
3320
3333
3340
3350
3360
3371
3382
3390
3401
3412
3420
3433
3440
3453
3460
3470
3481
3492
3500
3513
3520
3530
3540
3551
3567
3570
3583
3590
3601
3613
3620
3633
3640
3650
3661
3673
3680
3693
3700
3710
3720
3730
3740
3750
3760
3770
3781
3791
38019
3810
3822
3832
3840
3850
3860
3870
3880
3891
3901
39115
3920
3932
3942
3950
3960
3970
3980
3990
4001
4011
40219
4030
4042
4052
4060
4070
4080
4090
4100
4111
41220
4130
4142
4150
4160
4170
4180
4190
4200
4210
4221
4231
42419
4250
4261
4271
4281
4290
4302
4310
4321
4331
4340
4352
4360
4370
4380
4390
4400
4411
4421
44319
4440
4451
4461
4471
4480
4492
4500
4511
4521
4530
4542
4550
4560
4570
4580
4590
4601
4611
46219
4630
4641
4651
4661
4670
4682
4690
4701
4711
4720
4732
4740
4750
4760
4770
4780
4791
4801
48119
4820
4831
4841
4851
4860
4872
4880
4891
4901
4910
4922
4930
4940
4950
4960
4970
4981
4990
5001
50116
5020
5030
50419
50515
5060
/++ A module containing the discovery logic for spec tests 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.discovery.spec; import std.algorithm; import std.stdio; import std.array; import std.traits; import std.string; import trial.interfaces; import trial.discovery.code; alias SetupFunction = void delegate() @system; private string[] suitePath; private ulong[string] testsPerSuite; private TestCase[] testCases; private SetupFunction[] beforeList; private SetupFunction[] afterList; /// Define a Spec test suite void describe(T)(string name, T description) { if (suitePath.length == 0) { suitePath = [moduleName!description]; } auto beforeListIndex = beforeList.length; auto afterListIndex = afterList.length; suitePath ~= name; description(); beforeList = beforeList[0 .. beforeListIndex]; afterList = afterList[0 .. afterListIndex]; suitePath = suitePath[0 .. $ - 1]; } /// Define a function that will be ran before all the tests void before(T)(T setup) { bool wasRun; beforeList ~= { if (!wasRun) { setup(); wasRun = true; } }; } /// Define a function that will be ran before each test void beforeEach(T)(T setup) { beforeList ~= { setup(); }; } /// Define a function that will be ran after each test void afterEach(T)(T setup) { afterList ~= { setup(); }; } /// Define a function that will be ran after all the tests were ran void after(T)(T setup) { string suiteName = suitePath.join("."); long executedTests; bool wasRun; afterList ~= { if (wasRun) { return; } executedTests++; if (testsPerSuite[suiteName] < executedTests) { setup(); wasRun = true; } }; } private void updateTestCounter(string[] path, long value) { string tmp; string glue; foreach (key; path) { tmp ~= glue ~ key; glue = "."; testsPerSuite[tmp] += value; } } /// Define a Spec void it(T)(string name, T test, string file = __FILE__, size_t line = __LINE__) { auto before = beforeList.dup; auto after = afterList.dup; auto path = suitePath.dup; reverse(after); updateTestCounter(path, 1); auto testCase = TestCase(suitePath.join("."), name, ({ before.each!"a()"; test(); updateTestCounter(path, -1); after.each!"a()"; })); testCase.location = SourceLocation(file, line); testCases ~= testCase; } /// Define a pending Spec void it(string name, string file = __FILE__, size_t line = __LINE__) { auto before = beforeList.dup; auto after = afterList.dup; auto path = suitePath.dup; reverse(after); updateTestCounter(path, 1); auto testCase = TestCase(suitePath.join("."), name, ({ throw new PendingTestException(); })); testCase.location = SourceLocation(file, line); testCases ~= testCase; } /// The main spec container template Spec(alias definition) { shared static this() { suitePath = [moduleName!definition]; definition(); } } /// The default test discovery looks for unit test sections and groups them by module class SpecTestDiscovery : ITestDiscovery { /// Returns all the Specs as TestCase structure TestCase[] getTestCases() { return testCases; } /// It does nothing... void addModule(string file, string moduleName)() { } private void noTest() { assert(false, "you can not run this test"); } version (Have_libdparse) { private TestCase[] getTestCasesFromSpec(string file, string suite, const(Token)[] tokens) { TestCase[] testCases; auto iterator = TokenIterator(tokens); foreach(token; iterator) { if(token.text == "describe") { iterator.skipOne.skipWsAndComments; if(str(iterator.currentToken.type) == "(") { iterator.skipUntilType("stringLiteral"); string suiteName = iterator.currentToken.text.parseString.strip; auto block = iterator.readNextBlock; testCases ~= getTestCasesFromSpec(file, suite ~ "." ~ suiteName, block); } } if(token.text == "it") { iterator.skipOne.skipWsAndComments; auto location = SourceLocation(file, iterator.currentToken.line); if(str(iterator.currentToken.type) == "(") { iterator.skipUntilType("stringLiteral"); string testName = iterator.currentToken.text.parseString; testCases ~= TestCase(suite, testName, &this.noTest, [], location); } } } return testCases; } } TestCase[] discoverTestCases(string file) { TestCase[] testCases = []; version (Have_fluent_asserts) version (Have_libdparse) { import fluentasserts.core.results; auto tokens = fileToDTokens(file); auto iterator = TokenIterator(tokens); auto moduleName = iterator.skipUntilType("module").skipOne.readUntilType(";").strip; string lastName; DLangAttribute[] attributes; foreach (token; iterator) { auto type = str(token.type); if(token.text == "Spec") { iterator.skipOne.skipWsAndComments; if(str(iterator.currentToken.type) == "!") { iterator.skipOne.skipWsAndComments; if(str(iterator.currentToken.type) == "(") { auto block = iterator.readNextBlock; testCases ~= getTestCasesFromSpec(file, moduleName, block); } } } } } return testCases; } } /// string parseString(string someString) { if(someString == ""){ return ""; } if(someString[0] == '"') { return someString[1..$-1].replace(`\"`, `"`); } return someString[1..$-1]; } /// resolve the string tokens unittest { `"string token"`.parseString.should.equal("string token"); `"string \" token"`.parseString.should.equal("string \" token"); "`string token`".parseString.should.equal("string token"); } version (unittest) { version(Have_fluent_asserts): import fluent.asserts; private static string trace; private alias suite = Spec /* some comment*/ ! /* some comment*/ ( /* some comment*/ { describe("Algorithm", { it("should return false when the value is not present", { [1, 2, 3].canFind(4).should.equal(false); }); }); describe /* some comment*/ ("Nested describes", { describe("level 1", { describe("level 2", { it( /* some comment*/ "test name", { }); }); }); describe("other level 1", { describe("level 2", { it("test name", { }); }); }); }); describe("Before all", { before({ trace ~= "before1"; }); describe("level 2", { before({ trace ~= " before2"; }); it("should run the hooks", { trace ~= " test1"; }); it("should run the hooks", { trace ~= " test2"; }); }); describe("level 2 bis", { before({ trace ~= "before2-bis"; }); it("should run the hooks", { trace ~= " test3"; }); }); }); describe("Before each", { beforeEach({ trace ~= "before1 "; }); it("should run the hooks", { trace ~= "test1 "; }); describe("level 2", { beforeEach({ trace ~= "before2 "; }); it("should run the hooks", { trace ~= "test2 "; }); }); describe("level 2 bis", { beforeEach({ trace ~= "before2-bis "; }); it("should run the hooks", { trace ~= "test3"; }); }); }); describe("After all", { after({ trace ~= "after1"; }); describe("level 2", { after({ trace ~= " after2 "; }); it("should run the hooks", { trace ~= "test1"; }); it("should run the hooks", { trace ~= " test2"; }); }); describe("level 2 bis", { after({ trace ~= "after2-bis"; }); it("should run the hooks", { trace ~= "test3 "; }); }); }); describe("After each", { afterEach({ trace ~= " after1"; }); it("should run the hooks", { trace ~= "test1"; }); describe("level 2", { afterEach({ trace ~= " after2"; }); it("should run the hooks", { trace ~= " test2"; }); }); describe("level 2 bis", { afterEach({ trace ~= " after2-bis"; }); it("should run the hooks", { trace ~= "test3"; }); }); }); }); } /// getTestCases should find the spec suite unittest { auto specDiscovery = new SpecTestDiscovery; auto tests = specDiscovery.getTestCases.filter!( a => a.suiteName == "trial.discovery.spec.Algorithm").array; tests.length.should.equal(1).because("the Spec suite defined is in this file"); tests[0].name.should.equal("should return false when the value is not present"); } /// discoverTestCases should find the spec suite unittest { auto specDiscovery = new SpecTestDiscovery; auto tests = specDiscovery.discoverTestCases(__FILE__).filter!( a => a.suiteName == "trial.discovery.spec.Algorithm").array; tests.length.should.equal(1).because("the Spec suite defined is in this file"); tests[0].name.should.equal("should return false when the value is not present"); } /// getTestCases should find the spec suite unittest { auto specDiscovery = new SpecTestDiscovery; auto tests = specDiscovery.getTestCases.filter!( a => a.suiteName == "trial.discovery.spec.Algorithm").array; tests.length.should.equal(1).because("the Spec suite defined is in this file"); tests[0].name.should.equal("should return false when the value is not present"); } /// getTestCases should find nested spec suites unittest { auto specDiscovery = new SpecTestDiscovery; auto suites = specDiscovery.getTestCases.map!(a => a.suiteName).array; suites.should.contain(["trial.discovery.spec.Nested describes.level 1.level 2", "trial.discovery.spec.Nested describes.other level 1.level 2"]).because( "the Spec suites are defined in this file"); } /// It should execute the spec before all hooks unittest { auto specDiscovery = new SpecTestDiscovery; auto tests = specDiscovery.getTestCases.filter!( a => a.suiteName.startsWith("trial.discovery.spec.Before all")).array; trace = ""; tests[0].func(); tests[1].func(); trace.should.equal("before1 before2 test1 test2"); trace = ""; tests[2].func(); trace.should.equal("before2-bis test3"); } /// It should execute the spec after all hooks unittest { auto specDiscovery = new SpecTestDiscovery; auto tests = specDiscovery.getTestCases.filter!( a => a.suiteName.startsWith("trial.discovery.spec.After all")).array; trace = ""; tests[0].func(); tests[1].func(); trace.should.equal("test1 test2 after2 after1"); trace = ""; tests[2].func(); trace.should.equal("test3 after2-bis"); } /// It should execute the spec before hooks unittest { auto specDiscovery = new SpecTestDiscovery; auto tests = specDiscovery.getTestCases.filter!( a => a.suiteName.startsWith("trial.discovery.spec.Before each")).array; trace = ""; tests[0].func(); tests[1].func(); trace.should.equal("before1 test1 before1 before2 test2 "); trace = ""; tests[2].func(); trace.should.equal("before1 before2-bis test3"); } /// It should execute the spec after hooks unittest { auto specDiscovery = new SpecTestDiscovery; auto tests = specDiscovery.getTestCases.filter!( a => a.suiteName.startsWith("trial.discovery.spec.After each")).array; trace = ""; tests[0].func(); tests[1].func(); trace.should.equal("test1 after1 test2 after2 after1"); trace = ""; tests[2].func(); trace.should.equal("test3 after2-bis after1"); } /// discoverTestCases should find the same tests like testCases unittest { auto testDiscovery = new SpecTestDiscovery; testDiscovery .discoverTestCases(__FILE__).map!(a => a.toString).join("\n") .should.equal( testDiscovery.getTestCases .filter!(a => a.location.fileName.canFind(__FILE__)) .map!(a => a.toString).join("\n")); }