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 }