1 /++ 2 A module containing the SpecProgressReporter 3 4 Copyright: © 2017 Szabo Bogdan 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Szabo Bogdan 7 +/ 8 module trial.reporters.specprogress; 9 10 import std.stdio; 11 import std.array; 12 import std.conv; 13 import std.datetime; 14 import std.algorithm; 15 16 import trial.interfaces; 17 import trial.reporters.writer; 18 import trial.reporters.stats; 19 import trial.reporters.spec; 20 21 /// A flavour of the "spec" reporter that show the progress of long tests. This works well with the 22 /// parallel runner. If you are using the stats reporters, you will see a countdown for how long 23 /// you need to wait until the test is finished. 24 class SpecProgressReporter : SpecReporter, ISuiteLifecycleListener, ILifecycleListener { 25 private { 26 alias UpdateFunction = void delegate(CueInfo info); 27 28 struct CueInfo { 29 string id; 30 string name; 31 long duration; 32 SysTime begin; 33 } 34 35 size_t oldTextLength; 36 StatStorage storage; 37 string[] path; 38 39 CueInfo[] cues; 40 } 41 42 this(StatStorage storage) { 43 super(); 44 this.storage = storage; 45 writer.writeln(""); 46 } 47 48 this(ReportWriter writer, StatStorage storage) { 49 super(writer); 50 this.storage = storage; 51 writer.writeln(""); 52 } 53 54 void clearProgress() { 55 writer.goTo(1); 56 writer.write("\n" ~ " ".replicate(oldTextLength)); 57 writer.goTo(1); 58 oldTextLength = 0; 59 } 60 61 void begin(ulong) {} 62 void end(SuiteResult[]) {} 63 void update() { 64 writer.hideCursor; 65 auto now = Clock.currTime; 66 auto progress = cues.map!(cue => "*[" ~ (cue.duration - (now - cue.begin).total!"seconds").to!string ~ "s]" ~ cue.name).join(" ").to!string; 67 68 auto spaces = ""; 69 70 if(oldTextLength > progress.length) { 71 spaces = " ".replicate(oldTextLength - progress.length); 72 } 73 74 writer.goTo(1); 75 writer.write("\n" ~ progress ~ spaces ~ " "); 76 77 oldTextLength = progress.length; 78 writer.showCursor; 79 } 80 81 void removeCue(string id) { 82 cues = cues.filter!(a => a.id != id).array; 83 } 84 85 void begin(ref SuiteResult suite) { 86 auto stat = storage.find(suite.name); 87 auto duration = (stat.end - stat.begin).total!"seconds"; 88 89 cues ~= CueInfo(suite.name, suite.name, duration, Clock.currTime); 90 update; 91 } 92 93 void end(ref SuiteResult suite) { 94 removeCue(suite.name); 95 update; 96 } 97 98 override { 99 void begin(string suite, ref TestResult test) { 100 super.begin(suite, test); 101 102 auto stat = storage.find(suite ~ "." ~ test.name); 103 auto duration = (stat.end - stat.begin).total!"seconds"; 104 105 cues ~= CueInfo(suite ~ "." ~ test.name, test.name, duration, Clock.currTime); 106 update; 107 } 108 109 void end(string suite, ref TestResult test) { 110 clearProgress; 111 super.end(suite, test); 112 removeCue(suite ~ "." ~ test.name); 113 writer.writeln(""); 114 update; 115 } 116 } 117 } 118 119 version(unittest) { 120 import fluent.asserts; 121 import core.thread; 122 } 123 124 @("it should print a success test") 125 unittest { 126 auto storage = new StatStorage; 127 auto begin = SysTime.min; 128 auto end = begin + 10.seconds; 129 130 storage.values = [ Stat("some suite", begin, end), Stat("some suite.some test", begin, end) ]; 131 132 auto writer = new BufferedWriter; 133 auto reporter = new SpecProgressReporter(writer, storage); 134 135 auto suite = SuiteResult("some suite"); 136 auto test = new TestResult("some test"); 137 138 reporter.begin(suite); 139 reporter.begin("some suite", test); 140 141 writer.buffer.should.equal("\n*[10s]some suite *[10s]some test "); 142 143 Thread.sleep(1.seconds); 144 reporter.update(); 145 146 writer.buffer.should.equal("\n*[9s]some suite *[9s]some test "); 147 148 test.status = TestResult.Status.success; 149 reporter.end("some suite", test); 150 151 writer.buffer.should.equal("\n some suite \n ✓ some test\n\n*[9s]some suite "); 152 reporter.end(suite); 153 154 writer.buffer.should.equal("\n some suite \n ✓ some test\n\n "); 155 156 reporter.update(); 157 158 writer.buffer.should.equal("\n some suite \n ✓ some test\n\n "); 159 } 160 161 @("it should print two success tests") 162 unittest { 163 auto storage = new StatStorage; 164 auto begin = SysTime.min; 165 auto end = begin + 10.seconds; 166 167 storage.values = [ ]; 168 169 auto writer = new BufferedWriter; 170 auto reporter = new SpecProgressReporter(writer, storage); 171 172 auto suite = SuiteResult("some suite"); 173 auto test1 = new TestResult("test1"); 174 test1.status = TestResult.Status.success; 175 176 auto test2 = new TestResult("test2"); 177 test2.status = TestResult.Status.success; 178 179 reporter.begin(suite); 180 reporter.begin("some suite", test1); 181 reporter.end("some suite", test1); 182 183 reporter.begin("some suite", test2); 184 writer.buffer.should.equal("\n some suite \n ✓ test1\n\n*[0s]some suite *[0s]test2 "); 185 186 reporter.update(); 187 writer.buffer.should.equal("\n some suite \n ✓ test1\n\n*[0s]some suite *[0s]test2 "); 188 189 reporter.end("some suite", test2); 190 191 writer.buffer.should.equal("\n some suite \n ✓ test1\n ✓ test2\n \n*[0s]some suite "); 192 reporter.end(suite); 193 194 suite.name = "suite2"; 195 reporter.begin(suite); 196 reporter.begin("suite2", test1); 197 reporter.end("suite2", test1); 198 199 writer.buffer.should.equal( 200 "\n some suite \n" ~ 201 " ✓ test1\n"~ 202 " ✓ test2\n"~ 203 " \n"~ 204 " suite2 \n"~ 205 " ✓ test1\n\n"~ 206 "*[0s]suite2 "); 207 }