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 }