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