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