1 /++ 2 A module containing the SpecReporter 3 4 This is an example of how this reporter looks 5 <script type="text/javascript" src="https://asciinema.org/a/9z1tolgn7x55v41i3mm3wlkum.js" id="asciicast-9z1tolgn7x55v41i3mm3wlkum" 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.spec; 12 13 import std.stdio; 14 import std.array; 15 import std.conv; 16 import std.datetime; 17 import std..string; 18 import std.algorithm; 19 20 import trial.interfaces; 21 import trial.settings; 22 import trial.reporters.writer; 23 24 /// A structure containing the glyphs used for the spec reporter 25 struct SpecGlyphs { 26 version(Windows) { 27 /// 28 string ok = "+"; 29 } else { 30 /// 31 string ok = "✓"; 32 } 33 34 string pending = "-"; 35 } 36 37 /// 38 string specGlyphsToCode(SpecGlyphs glyphs) { 39 return "SpecGlyphs(`" ~ glyphs.ok ~ "`)"; 40 } 41 42 /// This is the default reporter. The "spec" reporter outputs a hierarchical view nested just as the test cases are. 43 class SpecReporter : ITestCaseLifecycleListener 44 { 45 enum Type 46 { 47 none, 48 success, 49 step, 50 pending, 51 failure, 52 testBegin, 53 testEnd, 54 emptyLine, 55 danger, 56 warning 57 } 58 59 protected 60 { 61 int failedTests = 0; 62 string lastSuiteName; 63 64 ReportWriter writer; 65 } 66 67 private 68 { 69 Settings settings; 70 } 71 72 this() 73 { 74 writer = defaultWriter; 75 } 76 77 this(Settings settings) 78 { 79 writer = defaultWriter; 80 this.settings = settings; 81 } 82 83 this(ReportWriter writer) 84 { 85 this.writer = writer; 86 } 87 88 private 89 { 90 string indentation(size_t cnt) pure 91 { 92 return " ".replicate(cnt); 93 } 94 } 95 96 void write(Type t)(string text = "", size_t spaces = 0) 97 { 98 writer.write(indentation(spaces)); 99 100 switch (t) 101 { 102 case Type.emptyLine: 103 writer.writeln(""); 104 break; 105 106 case Type.success: 107 writer.write(settings.glyphs.spec.ok, ReportWriter.Context.success); 108 writer.write(" " ~ text, ReportWriter.Context.inactive); 109 break; 110 111 case Type.pending: 112 writer.write(settings.glyphs.spec.pending, ReportWriter.Context.info); 113 writer.write(" " ~ text, ReportWriter.Context.inactive); 114 break; 115 116 case Type.failure: 117 writer.write(failedTests.to!string ~ ") " ~ text, 118 ReportWriter.Context.danger); 119 break; 120 121 case Type.danger: 122 writer.write(text, ReportWriter.Context.danger); 123 break; 124 125 case Type.warning: 126 writer.write(text, ReportWriter.Context.warning); 127 break; 128 129 default: 130 writer.write(text); 131 } 132 } 133 134 void begin(string suite, ref TestResult test) 135 { 136 } 137 138 protected auto printSuite(string suite) { 139 size_t indents = 1; 140 141 auto oldPieces = lastSuiteName.split("."); 142 auto pieces = suite.split("."); 143 lastSuiteName = suite; 144 145 auto prefix = oldPieces.commonPrefix(pieces).array.length; 146 147 write!(Type.emptyLine)(); 148 indents += prefix; 149 150 foreach (piece; pieces[prefix .. $]) 151 { 152 write!(Type.none)(piece, indents); 153 write!(Type.emptyLine)(); 154 indents++; 155 } 156 157 return indents; 158 } 159 160 void end(string suite, ref TestResult test) 161 { 162 size_t indents = 1; 163 164 if (suite != lastSuiteName) 165 { 166 indents = printSuite(suite); 167 } 168 else 169 { 170 indents = suite.count('.') + 2; 171 } 172 173 if (test.status == TestResult.Status.success) 174 { 175 write!(Type.success)(test.name, indents); 176 } 177 178 if (test.status == TestResult.Status.pending) 179 { 180 write!(Type.pending)(test.name, indents); 181 } 182 183 if (test.status == TestResult.Status.failure) 184 { 185 write!(Type.failure)(test.name, indents); 186 failedTests++; 187 } 188 189 auto timeDiff = (test.end - test.begin).total!"msecs"; 190 191 if(timeDiff >= settings.warningTestDuration && timeDiff < settings.dangerTestDuration) { 192 write!(Type.warning)(" (" ~ timeDiff.to!string ~ "ms)", 0); 193 } 194 195 if(timeDiff >= settings.dangerTestDuration) { 196 write!(Type.danger)(" (" ~ timeDiff.to!string ~ "ms)", 0); 197 } 198 199 write!(Type.emptyLine); 200 201 indents--; 202 } 203 } 204 205 version (unittest) 206 { 207 version(Have_fluent_asserts) { 208 import fluent.asserts; 209 } 210 } 211 212 @("it should print a success test") 213 unittest 214 { 215 auto writer = new BufferedWriter; 216 auto reporter = new SpecReporter(writer); 217 218 auto suite = SuiteResult("some suite"); 219 auto test = new TestResult("some test"); 220 test.status = TestResult.Status.success; 221 222 reporter.begin("some suite", test); 223 reporter.end("some suite", test); 224 225 writer.buffer.should.equal("\n some suite" ~ "\n ✓ some test\n"); 226 } 227 228 @("it should print two success tests") 229 unittest 230 { 231 auto writer = new BufferedWriter; 232 auto reporter = new SpecReporter(writer); 233 234 auto suite = SuiteResult("some suite"); 235 auto test1 = new TestResult("some test"); 236 test1.status = TestResult.Status.success; 237 238 auto test2 = new TestResult("other test"); 239 test2.status = TestResult.Status.success; 240 241 reporter.begin("some suite", test1); 242 reporter.end("some suite", test1); 243 244 reporter.begin("some suite", test2); 245 reporter.end("some suite", test2); 246 247 writer.buffer.should.contain("\n some suite\n"); 248 writer.buffer.should.contain("\n ✓ some test\n"); 249 writer.buffer.should.contain("\n ✓ other test\n"); 250 } 251 252 @("it should print a failing test") 253 unittest 254 { 255 auto writer = new BufferedWriter; 256 auto reporter = new SpecReporter(writer); 257 258 auto suite = SuiteResult("some suite"); 259 auto test = new TestResult("some test"); 260 261 test.status = TestResult.Status.failure; 262 263 reporter.begin("some suite", test); 264 reporter.end("some suite", test); 265 266 writer.buffer.should.equal("\n some suite" ~ "\n 0) some test\n"); 267 } 268 269 @("it should print a pending test") 270 unittest 271 { 272 auto writer = new BufferedWriter; 273 auto reporter = new SpecReporter(writer); 274 275 auto suite = SuiteResult("some suite"); 276 auto test = new TestResult("some test"); 277 278 test.status = TestResult.Status.pending; 279 280 reporter.begin("some suite", test); 281 reporter.end("some suite", test); 282 283 writer.buffer.should.equal("\n some suite" ~ "\n - some test\n"); 284 } 285 286 @("it should split suites by dot") 287 unittest 288 { 289 auto writer = new BufferedWriter; 290 auto reporter = new SpecReporter(writer); 291 292 auto suite = SuiteResult("some.suite"); 293 auto test = new TestResult("some test"); 294 295 test.status = TestResult.Status.failure; 296 test.throwable = new Exception("Random failure"); 297 298 reporter.end("some.suite", test); 299 reporter.end("some.suite", test); 300 301 writer.buffer.should.equal( 302 "\n" ~ " some\n" ~ " suite\n" ~ " 0) some test\n" ~ " 1) some test\n"); 303 } 304 305 @("it should omit the common suite names") 306 unittest 307 { 308 auto writer = new BufferedWriter; 309 auto reporter = new SpecReporter(writer); 310 311 auto suite = SuiteResult("some.suite"); 312 auto test = new TestResult("some test"); 313 314 test.status = TestResult.Status.failure; 315 test.throwable = new Exception("Random failure"); 316 317 reporter.end("some.suite", test); 318 reporter.end("some.other", test); 319 320 writer.buffer.should.equal( 321 "\n" ~ " some\n" ~ " suite\n" ~ " 0) some test\n\n" ~ " other\n" 322 ~ " 1) some test\n"); 323 } 324 325 /// it should print the test duration 326 unittest 327 { 328 auto writer = new BufferedWriter; 329 auto reporter = new SpecReporter(writer); 330 331 auto suite = SuiteResult("some.suite"); 332 auto test = new TestResult("some test"); 333 334 test.status = TestResult.Status.success; 335 test.end = Clock.currTime; 336 test.begin = test.end - 1.seconds; 337 338 test.throwable = new Exception("Random failure"); 339 340 reporter.end("some.suite", test); 341 342 writer.buffer.should.equal( 343 "\n" ~ " some\n" ~ " suite\n" ~ " ✓ some test (1000ms)\n"); 344 }