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 }