1 module unit_threaded.ut.io; 2 3 import unit_threaded.runner.io; 4 5 unittest { 6 import unit_threaded.runner.testcase: TestCase; 7 import unit_threaded.should; 8 import std..string: splitLines; 9 10 enableDebugOutput(false); 11 12 class TestOutput: Output { 13 string output; 14 override void send(in string output) { 15 import std.conv: text; 16 this.output ~= output; 17 } 18 19 override void flush() {} 20 } 21 22 class PrintTest: TestCase { 23 override void test() { 24 writelnUt("foo", "bar"); 25 } 26 override string getPath() @safe pure nothrow const { 27 return "PrintTest"; 28 } 29 } 30 31 auto test = new PrintTest; 32 auto writer = new TestOutput; 33 test.setOutput(writer); 34 test(); 35 36 writer.output.splitLines.shouldEqual( 37 [ 38 "PrintTest:", 39 ] 40 ); 41 } 42 43 unittest { 44 import unit_threaded.should; 45 import unit_threaded.runner.testcase: TestCase; 46 import unit_threaded.runner.reflection: TestData; 47 import unit_threaded.runner.factory: createTestCase; 48 import std.traits: fullyQualifiedName; 49 import std..string: splitLines; 50 51 enableDebugOutput; 52 scope(exit) enableDebugOutput(false); 53 54 class TestOutput: Output { 55 string output; 56 override void send(in string output) { 57 import std.conv: text; 58 this.output ~= output; 59 } 60 61 override void flush() {} 62 } 63 64 class PrintTest: TestCase { 65 override void test() { 66 writelnUt("foo", "bar"); 67 } 68 override string getPath() @safe pure nothrow const { 69 return "PrintTest"; 70 } 71 } 72 73 auto test = new PrintTest; 74 auto writer = new TestOutput; 75 test.setOutput(writer); 76 test(); 77 78 writer.output.splitLines.shouldEqual( 79 [ 80 "PrintTest:", 81 "foobar", 82 ] 83 ); 84 } 85 86 87 88 struct FakeFile { 89 string fileName; 90 string mode; 91 string output; 92 void flush() shared {} 93 void write(in string s) shared { 94 output ~= s.dup; 95 } 96 string[] lines() shared const @safe pure { 97 import std..string: splitLines; 98 return output.splitLines; 99 } 100 } 101 shared FakeFile gOut; 102 shared FakeFile gErr; 103 void resetFakeFiles() { 104 synchronized { 105 gOut = FakeFile("out", "mode"); 106 gErr = FakeFile("err", "mode"); 107 } 108 } 109 110 unittest { 111 import std.concurrency: spawn, thisTid, send, receiveOnly; 112 import unit_threaded.should; 113 114 enableDebugOutput(false); 115 resetFakeFiles; 116 117 auto tid = spawn(&threadWriter!(gOut, gErr), thisTid); 118 tid.send(ThreadWait()); 119 receiveOnly!ThreadStarted; 120 121 gOut.shouldEqual(shared FakeFile(nullFileName, "w")); 122 gErr.shouldEqual(shared FakeFile(nullFileName, "w")); 123 124 tid.send(ThreadFinish()); 125 receiveOnly!ThreadEnded; 126 } 127 128 unittest { 129 import std.concurrency: spawn, send, thisTid, receiveOnly; 130 import unit_threaded.should; 131 132 enableDebugOutput(true); 133 scope(exit) enableDebugOutput(false); 134 resetFakeFiles; 135 136 auto tid = spawn(&threadWriter!(gOut, gErr), thisTid); 137 tid.send(ThreadWait()); 138 receiveOnly!ThreadStarted; 139 140 gOut.shouldEqual(shared FakeFile("out", "mode")); 141 gErr.shouldEqual(shared FakeFile("err", "mode")); 142 143 tid.send(ThreadFinish()); 144 receiveOnly!ThreadEnded; 145 } 146 147 unittest { 148 import std.concurrency: spawn, thisTid, send, receiveOnly; 149 import unit_threaded.should; 150 151 resetFakeFiles; 152 153 auto tid = spawn(&threadWriter!(gOut, gErr), thisTid); 154 tid.send(ThreadWait()); 155 receiveOnly!ThreadStarted; 156 157 tid.send("foobar\n", thisTid); 158 tid.send("toto\n", thisTid); 159 gOut.output.shouldBeEmpty; // since it writes to the old gOut 160 161 tid.send(ThreadFinish()); 162 receiveOnly!ThreadEnded; 163 164 // gOut is restored so the output should be here 165 gOut.lines.shouldEqual( 166 [ 167 "foobar", 168 "toto", 169 ] 170 ); 171 } 172 173 unittest { 174 import std.concurrency: spawn, thisTid, send, receiveOnly, Tid; 175 import unit_threaded.should; 176 177 resetFakeFiles; 178 179 auto writerTid = spawn(&threadWriter!(gOut, gErr), thisTid); 180 writerTid.send(ThreadWait()); 181 receiveOnly!ThreadStarted; 182 183 writerTid.send("foobar\n", thisTid); 184 auto otherTid = spawn( 185 (Tid writerTid, Tid testTid) { 186 import std.concurrency: send, receiveOnly, OwnerTerminated, thisTid; 187 try { 188 writerTid.send("what about me?\n", thisTid); 189 testTid.send(true); 190 receiveOnly!bool; 191 192 writerTid.send("seriously, what about me?\n", thisTid); 193 testTid.send(true); 194 receiveOnly!bool; 195 196 writerTid.send(Flush(), thisTid); 197 testTid.send(true); 198 receiveOnly!bool; 199 200 writerTid.send("final attempt\n", thisTid); 201 testTid.send(true); 202 203 } catch(OwnerTerminated ex) {} 204 }, 205 writerTid, 206 thisTid); 207 receiveOnly!bool; //wait for otherThread 1st message 208 209 writerTid.send("toto\n", thisTid); 210 otherTid.send(true); //tell otherThread to continue 211 receiveOnly!bool; //wait for otherThread 2nd message 212 213 writerTid.send("last one from me\n", thisTid); 214 otherTid.send(true); // tell otherThread to continue 215 receiveOnly!bool; // wait for otherThread to try and flush (won't work) 216 217 writerTid.send(Flush(), thisTid); //finish with our output 218 otherTid.send(true); //finish 219 receiveOnly!bool; // wait for otherThread to finish 220 221 writerTid.send(ThreadFinish()); 222 receiveOnly!ThreadEnded; 223 224 // gOut is restored so the output should be here 225 // the output should also be serialised despite 226 // sending messages from two threads 227 gOut.lines.shouldEqual( 228 [ 229 "foobar", 230 "toto", 231 "last one from me", 232 "what about me?", 233 "seriously, what about me?", 234 "final attempt", 235 ] 236 ); 237 } 238 239 unittest { 240 import std.concurrency: spawn, thisTid, send, receiveOnly, Tid; 241 import unit_threaded.should; 242 243 resetFakeFiles; 244 245 auto writerTid = spawn(&threadWriter!(gOut, gErr), thisTid); 246 writerTid.send(ThreadWait()); 247 receiveOnly!ThreadStarted; 248 249 writerTid.send("foo\n", thisTid); 250 251 auto otherTid = spawn( 252 (Tid writerTid, Tid testTid) { 253 writerTid.send("bar\n", thisTid); 254 testTid.send(true); // synchronize with test tid 255 }, 256 writerTid, 257 thisTid); 258 259 receiveOnly!bool; //wait for spawned thread to do its thing 260 261 // from now on, we've send "foo\n" but not flushed 262 // and the other tid has send "bar\n" and flushed 263 264 writerTid.send(Flush(), thisTid); 265 266 writerTid.send(ThreadFinish()); 267 receiveOnly!ThreadEnded; 268 269 gOut.lines.shouldEqual( 270 [ 271 "foo", 272 ] 273 ); 274 } 275 276 unittest { 277 import std.concurrency: spawn, thisTid, send, receiveOnly, Tid; 278 import unit_threaded.should; 279 280 resetFakeFiles; 281 282 auto writerTid = spawn(&threadWriter!(gOut, gErr), thisTid); 283 writerTid.send(ThreadWait()); 284 receiveOnly!ThreadStarted; 285 286 writerTid.send("foo\n", thisTid); 287 288 auto otherTid = spawn( 289 (Tid writerTid, Tid testTid) { 290 writerTid.send("bar\n", thisTid); 291 writerTid.send(Flush(), thisTid); 292 writerTid.send("baz\n", thisTid); 293 testTid.send(true); // synchronize with test tid 294 }, 295 writerTid, 296 thisTid); 297 298 receiveOnly!bool; //wait for spawned thread to do its thing 299 300 // from now on, we've send "foo\n" but not flushed 301 // and the other tid has send "bar\n", flushed, then "baz\n" 302 303 writerTid.send(Flush(), thisTid); 304 305 writerTid.send(ThreadFinish()); 306 receiveOnly!ThreadEnded; 307 308 gOut.lines.shouldEqual( 309 [ 310 "foo", 311 "bar", 312 ] 313 ); 314 } 315 316 unittest { 317 import std.concurrency: spawn, thisTid, send, receiveOnly, Tid; 318 import unit_threaded.should; 319 320 resetFakeFiles; 321 322 auto writerTid = spawn(&threadWriter!(gOut, gErr), thisTid); 323 writerTid.send(ThreadWait()); 324 receiveOnly!ThreadStarted; 325 326 writerTid.send("foo\n", thisTid); 327 328 auto otherTid = spawn( 329 (Tid writerTid, Tid testTid) { 330 writerTid.send("bar\n", thisTid); 331 testTid.send(true); // synchronize with test tid 332 receiveOnly!bool; // wait for test thread to flush and give up being the primary thread 333 writerTid.send("baz\n", thisTid); 334 writerTid.send(Flush(), thisTid); 335 testTid.send(true); 336 }, 337 writerTid, 338 thisTid); 339 340 receiveOnly!bool; //wait for spawned thread to do its thing 341 342 // from now on, we've send "foo\n" but not flushed 343 // and the other tid has send "bar\n" and flushed 344 345 writerTid.send(Flush(), thisTid); 346 347 otherTid.send(true); // tell it to continue 348 receiveOnly!bool; 349 350 // now the other thread should be the main thread and prints out its partial output ("bar") 351 // and what it sent afterwards in order 352 353 writerTid.send(ThreadFinish()); 354 receiveOnly!ThreadEnded; 355 356 gOut.lines.shouldEqual( 357 [ 358 "foo", 359 "bar", 360 "baz", 361 ] 362 ); 363 } 364 365 unittest { 366 import std.concurrency: spawn, thisTid, send, receiveOnly; 367 import std.range: iota; 368 import std.parallelism: parallel; 369 import std.algorithm: map, canFind; 370 import std.array: array; 371 import std.conv: text; 372 import unit_threaded.should; 373 374 resetFakeFiles; 375 376 auto writerTid = spawn(&threadWriter!(gOut, gErr), thisTid); 377 writerTid.send(ThreadWait()); 378 receiveOnly!ThreadStarted; 379 380 string textFor(int i, int j) { 381 return text("i_", i, "_j_", j); 382 } 383 384 enum numThreads = 100; 385 enum numMessages = 5; 386 387 foreach(i; numThreads.iota.parallel) { 388 foreach(j; 0 .. numMessages) { 389 writerTid.send(textFor(i, j) ~ "\n", thisTid); 390 } 391 writerTid.send(Flush(), thisTid); 392 } 393 394 395 writerTid.send(ThreadFinish()); 396 receiveOnly!ThreadEnded; 397 398 foreach(i; 0 .. numThreads) { 399 const messages = numMessages.iota.map!(j => textFor(i, j)).array; 400 if(!gOut.lines.canFind(messages)) 401 throw new Exception(text("Could not find ", messages, " in:\n", gOut.lines)); 402 } 403 }