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 }