1 module unit_threaded.ut.io;
2 
3 import unit_threaded.runner.io;
4 
5 unittest {
6     import unit_threaded.runner.testcase: TestCase;
7     import unit_threaded.should;
8     import std.string: splitLines;
9 
10     enableDebugOutput(false);
11 
12     class TestOutput: Output {
13         string output;
14         override void send(in string output) {
15             import std.conv: text;
16             this.output ~= output;
17         }
18 
19         override void flush() {}
20     }
21 
22     class PrintTest: TestCase {
23         override void test() {
24             writelnUt("foo", "bar");
25         }
26         override string getPath() @safe pure nothrow const {
27             return "PrintTest";
28         }
29     }
30 
31     auto test = new PrintTest;
32     auto writer = new TestOutput;
33     test.setOutput(writer);
34     test();
35 
36     writer.output.splitLines.shouldEqual(
37         [
38             "PrintTest:",
39         ]
40     );
41 }
42 
43 
44 unittest {
45     import unit_threaded.should;
46     import unit_threaded.runner.testcase: TestCase;
47     import unit_threaded.runner.reflection: TestData;
48     import unit_threaded.runner.factory: createTestCase;
49     import std.traits: fullyQualifiedName;
50     import std.string: splitLines;
51 
52     enableDebugOutput;
53     scope(exit) enableDebugOutput(false);
54 
55     class TestOutput: Output {
56         string output;
57         override void send(in string output) {
58             import std.conv: text;
59             this.output ~= output;
60         }
61 
62         override void flush() {}
63     }
64 
65     class PrintTest: TestCase {
66         override void test() {
67             writelnUt("foo", "bar");
68         }
69         override string getPath() @safe pure nothrow const {
70             return "PrintTest";
71         }
72     }
73 
74     auto test = new PrintTest;
75     auto writer = new TestOutput;
76     test.setOutput(writer);
77     test();
78 
79     writer.output.splitLines.shouldEqual(
80         [
81             "PrintTest:",
82             "foobar",
83         ]
84     );
85 }
86 
87 
88 
89 struct FakeFile {
90     string fileName;
91     string mode;
92     string output;
93     void flush() shared {}
94     void write(in string s) shared {
95         output ~= s.dup;
96     }
97     string[] lines() shared const @safe pure {
98         import std.string: splitLines;
99         return output.splitLines;
100     }
101 }
102 shared FakeFile gOut;
103 shared FakeFile gErr;
104 void resetFakeFiles() {
105     synchronized {
106         gOut = FakeFile("out", "mode");
107         gErr = FakeFile("err", "mode");
108     }
109 }
110 
111 unittest {
112     import std.concurrency: spawn, thisTid, send, receiveOnly;
113     import unit_threaded.should;
114 
115     enableDebugOutput(false);
116     resetFakeFiles;
117 
118     auto tid = spawn(&threadWriter!(gOut, gErr), thisTid);
119     tid.send(ThreadWait());
120     receiveOnly!ThreadStarted;
121 
122     gOut.shouldEqual(shared FakeFile(nullFileName, "w"));
123     gErr.shouldEqual(shared FakeFile(nullFileName, "w"));
124 
125     tid.send(ThreadFinish());
126     receiveOnly!ThreadEnded;
127 }
128 
129 unittest {
130     import std.concurrency: spawn, send, thisTid, receiveOnly;
131     import unit_threaded.should;
132 
133     enableDebugOutput(true);
134     scope(exit) enableDebugOutput(false);
135     resetFakeFiles;
136 
137     auto tid = spawn(&threadWriter!(gOut, gErr), thisTid);
138     tid.send(ThreadWait());
139     receiveOnly!ThreadStarted;
140 
141     gOut.shouldEqual(shared FakeFile("out", "mode"));
142     gErr.shouldEqual(shared FakeFile("err", "mode"));
143 
144     tid.send(ThreadFinish());
145     receiveOnly!ThreadEnded;
146 }
147 
148 unittest {
149     import std.concurrency: spawn, thisTid, send, receiveOnly;
150     import unit_threaded.should;
151 
152     resetFakeFiles;
153 
154     auto tid = spawn(&threadWriter!(gOut, gErr), thisTid);
155     tid.send(ThreadWait());
156     receiveOnly!ThreadStarted;
157 
158     tid.send("foobar\n", thisTid);
159     tid.send("toto\n", thisTid);
160     gOut.output.shouldBeEmpty; // since it writes to the old gOut
161 
162     tid.send(ThreadFinish());
163     receiveOnly!ThreadEnded;
164 
165     // gOut is restored so the output should be here
166     gOut.lines.shouldEqual(
167         [
168             "foobar",
169             "toto",
170             ]
171         );
172 }
173 
174 unittest {
175     import std.concurrency: spawn, thisTid, send, receiveOnly, Tid;
176     import unit_threaded.should;
177 
178     resetFakeFiles;
179 
180     auto writerTid = spawn(&threadWriter!(gOut, gErr), thisTid);
181     writerTid.send(ThreadWait());
182     receiveOnly!ThreadStarted;
183 
184     writerTid.send("foobar\n", thisTid);
185     auto otherTid = spawn(
186         (Tid writerTid, Tid testTid) {
187             import std.concurrency: send, receiveOnly, OwnerTerminated, thisTid;
188             try {
189                 writerTid.send("what about me?\n", thisTid);
190                 testTid.send(true);
191                 receiveOnly!bool;
192 
193                 writerTid.send("seriously, what about me?\n", thisTid);
194                 testTid.send(true);
195                 receiveOnly!bool;
196 
197                 writerTid.send(Flush(), thisTid);
198                 testTid.send(true);
199                 receiveOnly!bool;
200 
201                 writerTid.send("final attempt\n", thisTid);
202                 testTid.send(true);
203 
204             } catch(OwnerTerminated ex) {}
205         },
206         writerTid,
207         thisTid);
208     receiveOnly!bool; //wait for otherThread 1st message
209 
210     writerTid.send("toto\n", thisTid);
211     otherTid.send(true); //tell otherThread to continue
212     receiveOnly!bool; //wait for otherThread 2nd message
213 
214     writerTid.send("last one from me\n", thisTid);
215     otherTid.send(true); // tell otherThread to continue
216     receiveOnly!bool; // wait for otherThread to try and flush (won't work)
217 
218     writerTid.send(Flush(), thisTid); //finish with our output
219     otherTid.send(true); //finish
220     receiveOnly!bool; // wait for otherThread to finish
221 
222     writerTid.send(ThreadFinish());
223     receiveOnly!ThreadEnded;
224 
225     // gOut is restored so the output should be here
226     // the output should also be serialised despite
227     // sending messages from two threads
228     gOut.lines.shouldEqual(
229         [
230             "foobar",
231             "toto",
232             "last one from me",
233             "what about me?",
234             "seriously, what about me?",
235             "final attempt",
236             ]
237         );
238 }
239 
240 unittest {
241     import std.concurrency: spawn, thisTid, send, receiveOnly, Tid;
242     import unit_threaded.should;
243 
244     resetFakeFiles;
245 
246     auto writerTid = spawn(&threadWriter!(gOut, gErr), thisTid);
247     writerTid.send(ThreadWait());
248     receiveOnly!ThreadStarted;
249 
250     writerTid.send("foo\n", thisTid);
251 
252     auto otherTid = spawn(
253         (Tid writerTid, Tid testTid) {
254             writerTid.send("bar\n", thisTid);
255             testTid.send(true); // synchronize with test tid
256         },
257         writerTid,
258         thisTid);
259 
260     receiveOnly!bool; //wait for spawned thread to do its thing
261 
262     // from now on, we've send "foo\n" but not flushed
263     // and the other tid has send "bar\n" and flushed
264 
265     writerTid.send(Flush(), thisTid);
266 
267     writerTid.send(ThreadFinish());
268     receiveOnly!ThreadEnded;
269 
270     gOut.lines.shouldEqual(
271         [
272             "foo",
273             ]
274         );
275 }
276 
277 unittest {
278     import std.concurrency: spawn, thisTid, send, receiveOnly, Tid;
279     import unit_threaded.should;
280 
281     resetFakeFiles;
282 
283     auto writerTid = spawn(&threadWriter!(gOut, gErr), thisTid);
284     writerTid.send(ThreadWait());
285     receiveOnly!ThreadStarted;
286 
287     writerTid.send("foo\n", thisTid);
288 
289     auto otherTid = spawn(
290         (Tid writerTid, Tid testTid) {
291             writerTid.send("bar\n", thisTid);
292             writerTid.send(Flush(), thisTid);
293             writerTid.send("baz\n", thisTid);
294             testTid.send(true); // synchronize with test tid
295         },
296         writerTid,
297         thisTid);
298 
299     receiveOnly!bool; //wait for spawned thread to do its thing
300 
301     // from now on, we've send "foo\n" but not flushed
302     // and the other tid has send "bar\n", flushed, then "baz\n"
303 
304     writerTid.send(Flush(), thisTid);
305 
306     writerTid.send(ThreadFinish());
307     receiveOnly!ThreadEnded;
308 
309     gOut.lines.shouldEqual(
310         [
311             "foo",
312             "bar",
313             ]
314         );
315 }
316 
317 unittest {
318     import std.concurrency: spawn, thisTid, send, receiveOnly, Tid;
319     import unit_threaded.should;
320 
321     resetFakeFiles;
322 
323     auto writerTid = spawn(&threadWriter!(gOut, gErr), thisTid);
324     writerTid.send(ThreadWait());
325     receiveOnly!ThreadStarted;
326 
327     writerTid.send("foo\n", thisTid);
328 
329     auto otherTid = spawn(
330         (Tid writerTid, Tid testTid) {
331             writerTid.send("bar\n", thisTid);
332             testTid.send(true); // synchronize with test tid
333             receiveOnly!bool; // wait for test thread to flush and give up being the primary thread
334             writerTid.send("baz\n", thisTid);
335             writerTid.send(Flush(), thisTid);
336             testTid.send(true);
337         },
338         writerTid,
339         thisTid);
340 
341     receiveOnly!bool; //wait for spawned thread to do its thing
342 
343     // from now on, we've send "foo\n" but not flushed
344     // and the other tid has send "bar\n" and flushed
345 
346     writerTid.send(Flush(), thisTid);
347 
348     otherTid.send(true); // tell it to continue
349     receiveOnly!bool;
350 
351     // now the other thread should be the main thread and prints out its partial output ("bar")
352     // and what it sent afterwards in order
353 
354     writerTid.send(ThreadFinish());
355     receiveOnly!ThreadEnded;
356 
357     gOut.lines.shouldEqual(
358         [
359             "foo",
360             "bar",
361             "baz",
362         ]
363     );
364 }
365 
366 unittest {
367     import std.concurrency: spawn, thisTid, send, receiveOnly;
368     import std.range: iota;
369     import std.parallelism: parallel;
370     import std.algorithm: map, canFind;
371     import std.array: array;
372     import std.conv: text;
373     import unit_threaded.should;
374 
375     resetFakeFiles;
376 
377     auto writerTid = spawn(&threadWriter!(gOut, gErr), thisTid);
378     writerTid.send(ThreadWait());
379     receiveOnly!ThreadStarted;
380 
381     string textFor(int i, int j) {
382         return text("i_", i, "_j_", j);
383     }
384 
385     enum numThreads = 100;
386     enum numMessages = 5;
387 
388     foreach(i; numThreads.iota.parallel) {
389         foreach(j; 0 .. numMessages) {
390             writerTid.send(textFor(i, j) ~ "\n", thisTid);
391         }
392         writerTid.send(Flush(), thisTid);
393     }
394 
395 
396     writerTid.send(ThreadFinish());
397     receiveOnly!ThreadEnded;
398 
399     foreach(i; 0 .. numThreads) {
400         const messages = numMessages.iota.map!(j => textFor(i, j)).array;
401         if(!gOut.lines.canFind(messages))
402             throw new Exception(text("Could not find ", messages, " in:\n", gOut.lines));
403     }
404 }
405 
406 
407 unittest {
408     import unit_threaded.runner.testcase: TestCase;
409     import unit_threaded.should;
410     import std.string: splitLines;
411 
412     enableDebugOutput(false);
413 
414     class TestOutput: Output {
415         string output;
416         override void send(in string output) {
417             import std.conv: text;
418             this.output ~= output;
419         }
420 
421         override void flush() {}
422     }
423 
424     class OkTest: TestCase {
425         override void test() { }
426         override string getPath() @safe pure nothrow const {
427             return "OkTest";
428         }
429     }
430 
431     class OopsTest: TestCase {
432         override void test() { throw new UnitTestException("oops"); }
433         override string getPath() @safe pure nothrow const {
434             return "OopsTest";
435         }
436     }
437 
438     auto ok = new OkTest;
439     auto oops = new OopsTest;
440     auto writer = new TestOutput;
441 
442     foreach(test; [ok, oops]) {
443         test.setOutput(writer);
444         test.quiet;
445     }
446 
447     ok();
448     writer.output.splitLines.shouldBeEmpty;
449 
450     oops();
451     writer.output.splitLines.should == [
452         "OopsTest:",
453         "    tests/unit_threaded/ut/io.d:432 - oops",
454         "",
455     ];
456 }