102030405060708090100110120130140150160170180190200210220230240250260270280290300310320330340350360370380391400410420430440450460470480490500510520530540550560570580590600610620630640650660670680690700710720730740750760771780791801810820831684085168608708808909009109276593094095096097098765990100765101010238610338610438610501062331072331082331092331100111311231133114311501169117911801199120012171227123712401251126112711280129126130126131013201330134013501360137013801395714001415714257143571440145571460147571485714901504531510152941539415494155015601575715801590160016101622351630164235165016653167016801690170182171017201732351740175229176017701782351790180118101820183235184018551865187018801892351900191242192119301940195235196619701980199235200020123520202030204020502060207020802090210021102120213021402151216121702181219122012210222122312240225222602270228022902300231123212330234123512361237023812391240024112421243024412451246024722482249225002510252025302540255125612570258125912600261126202631264126502662267026802690270027102721273127402751276127702781279028012811282028322840285028602870288028912901291029212931294029512961297029812991300030123020303030403050306030703081309131003111312131303141315131603171318131903202321032203230324032503260327032813291330033113321333033413351336133703381339034013410342234303440 /++ A module containing the SpecReporter This is an example of how this reporter looks <script type="text/javascript" src="https://asciinema.org/a/9z1tolgn7x55v41i3mm3wlkum.js" id="asciicast-9z1tolgn7x55v41i3mm3wlkum" async></script> 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.reporters.spec; import std.stdio; import std.array; import std.conv; import std.datetime; import std.string; import std.algorithm; import trial.interfaces; import trial.settings; import trial.reporters.writer; /// A structure containing the glyphs used for the spec reporter struct SpecGlyphs { version(Windows) { /// string ok = "+"; } else { /// string ok = "✓"; } string pending = "-"; } /// string specGlyphsToCode(SpecGlyphs glyphs) { return "SpecGlyphs(`" ~ glyphs.ok ~ "`)"; } /// This is the default reporter. The "spec" reporter outputs a hierarchical view nested just as the test cases are. class SpecReporter : ITestCaseLifecycleListener { enum Type { none, success, step, pending, failure, testBegin, testEnd, emptyLine, danger, warning } protected { int failedTests = 0; string lastSuiteName; ReportWriter writer; } private { Settings settings; } this() { writer = defaultWriter; } this(Settings settings) { writer = defaultWriter; this.settings = settings; } this(ReportWriter writer) { this.writer = writer; } private { string indentation(size_t cnt) pure { return " ".replicate(cnt); } } void write(Type t)(string text = "", size_t spaces = 0) { writer.write(indentation(spaces)); switch (t) { case Type.emptyLine: writer.writeln(""); break; case Type.success: writer.write(settings.glyphs.spec.ok, ReportWriter.Context.success); writer.write(" " ~ text, ReportWriter.Context.inactive); break; case Type.pending: writer.write(settings.glyphs.spec.pending, ReportWriter.Context.info); writer.write(" " ~ text, ReportWriter.Context.inactive); break; case Type.failure: writer.write(failedTests.to!string ~ ") " ~ text, ReportWriter.Context.danger); break; case Type.danger: writer.write(text, ReportWriter.Context.danger); break; case Type.warning: writer.write(text, ReportWriter.Context.warning); break; default: writer.write(text); } } void begin(string suite, ref TestResult test) { } protected auto printSuite(string suite) { size_t indents = 1; auto oldPieces = lastSuiteName.split("."); auto pieces = suite.split("."); lastSuiteName = suite; auto prefix = oldPieces.commonPrefix(pieces).array.length; write!(Type.emptyLine)(); indents += prefix; foreach (piece; pieces[prefix .. $]) { write!(Type.none)(piece, indents); write!(Type.emptyLine)(); indents++; } return indents; } void end(string suite, ref TestResult test) { size_t indents = 1; if (suite != lastSuiteName) { indents = printSuite(suite); } else { indents = suite.count('.') + 2; } if (test.status == TestResult.Status.success) { write!(Type.success)(test.name, indents); } if (test.status == TestResult.Status.pending) { write!(Type.pending)(test.name, indents); } if (test.status == TestResult.Status.failure) { write!(Type.failure)(test.name, indents); failedTests++; } auto timeDiff = (test.end - test.begin).total!"msecs"; if(timeDiff >= settings.warningTestDuration && timeDiff < settings.dangerTestDuration) { write!(Type.warning)(" (" ~ timeDiff.to!string ~ "ms)", 0); } if(timeDiff >= settings.dangerTestDuration) { write!(Type.danger)(" (" ~ timeDiff.to!string ~ "ms)", 0); } write!(Type.emptyLine); indents--; } } version (unittest) { version(Have_fluent_asserts) { import fluent.asserts; } } @("it should print a success test") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.success; reporter.begin("some suite", test); reporter.end("some suite", test); writer.buffer.should.equal("\n some suite" ~ "\n ✓ some test\n"); } @("it should print two success tests") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some suite"); auto test1 = new TestResult("some test"); test1.status = TestResult.Status.success; auto test2 = new TestResult("other test"); test2.status = TestResult.Status.success; reporter.begin("some suite", test1); reporter.end("some suite", test1); reporter.begin("some suite", test2); reporter.end("some suite", test2); writer.buffer.should.contain("\n some suite\n"); writer.buffer.should.contain("\n ✓ some test\n"); writer.buffer.should.contain("\n ✓ other test\n"); } @("it should print a failing test") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.failure; reporter.begin("some suite", test); reporter.end("some suite", test); writer.buffer.should.equal("\n some suite" ~ "\n 0) some test\n"); } @("it should print a pending test") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.pending; reporter.begin("some suite", test); reporter.end("some suite", test); writer.buffer.should.equal("\n some suite" ~ "\n - some test\n"); } @("it should split suites by dot") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some.suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.failure; test.throwable = new Exception("Random failure"); reporter.end("some.suite", test); reporter.end("some.suite", test); writer.buffer.should.equal( "\n" ~ " some\n" ~ " suite\n" ~ " 0) some test\n" ~ " 1) some test\n"); } @("it should omit the common suite names") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some.suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.failure; test.throwable = new Exception("Random failure"); reporter.end("some.suite", test); reporter.end("some.other", test); writer.buffer.should.equal( "\n" ~ " some\n" ~ " suite\n" ~ " 0) some test\n\n" ~ " other\n" ~ " 1) some test\n"); } /// it should print the test duration unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some.suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.success; test.end = Clock.currTime; test.begin = test.end - 1.seconds; test.throwable = new Exception("Random failure"); reporter.end("some.suite", test); writer.buffer.should.equal( "\n" ~ " some\n" ~ " suite\n" ~ " ✓ some test (1000ms)\n"); }
/++ A module containing the SpecReporter This is an example of how this reporter looks <script type="text/javascript" src="https://asciinema.org/a/9z1tolgn7x55v41i3mm3wlkum.js" id="asciicast-9z1tolgn7x55v41i3mm3wlkum" async></script> 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.reporters.spec; import std.stdio; import std.array; import std.conv; import std.datetime; import std.string; import std.algorithm; import trial.interfaces; import trial.settings; import trial.reporters.writer; /// A structure containing the glyphs used for the spec reporter struct SpecGlyphs { version(Windows) { /// string ok = "+"; } else { /// string ok = "✓"; } string pending = "-"; } /// string specGlyphsToCode(SpecGlyphs glyphs) { return "SpecGlyphs(`" ~ glyphs.ok ~ "`)"; } /// This is the default reporter. The "spec" reporter outputs a hierarchical view nested just as the test cases are. class SpecReporter : ITestCaseLifecycleListener { enum Type { none, success, step, pending, failure, testBegin, testEnd, emptyLine, danger, warning } protected { int failedTests = 0; string lastSuiteName; ReportWriter writer; } private { Settings settings; } this() { writer = defaultWriter; } this(Settings settings) { writer = defaultWriter; this.settings = settings; } this(ReportWriter writer) { this.writer = writer; } private { string indentation(size_t cnt) pure { return " ".replicate(cnt); } } void write(Type t)(string text = "", size_t spaces = 0) { writer.write(indentation(spaces)); switch (t) { case Type.emptyLine: writer.writeln(""); break; case Type.success: writer.write(settings.glyphs.spec.ok, ReportWriter.Context.success); writer.write(" " ~ text, ReportWriter.Context.inactive); break; case Type.pending: writer.write(settings.glyphs.spec.pending, ReportWriter.Context.info); writer.write(" " ~ text, ReportWriter.Context.inactive); break; case Type.failure: writer.write(failedTests.to!string ~ ") " ~ text, ReportWriter.Context.danger); break; case Type.danger: writer.write(text, ReportWriter.Context.danger); break; case Type.warning: writer.write(text, ReportWriter.Context.warning); break; default: writer.write(text); } } void begin(string suite, ref TestResult test) { } protected auto printSuite(string suite) { size_t indents = 1; auto oldPieces = lastSuiteName.split("."); auto pieces = suite.split("."); lastSuiteName = suite; auto prefix = oldPieces.commonPrefix(pieces).array.length; write!(Type.emptyLine)(); indents += prefix; foreach (piece; pieces[prefix .. $]) { write!(Type.none)(piece, indents); write!(Type.emptyLine)(); indents++; } return indents; } void end(string suite, ref TestResult test) { size_t indents = 1; if (suite != lastSuiteName) { indents = printSuite(suite); } else { indents = suite.count('.') + 2; } if (test.status == TestResult.Status.success) { write!(Type.success)(test.name, indents); } if (test.status == TestResult.Status.pending) { write!(Type.pending)(test.name, indents); } if (test.status == TestResult.Status.failure) { write!(Type.failure)(test.name, indents); failedTests++; } auto timeDiff = (test.end - test.begin).total!"msecs"; if(timeDiff >= settings.warningTestDuration && timeDiff < settings.dangerTestDuration) { write!(Type.warning)(" (" ~ timeDiff.to!string ~ "ms)", 0); } if(timeDiff >= settings.dangerTestDuration) { write!(Type.danger)(" (" ~ timeDiff.to!string ~ "ms)", 0); } write!(Type.emptyLine); indents--; } } version (unittest) { version(Have_fluent_asserts) { import fluent.asserts; } } @("it should print a success test") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.success; reporter.begin("some suite", test); reporter.end("some suite", test); writer.buffer.should.equal("\n some suite" ~ "\n ✓ some test\n"); } @("it should print two success tests") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some suite"); auto test1 = new TestResult("some test"); test1.status = TestResult.Status.success; auto test2 = new TestResult("other test"); test2.status = TestResult.Status.success; reporter.begin("some suite", test1); reporter.end("some suite", test1); reporter.begin("some suite", test2); reporter.end("some suite", test2); writer.buffer.should.contain("\n some suite\n"); writer.buffer.should.contain("\n ✓ some test\n"); writer.buffer.should.contain("\n ✓ other test\n"); } @("it should print a failing test") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.failure; reporter.begin("some suite", test); reporter.end("some suite", test); writer.buffer.should.equal("\n some suite" ~ "\n 0) some test\n"); } @("it should print a pending test") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.pending; reporter.begin("some suite", test); reporter.end("some suite", test); writer.buffer.should.equal("\n some suite" ~ "\n - some test\n"); } @("it should split suites by dot") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some.suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.failure; test.throwable = new Exception("Random failure"); reporter.end("some.suite", test); reporter.end("some.suite", test); writer.buffer.should.equal( "\n" ~ " some\n" ~ " suite\n" ~ " 0) some test\n" ~ " 1) some test\n"); } @("it should omit the common suite names") unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some.suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.failure; test.throwable = new Exception("Random failure"); reporter.end("some.suite", test); reporter.end("some.other", test); writer.buffer.should.equal( "\n" ~ " some\n" ~ " suite\n" ~ " 0) some test\n\n" ~ " other\n" ~ " 1) some test\n"); } /// it should print the test duration unittest { auto writer = new BufferedWriter; auto reporter = new SpecReporter(writer); auto suite = SuiteResult("some.suite"); auto test = new TestResult("some test"); test.status = TestResult.Status.success; test.end = Clock.currTime; test.begin = test.end - 1.seconds; test.throwable = new Exception("Random failure"); reporter.end("some.suite", test); writer.buffer.should.equal( "\n" ~ " some\n" ~ " suite\n" ~ " ✓ some test (1000ms)\n"); }