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