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 }