1 /++ 2 A module containing the ResultReporter 3 4 This is an example of how this reporter looks 5 <script type="text/javascript" src="https://asciinema.org/a/12x1mkxfmsj1j0f7qqwarkiyw.js" id="asciicast-12x1mkxfmsj1j0f7qqwarkiyw" async></script> 6 7 Copyright: © 2017 Szabo Bogdan 8 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 9 Authors: Szabo Bogdan 10 +/ 11 module trial.reporters.result; 12 13 import std.stdio; 14 import std.array; 15 import std.conv; 16 import std.datetime; 17 18 import trial.interfaces; 19 import trial.reporters.writer; 20 21 version (Have_fluent_asserts) 22 { 23 import fluentasserts.core.base; 24 import fluentasserts.core.results; 25 } 26 27 /// A structure containing the glyphs used for the result reporter 28 struct TestResultGlyphs { 29 version(Windows) { 30 /// 31 string error = "x"; 32 } else { 33 /// 34 string error = "✖"; 35 } 36 } 37 38 /// 39 string testResultGlyphsToCode(TestResultGlyphs glyphs) { 40 return "TestResultGlyphs(`" ~ glyphs.error ~ "`)"; 41 } 42 43 44 45 /// The "Result" reporter will print an overview of your test run 46 class ResultReporter : ILifecycleListener, ITestCaseLifecycleListener, 47 ISuiteLifecycleListener, IStepLifecycleListener 48 { 49 private 50 { 51 TestResultGlyphs glyphs; 52 53 int suites; 54 int tests; 55 int pending; 56 int failedTests; 57 58 SysTime beginTime; 59 ReportWriter writer; 60 61 Throwable[] exceptions; 62 string[] failedTestNames; 63 64 string currentSuite; 65 } 66 67 this() 68 { 69 writer = defaultWriter; 70 } 71 72 this(TestResultGlyphs glyphs) 73 { 74 writer = defaultWriter; 75 this.glyphs = glyphs; 76 } 77 78 this(ReportWriter writer) 79 { 80 this.writer = writer; 81 } 82 83 void begin(ref SuiteResult suite) 84 { 85 suites++; 86 currentSuite = suite.name; 87 } 88 89 void end(ref SuiteResult suite) 90 { 91 } 92 93 void update() 94 { 95 } 96 97 void begin(string suite, ref TestResult test) 98 { 99 } 100 101 void end(string suite, ref TestResult test) 102 { 103 if(test.status == TestResult.Status.pending) { 104 pending++; 105 } else { 106 tests++; 107 } 108 109 if (test.status != TestResult.Status.failure) 110 { 111 return; 112 } 113 114 exceptions ~= test.throwable; 115 failedTestNames ~= currentSuite ~ " " ~ test.name; 116 117 failedTests++; 118 } 119 120 void begin(string suite, string test, ref StepResult step) 121 { 122 } 123 124 void end(string suite, string test, ref StepResult step) 125 { 126 } 127 128 void begin(ulong) 129 { 130 beginTime = Clock.currTime; 131 } 132 133 void end(SuiteResult[] results) 134 { 135 auto diff = Clock.currTime - beginTime; 136 137 writer.writeln(""); 138 139 reportExceptions; 140 141 writer.writeln(""); 142 143 if (tests == 0) 144 { 145 reportNoTest; 146 } 147 148 if (tests == 1) 149 { 150 reportOneTestResult; 151 } 152 153 if (tests > 1) 154 { 155 reportTestsResult; 156 } 157 158 if(pending == 1) { 159 reportOnePendingTest; 160 } else if(pending > 1) { 161 reportManyPendingTests; 162 } 163 164 writer.writeln(""); 165 } 166 167 private 168 { 169 void reportNoTest() 170 { 171 writer.write("There are no tests to run."); 172 } 173 174 void reportOnePendingTest() 175 { 176 writer.write("There is a pending test.\n", ReportWriter.Context.info); 177 } 178 179 void reportManyPendingTests() 180 { 181 writer.write("There are " ~ pending.to!string ~ " pending tests.\n", ReportWriter.Context.info); 182 } 183 184 void reportOneTestResult() 185 { 186 auto timeDiff = Clock.currTime - beginTime; 187 188 if (failedTests > 0) 189 { 190 writer.write(glyphs.error ~ " The test failed in " ~ timeDiff.to!string ~ ":", 191 ReportWriter.Context.danger); 192 return; 193 } 194 195 writer.write("The test succeeded in ", ReportWriter.Context.active); 196 writer.write(timeDiff.to!string, ReportWriter.Context.info); 197 writer.write("!\n", ReportWriter.Context.active); 198 } 199 200 void reportTestsResult() 201 { 202 string suiteText = suites == 1 ? "1 suite" : suites.to!string ~ " suites"; 203 auto timeDiff = Clock.currTime - beginTime; 204 writer.write("Executed ", ReportWriter.Context.active); 205 writer.write(tests.to!string, ReportWriter.Context.info); 206 207 if(failedTests > 0) { 208 writer.write(" (", ReportWriter.Context.active); 209 writer.write(failedTests.to!string ~ " failed", ReportWriter.Context.danger); 210 writer.write(")", ReportWriter.Context.active); 211 } 212 213 writer.write(" tests in ", ReportWriter.Context.active); 214 writer.write(suiteText, ReportWriter.Context.info); 215 writer.write(" in ", ReportWriter.Context.active); 216 writer.write(timeDiff.to!string, ReportWriter.Context.info); 217 writer.write(".\n", ReportWriter.Context.info); 218 } 219 220 void reportExceptions() 221 { 222 foreach (size_t i, t; exceptions) 223 { 224 writer.writeln(""); 225 writer.writeln(i.to!string ~ ") " ~ failedTestNames[i] ~ ":", ReportWriter.Context.danger); 226 227 version (Have_fluent_asserts) 228 { 229 TestException e = cast(TestException) t; 230 231 if (e is null) 232 { 233 writer.writeln(t.to!string); 234 } 235 else 236 { 237 e.print(new TrialResultPrinter(defaultWriter)); 238 } 239 } 240 else 241 { 242 writer.writeln(t.to!string); 243 } 244 245 writer.writeln(""); 246 } 247 } 248 } 249 } 250 251 version (Have_fluent_asserts) { 252 class TrialResultPrinter : ResultPrinter { 253 @trusted: 254 ReportWriter writer; 255 256 this(ReportWriter writer) { 257 this.writer = writer; 258 } 259 260 void primary(string text) { 261 writer.write(text, ReportWriter.Context._default); 262 writer.write(""); 263 } 264 265 void info(string text) { 266 writer.write(text, ReportWriter.Context.info); 267 writer.write(""); 268 } 269 270 void danger(string text) { 271 writer.write(text, ReportWriter.Context.danger); 272 writer.write(""); 273 } 274 275 void success(string text) { 276 writer.write(text, ReportWriter.Context.success); 277 writer.write(""); 278 } 279 280 void dangerReverse(string text) { 281 writer.writeReverse(text, ReportWriter.Context.danger); 282 writer.write(""); 283 } 284 285 void successReverse(string text) { 286 writer.writeReverse(text, ReportWriter.Context.success); 287 writer.write(""); 288 } 289 } 290 } 291 292 version (unittest) 293 { 294 version(Have_fluent_asserts) { 295 import fluent.asserts; 296 } 297 } 298 299 @("The user should be notified with a message when no test is present") 300 unittest 301 { 302 auto writer = new BufferedWriter; 303 auto reporter = new ResultReporter(writer); 304 SuiteResult[] results; 305 306 reporter.begin(0); 307 reporter.end(results); 308 309 writer.buffer.should.contain("There are no tests to run."); 310 } 311 312 @("The user should see a nice message when one test is run") 313 unittest 314 { 315 auto writer = new BufferedWriter; 316 auto reporter = new ResultReporter(writer); 317 SuiteResult[] results = [SuiteResult("some suite")]; 318 319 results[0].tests = [new TestResult("some test")]; 320 results[0].tests[0].status = TestResult.Status.success; 321 322 reporter.begin(1); 323 reporter.begin(results[0]); 324 325 reporter.begin("some suite", results[0].tests[0]); 326 reporter.end("some suite", results[0].tests[0]); 327 328 reporter.end(results[0]); 329 reporter.end(results); 330 331 writer.buffer.should.contain("The test succeeded in"); 332 } 333 334 @("The user should see the number of suites and tests when multiple tests are run") 335 unittest 336 { 337 auto writer = new BufferedWriter; 338 auto reporter = new ResultReporter(writer); 339 SuiteResult[] results = [SuiteResult("some suite")]; 340 341 results[0].tests = [new TestResult("some test"), new TestResult("other test")]; 342 results[0].tests[0].status = TestResult.Status.success; 343 results[0].tests[1].status = TestResult.Status.success; 344 345 reporter.begin(2); 346 reporter.begin(results[0]); 347 348 reporter.begin("some suite", results[0].tests[0]); 349 reporter.end("some suite", results[0].tests[0]); 350 351 reporter.begin("some suite", results[0].tests[1]); 352 reporter.end("some suite", results[0].tests[1]); 353 354 reporter.end(results[0]); 355 reporter.end(results); 356 357 writer.buffer.should.contain("Executed 2 tests in 1 suite in "); 358 } 359 360 361 @("The user should see the number if there is a pending test") 362 unittest 363 { 364 auto writer = new BufferedWriter; 365 auto reporter = new ResultReporter(writer); 366 SuiteResult[] results = [SuiteResult("some suite")]; 367 368 results[0].tests = [new TestResult("some test"), new TestResult("other test"), new TestResult("pending test")]; 369 results[0].tests[0].status = TestResult.Status.success; 370 results[0].tests[1].status = TestResult.Status.success; 371 results[0].tests[2].status = TestResult.Status.pending; 372 373 reporter.begin(2); 374 reporter.begin(results[0]); 375 376 reporter.begin("some suite", results[0].tests[0]); 377 reporter.end("some suite", results[0].tests[0]); 378 379 reporter.begin("some suite", results[0].tests[1]); 380 reporter.end("some suite", results[0].tests[1]); 381 382 reporter.begin("some suite", results[0].tests[2]); 383 reporter.end("some suite", results[0].tests[2]); 384 385 reporter.end(results[0]); 386 reporter.end(results); 387 388 writer.buffer.should.contain("Executed 2 tests in 1 suite in "); 389 writer.buffer.should.contain("There is a pending test."); 390 } 391 392 @("The user should see the number if there are more than one pending tests") 393 unittest 394 { 395 auto writer = new BufferedWriter; 396 auto reporter = new ResultReporter(writer); 397 SuiteResult[] results = [SuiteResult("some suite")]; 398 399 results[0].tests = [new TestResult("some test"), new TestResult("other test"), new TestResult("pending test")]; 400 results[0].tests[0].status = TestResult.Status.success; 401 results[0].tests[1].status = TestResult.Status.pending; 402 results[0].tests[2].status = TestResult.Status.pending; 403 404 reporter.begin(2); 405 reporter.begin(results[0]); 406 407 reporter.begin("some suite", results[0].tests[0]); 408 reporter.end("some suite", results[0].tests[0]); 409 410 reporter.begin("some suite", results[0].tests[1]); 411 reporter.end("some suite", results[0].tests[1]); 412 413 reporter.begin("some suite", results[0].tests[2]); 414 reporter.end("some suite", results[0].tests[2]); 415 416 reporter.end(results[0]); 417 reporter.end(results); 418 419 writer.buffer.should.contain("The test succeeded in"); 420 writer.buffer.should.contain("There are 2 pending tests."); 421 } 422 423 @("The user should see the reason of a failing test") 424 unittest 425 { 426 auto writer = new BufferedWriter; 427 auto reporter = new ResultReporter(writer); 428 SuiteResult[] results = [SuiteResult("some suite")]; 429 430 results[0].tests = [new TestResult("some test")]; 431 results[0].tests[0].status = TestResult.Status.failure; 432 results[0].tests[0].throwable = new Exception("Random failure"); 433 434 reporter.begin(1); 435 reporter.begin(results[0]); 436 437 reporter.begin("some suite", results[0].tests[0]); 438 reporter.end("some suite", results[0].tests[0]); 439 440 reporter.end(results[0]); 441 reporter.end(results); 442 443 writer.buffer.should.contain("✖ The test failed in"); 444 writer.buffer.should.contain("0) some suite some test:\n"); 445 }