1 /++
2   A module containing the TAP13 reporter https://testanything.org/
3 
4   This is an example of how this reporter looks
5   <script type="text/javascript" src="https://asciinema.org/a/135734.js" id="asciicast-135734" 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.tap;
12 
13 import std.conv;
14 import std..string;
15 import std.algorithm;
16 
17 version(Have_fluent_asserts) {
18   import fluentasserts.core.base;
19   import fluentasserts.core.results;
20 }
21 
22 import trial.interfaces;
23 import trial.reporters.writer;
24 
25 /// This reporter will print the results using thr Test anything protocol version 13
26 class TapReporter : ILifecycleListener, ITestCaseLifecycleListener
27 {
28   private {
29     ReportWriter writer;
30   }
31 
32   ///
33   this()
34   {
35     writer = defaultWriter;
36   }
37 
38   ///
39   this(ReportWriter writer)
40   {
41     this.writer = writer;
42   }
43 
44   ///
45   void begin(ulong testCount) {
46     writer.writeln("TAP version 13", ReportWriter.Context._default);
47     writer.writeln("1.." ~ testCount.to!string, ReportWriter.Context._default);
48   }
49 
50   ///
51   void update() { }
52 
53   ///
54   void end(SuiteResult[]) { }
55 
56   ///
57   void begin(string, ref TestResult)
58   {
59   }
60 
61   ///
62   void end(string suite, ref TestResult test)
63   {
64     if(test.status == TestResult.Status.success) {
65       writer.writeln("ok - " ~ suite ~ "." ~ test.name, ReportWriter.Context._default);
66     } else {
67       writer.writeln("not ok - " ~ suite ~ "." ~ test.name, ReportWriter.Context._default);
68 
69       version(Have_fluent_asserts) {
70         if(test.throwable !is null) {
71           if(cast(TestException) test.throwable !is null) {
72             printTestException(test);
73           } else {
74             printThrowable(test);
75           }
76 
77           writer.writeln("");
78         }
79       } else {
80         printThrowable(test);
81       }
82     }
83   }
84 
85   version(Have_fluent_asserts) {
86     private void printTestException(ref TestResult test) {
87       auto diagnostic = test.throwable.msg.split("\n").map!(a => "# " ~ a).join("\n");
88 
89       auto msg = test.throwable.msg.split("\n")[0];
90 
91       writer.writeln(diagnostic, ReportWriter.Context._default);
92       writer.writeln("  ---", ReportWriter.Context._default);
93       writer.writeln("  message: '" ~ msg ~ "'", ReportWriter.Context._default);
94       writer.writeln("  severity: " ~ test.status.to!string, ReportWriter.Context._default);
95       writer.writeln("  location:", ReportWriter.Context._default);
96       writer.writeln("    fileName: '" ~ test.throwable.file.replace("'", "\'") ~ "'", ReportWriter.Context._default);
97       writer.writeln("    line: " ~ test.throwable.line.to!string, ReportWriter.Context._default);
98     }
99   }
100 
101   private void printThrowable(ref TestResult test) {
102     writer.writeln("  ---", ReportWriter.Context._default);
103     writer.writeln("  message: '" ~ test.throwable.msg ~ "'", ReportWriter.Context._default);
104     writer.writeln("  severity: " ~ test.status.to!string, ReportWriter.Context._default);
105     writer.writeln("  location:", ReportWriter.Context._default);
106     writer.writeln("    fileName: '" ~ test.throwable.file.replace("'", "\'") ~ "'", ReportWriter.Context._default);
107     writer.writeln("    line: " ~ test.throwable.line.to!string, ReportWriter.Context._default);
108   }
109 }
110 
111 version(unittest) {
112   version(Have_fluent_asserts) {
113     import fluent.asserts;
114   }
115 }
116 
117 /// it should print "The Plan" at the beginning
118 unittest {
119   auto writer = new BufferedWriter;
120   auto reporter = new TapReporter(writer);
121   reporter.begin(10);
122 
123   writer.buffer.should.equal("TAP version 13\n1..10\n");
124 }
125 
126 /// it should print a sucess test
127 unittest
128 {
129   auto writer = new BufferedWriter;
130   auto reporter = new TapReporter(writer);
131 
132   auto test = new TestResult("other test");
133   test.status = TestResult.Status.success;
134 
135   reporter.end("some suite", test);
136 
137   writer.buffer.should.equal("ok - some suite.other test\n");
138 }
139 
140 /// it should print a failing test with a basic throwable
141 unittest
142 {
143   auto writer = new BufferedWriter;
144   auto reporter = new TapReporter(writer);
145 
146   auto test = new TestResult("other's test");
147   test.status = TestResult.Status.failure;
148   test.throwable = new Exception("Test's failure", "file.d", 42);
149 
150   reporter.end("some suite", test);
151 
152   writer.buffer.should.equal("not ok - some suite.other's test\n" ~
153   "  ---\n" ~
154   "  message: 'Test\'s failure'\n" ~
155   "  severity: failure\n" ~
156   "  location:\n" ~
157   "    fileName: 'file.d'\n" ~
158   "    line: 42\n\n");
159 }
160 
161 /// it should not print the YAML if the throwable is missing
162 unittest
163 {
164   auto writer = new BufferedWriter;
165   auto reporter = new TapReporter(writer);
166 
167   auto test = new TestResult("other's test");
168   test.status = TestResult.Status.failure;
169 
170   reporter.end("some suite", test);
171 
172   writer.buffer.should.equal("not ok - some suite.other's test\n");
173 }
174 
175 /// it should print the results of a TestException
176 unittest {
177   IResult[] results = [
178     cast(IResult) new MessageResult("message"),
179     cast(IResult) new ExtraMissingResult("a", "b") ];
180 
181   auto exception = new TestException(results, "unknown", 0);
182 
183   auto writer = new BufferedWriter;
184   auto reporter = new TapReporter(writer);
185 
186   auto test = new TestResult("other's test");
187   test.status = TestResult.Status.failure;
188   test.throwable = exception;
189 
190   reporter.end("some suite", test);
191 
192   writer.buffer.should.equal("not ok - some suite.other's test\n" ~
193   "# message\n" ~
194   "# \n" ~
195   "#     Extra:a\n" ~
196   "#   Missing:b\n" ~
197   "# \n" ~
198   "  ---\n" ~
199   "  message: 'message'\n" ~
200   "  severity: failure\n" ~
201   "  location:\n" ~
202   "    fileName: 'unknown'\n" ~
203   "    line: 0\n\n");
204 }