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