1 /**
2  * IO related functions
3  */
4 
5 module unit_threaded.io;
6 
7 import std.concurrency;
8 import std.stdio;
9 import std.conv;
10 
11 /**
12  * Write if debug output was enabled. Not thread-safe in the sense that it
13  * will get printed out immediately and may overlap with other output.
14  * This is why the test runner forces single-threaded mode when debug mode
15  * is selected.
16  */
17 void writelnUt(T...)(T args)
18 {
19     import std.stdio;
20 
21     if (_debugOutput)
22         writeln("    ", args);
23 }
24 
25 private shared(bool) _debugOutput = false; ///print debug msgs?
26 private shared(bool) _forceEscCodes = false; ///use ANSI escape codes anyway?
27 
28 package void enableDebugOutput() nothrow
29 {
30     synchronized
31     {
32         _debugOutput = true;
33     }
34 }
35 
36 package bool isDebugOutputEnabled() nothrow
37 {
38     synchronized
39     {
40         return _debugOutput;
41     }
42 }
43 
44 package void forceEscCodes() nothrow
45 {
46     synchronized
47     {
48         _forceEscCodes = true;
49     }
50 }
51 
52 /**
53  * Adds to the test cases output so far or immediately prints
54  * Params:
55  *  output = The output to add to.
56  *  msg = The string to add.
57  */
58 package void addToOutput(ref string output, in string msg) @safe
59 {
60     if (_debugOutput)
61     {
62         import std.stdio;
63 
64         writeln(msg);
65     }
66     else
67     {
68         output ~= msg;
69     }
70 }
71 
72 package void utWrite(T...)(T args)
73 {
74     WriterThread.get().write(args);
75 }
76 
77 package void utWriteln(T...)(T args)
78 {
79     WriterThread.get().writeln(args);
80 }
81 
82 package void utWritelnGreen(T...)(T args)
83 {
84     WriterThread.get().writelnGreen(args);
85 }
86 
87 package void utWritelnRed(T...)(T args)
88 {
89     WriterThread.get().writelnRed(args);
90 }
91 
92 package void utWriteRed(T...)(T args)
93 {
94     WriterThread.get().writeRed(args);
95 }
96 
97 package void utWriteYellow(T...)(T args)
98 {
99     WriterThread.get().writeYellow(args);
100 }
101 
102 /**
103  * Thread to output to stdout
104  */
105 class WriterThread
106 {
107 
108     /**
109      * Returns a reference to the only instance of this class.
110      */
111     static WriterThread get()
112     {
113         if (!_instantiated)
114         {
115             synchronized
116             {
117                 if (_instance is null)
118                 {
119                     _instance = new WriterThread;
120                 }
121                 _instantiated = true;
122             }
123         }
124         return _instance;
125     }
126 
127     /**
128      * Writes the args in a thread-safe manner.
129      */
130     void write(T...)(T args)
131     {
132         _tid.send(text(args));
133     }
134 
135     /**
136      * Writes the args in a thread-safe manner and appends a newline.
137      */
138     void writeln(T...)(T args)
139     {
140         write(args, "\n");
141     }
142 
143     /**
144      * Writes the args in a thread-safe manner in green (POSIX only).
145      * and appends a newline.
146      */
147     void writelnGreen(T...)(T args)
148     {
149         _tid.send(green(text(args) ~ "\n"));
150     }
151 
152     /**
153      * Writes the args in a thread-safe manner in red (POSIX only)
154      * and appends a newline.
155      */
156     void writelnRed(T...)(T args)
157     {
158         _tid.send(red(text(args) ~ "\n"));
159     }
160 
161     /**
162      * Writes the args in a thread-safe manner in red (POSIX only).
163      * and appends a newline.
164      */
165     void writeRed(T...)(T args)
166     {
167         _tid.send(red(text(args)));
168     }
169 
170     /**
171      * Writes the args in a thread-safe manner in yellow (POSIX only).
172      * and appends a newline.
173      */
174     void writeYellow(T...)(T args)
175     {
176         _tid.send(yellow(text(args)));
177     }
178 
179     /**
180      * Creates the singleton instance and waits until it's ready.
181      */
182     static void start()
183     {
184         WriterThread.get._tid.send(true, thisTid);
185         receiveOnly!bool; //wait for it to start
186     }
187 
188     /**
189      * Waits for the writer thread to terminate.
190      */
191     void join()
192     {
193         _tid.send(thisTid); //tell it to join
194         receiveOnly!Tid(); //wait for it to join
195         _instance = null;
196         _instantiated = false;
197     }
198 
199 private:
200 
201     enum Color
202     {
203         red,
204         green,
205         yellow,
206         cancel,
207     }
208 
209     this()
210     {
211         _tid = spawn(&threadWriter);
212 
213         version (Posix)
214         {
215             import core.sys.posix.unistd;
216 
217             _useEscCodes = _forceEscCodes || isatty(stdout.fileno()) != 0;
218         }
219     }
220 
221     /**
222      * Generate green coloured output on POSIX systems
223      */
224     string green(in string msg) @safe pure const
225     {
226         return escCode(Color.green) ~ msg ~ escCode(Color.cancel);
227     }
228 
229     /**
230      * Generate red coloured output on POSIX systems
231      */
232     string red(in string msg) @safe pure const
233     {
234         return escCode(Color.red) ~ msg ~ escCode(Color.cancel);
235     }
236 
237     /**
238      * Generate yellow coloured output on POSIX systems
239      */
240     string yellow(in string msg) @safe pure const
241     {
242         return escCode(Color.yellow) ~ msg ~ escCode(Color.cancel);
243     }
244 
245     /**
246      * Send escape code to the console
247      */
248     string escCode(in Color code) @safe pure const
249     {
250         return _useEscCodes ? _escCodes[code] : "";
251     }
252 
253     Tid _tid;
254     static immutable string[] _escCodes = ["\033[31;1m", "\033[32;1m", "\033[33;1m",
255         "\033[0;;m"];
256     bool _useEscCodes;
257 
258     static bool _instantiated; /// Thread local
259     __gshared WriterThread _instance;
260 }
261 
262 private void threadWriter()
263 {
264     auto done = false;
265     Tid _tid;
266 
267     auto saveStdout = stdout;
268     auto saveStderr = stderr;
269 
270     scope (exit)
271     {
272         saveStdout.flush();
273         stdout = saveStdout;
274         stderr = saveStderr;
275     }
276 
277     if (!isDebugOutputEnabled())
278     {
279         version (Posix)
280         {
281             enum nullFileName = "/dev/null";
282         }
283         else
284         {
285             enum nullFileName = "NUL";
286         }
287 
288         stdout = File(nullFileName, "w");
289         stderr = File(nullFileName, "w");
290     }
291 
292     while (!done)
293     {
294         string output;
295         receive(
296             (string msg)
297             {
298                 output ~= msg;
299             },
300            (bool, Tid tid)
301             {  //another thread is waiting for confirmation
302                 //that we started, let them know it's ok to proceed
303                 tid.send(true);
304             },
305             (Tid tid)
306             {
307                 done = true;
308                 _tid = tid;
309             },
310             (OwnerTerminated trm)
311             {
312                 done = true;
313             }
314         );
315         saveStdout.write(output);
316     }
317     if (_tid != Tid.init)
318         _tid.send(thisTid);
319 }
320 
321 unittest
322 {
323     //make sure this can be brought up and down again
324     WriterThread.get.join;
325     WriterThread.get.join;
326 }