10203040506070809010011012013014015016017018019020021022023024025026027028029030193103203303403519361937038193904019410421943194404519460470480490500513523531254055356357058059060061062063064166506606706806907016710720730740750764774784790804811482083784085086787088789090491492093094095096097098469946100010152810201031301041301050106130107010801090110011101120113171140115171160117011817119171200121171221591232412401250126291270128291290130139131261320133013401351713601371713801390140014101420143014401450146014701480149015001510152015301540155015601570158015901602161216201630164016501660167016801690170017191720173017401750176017701780179018001810182018301840185018601873818838189019038191118219236193019436195361963619701983619936200020102020203118220430205302060207302083020930210021130212021302140215021638217021802190220022102222223022402250226022702280229223002312232223302342235223602372238023950082400241500824242430244424522460247224822490250225102520253025402550256025722580259026002610262026369264026502660267692686826902700271127202730274027502760277227822792280028102820283028402850286028702880289029002911292129322940295029602971298329903003301030203031304230503061307230803093310031133120313031413152316031733180319032003211322732303243325032613273328032933300331033213333334033533360337033803391340234103421343234403453346034733480349035013512352035333540355035603571358735903603361036213633364036533660367036813693370037133720373037403750376037703780379038013811382173830384238523860387038803890390039113921393153940395239623970398039904000401040214031404174050406240724080409041004110412041314141841504162417041804190420042104220423042414251426174270428142914301431043224330434143514360437243804390440044104420443144414451744604471448144914500451245204531454145504562457045804590460046104621463146417465046614671468146904702471047214731474047524760477047804790480048114821483174840485148614871488048924900491149214930494249504960497049804990500150105021503165040505050617507155080 /++ 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 path = suitePath.dup; updateTestCounter(path, 1); import std.stdio; auto before = beforeList; auto after = afterList; auto testCase = TestCase(suitePath.join("."), name, ({ foreach(a; before) { a(); } test(); updateTestCounter(path, -1); foreach_reverse(a; after) { a(); } })); testCase.location = SourceLocation(file, line); testCases ~= testCase; } /// Define a pending Spec void it(string name, string file = __FILE__, size_t line = __LINE__) { auto path = suitePath.dup; 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")); }
/++ 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 path = suitePath.dup; updateTestCounter(path, 1); import std.stdio; auto before = beforeList; auto after = afterList; auto testCase = TestCase(suitePath.join("."), name, ({ foreach(a; before) { a(); } test(); updateTestCounter(path, -1); foreach_reverse(a; after) { a(); } })); testCase.location = SourceLocation(file, line); testCases ~= testCase; } /// Define a pending Spec void it(string name, string file = __FILE__, size_t line = __LINE__) { auto path = suitePath.dup; 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")); }