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 }