1 module test; 2 3 import std.exception; 4 import vibe.d, vibe.rpcchannel; 5 import tinyevent; 6 7 version (unittest) : import std.stdio; 8 9 // Hack. Well for testing this should be OK ;-) 10 TCPServer!(APIServer, API) server; 11 12 struct TestStruct 13 { 14 int a; 15 string[] b; 16 } 17 18 abstract class API 19 { 20 /// Test return values, parameters, overloading 21 void request1(); 22 /// ditto 23 int request1(int a); 24 /// ditto 25 int request2(int[] a); 26 /// ditto 27 int request2(TestStruct s); 28 /// ditto 29 int getCounter(); 30 /// ditto 31 TestStruct getStruct(); 32 /// emit a event on the server which is not emitted to clients 33 void emitIgnoredEvent(); 34 35 /// Test events 36 Event!() voidEvent; 37 Event!(int) intEvent; 38 Event!(int, int) twoEvent; 39 /// This event is emitted from a free-running counter 40 Event!() counterEvent; 41 42 /// Call all events 43 void testEvents(); 44 45 @ignoreRPC Event!() ignoredEvent; 46 47 @ignoreRPC int ignoredFunction() 48 { 49 return 42; 50 } 51 52 /// stop the server 53 void stopServer(); 54 55 /// stop the server and exception 56 void stopServerException(); 57 58 /// Throw 59 void remoteException(); 60 61 void startEvents(); 62 63 void stopEvents(); 64 } 65 66 size_t numDestroyed = 0; 67 68 class APIServer : API 69 { 70 int counter; 71 TCPConnection _info; 72 bool _eventStop = false; 73 74 this(TCPConnection info) 75 { 76 _info = info; 77 } 78 79 static APIServer startSession(TCPConnection info) 80 { 81 return new APIServer(info); 82 } 83 84 override void request1() 85 { 86 counter = 10; 87 } 88 89 override int request1(int a) 90 { 91 counter += a; 92 return counter; 93 } 94 95 override int request2(int[] a) 96 { 97 counter += a.length; 98 return counter; 99 } 100 101 override int request2(TestStruct s) 102 { 103 counter += s.a; 104 counter += s.b.length; 105 return counter; 106 } 107 108 override int getCounter() 109 { 110 return counter; 111 } 112 113 override TestStruct getStruct() 114 { 115 TestStruct result; 116 result.a = 42; 117 result.b = ["a", "b"]; 118 return result; 119 } 120 121 override void emitIgnoredEvent() 122 { 123 ignoredEvent.emit(); 124 } 125 126 override int ignoredFunction() 127 { 128 return 0; 129 } 130 131 override void testEvents() 132 { 133 voidEvent.emit(); 134 intEvent.emit(42); 135 136 Timer timer; 137 int i = 0; 138 timer = setTimer(10.msecs, () 139 { 140 counterEvent.emit(); if (++i == 3) 141 timer.stop(); 142 }, true); 143 } 144 145 override void stopServer() 146 { 147 // Hack. Well for testing this should be OK ;-) 148 server.stop(); 149 } 150 151 override void stopServerException() 152 { 153 // Hack. Well for testing this should be OK ;-) 154 server.stop(); 155 throw new Exception(""); 156 } 157 158 override void remoteException() 159 { 160 throw new Exception("A error message"); 161 } 162 163 override void startEvents() 164 { 165 166 runTask(() 167 { 168 while (!_eventStop) 169 { 170 counterEvent.emit(); 171 sleep(10.usecs); 172 } 173 }); 174 } 175 176 override void stopEvents() 177 { 178 _eventStop = true; 179 } 180 181 ~this() 182 { 183 numDestroyed++; 184 } 185 } 186 187 // Test basic functions 188 unittest 189 { 190 uint done = 0; 191 192 void clientMain() 193 { 194 try 195 { 196 scope (exit) 197 { 198 done++; 199 if (done == 3) 200 { 201 // We eant to test that the client disconnects first! 202 sleep(10.msecs); 203 assert(numDestroyed == 3); 204 server.stop(); 205 exitEventLoop(); 206 } 207 } 208 209 auto client = clientTCP!(API)("localhost", 8030); 210 client.request1(); 211 assert(client.getCounter() == 10); 212 assert(client.request1(10) == 20); 213 assert(client.getCounter() == 20); 214 assert(client.request2([1, 3]) == 22); 215 assert(client.getCounter() == 22); 216 assert(client.request2(TestStruct(6, ["1", "2"])) == 30); 217 assert(client.getCounter() == 30); 218 assert(client.getStruct() == TestStruct(42, ["a", "b"])); 219 220 // Test events 221 bool voidCalled, intCalled; 222 size_t counter = 0; 223 client.voidEvent ~= () 224 { 225 voidCalled = true; 226 }; 227 client.intEvent ~= (int a) 228 { 229 assert(a == 42); 230 intCalled = true; 231 }; 232 client.counterEvent ~= () 233 { 234 counter++; 235 }; 236 client.testEvents(); 237 // Other request parallel to counterEvent 238 client.getCounter(); 239 // wait till counterEvents are done 240 sleep(100.msecs); 241 assert(voidCalled); 242 assert(intCalled); 243 assert(counter == 3); 244 245 // Server would return 0, local returns 42 246 assert(client.ignoredFunction() == 42); 247 client.ignoredEvent ~= () 248 { 249 assert(false, "Event should be ignored"); 250 }; 251 client.emitIgnoredEvent(); 252 client.closeTCP(); 253 } 254 catch (Exception e) 255 assert(false, "Should not throw: " ~ e.toString()); 256 } 257 258 numDestroyed = 0; 259 server = serveTCP!(APIServer, API)(8030); 260 runTask( & clientMain); 261 runTask( & clientMain); 262 runTask( & clientMain); 263 runEventLoop(); 264 } 265 266 // Test error handling 267 unittest 268 { 269 void clientMain() 270 { 271 try 272 { 273 scope (exit) 274 { 275 server.stop(); 276 exitEventLoop(); 277 } 278 279 auto client = clientTCP!(API)("localhost", 8030); 280 assertThrown!RPCException(client.remoteException()); 281 auto e = collectException!RPCException(client.remoteException); 282 debug 283 { 284 import std.algorithm : canFind; 285 286 // Contains complete backtrace 287 assert(e.msg.canFind("A error message")); 288 assert(e.file.canFind("test.d")); 289 assert(e.line == 160); 290 } 291 292 client.closeTCP(); 293 } 294 catch (Exception e) 295 assert(false, "Should not throw: " ~ e.toString()); 296 } 297 298 server = serveTCP!(APIServer, API)(8030); 299 runTask( & clientMain); 300 runEventLoop(); 301 } 302 303 // Test error handling in client, invalid server responses 304 unittest 305 { 306 TCPListener[] listener; 307 308 void clientMain() 309 { 310 try 311 { 312 scope (exit) 313 { 314 foreach (l; listener) 315 l.stopListening(); 316 exitEventLoop(); 317 } 318 319 auto client = clientTCP!(API)("localhost", 8030); 320 client.twoEvent ~= (int, int) 321 { 322 assert(false, "Shouldn't get called"); 323 }; 324 bool called = false; 325 client.onDisconnect ~= (RPCClient!(API, TCPConnection)) 326 { 327 called = true; 328 }; 329 // Real return value is int, here typed as void 330 client.request1(); 331 // Test proper recovery 332 client.request1(); 333 334 // Test wrong result type 335 assertThrown(client.request1(42)); 336 assert(client.connected); 337 assert(!called); 338 339 client.request1(); 340 341 client.closeTCP(); 342 } 343 catch (Exception e) 344 assert(false, "Should not throw: " ~ e.toString()); 345 } 346 347 listener = listenTCP(8030, delegate(TCPConnection conn) 348 { 349 import vibe.rpcchannel.protocol; 350 351 uint readCall() 352 { 353 auto type = conn.deserializeJsonLine!RequestType(); auto call = conn 354 .deserializeJsonLine!CallMessage(); for (size_t i = 0; i < call.parameters; 355 i++) 356 conn.deserializeJsonLine!void(); return call.id;} 357 358 // Client expects void result 359 auto id = readCall(); conn.serializeToJsonLine(ResponseType.result); 360 conn.serializeToJsonLine(ResultMessage(id, true)); conn.serializeToJsonLine( 361 42); // Send an event with wrong number of parameters 362 conn.serializeToJsonLine(ResponseType.event); conn.serializeToJsonLine( 363 EventMessage("twoEvent", 1)); conn.serializeToJsonLine("wrong type!"); 364 365 // Send an event with wrong type of parameter 366 conn.serializeToJsonLine(ResponseType.event); conn.serializeToJsonLine( 367 EventMessage("twoEvent", 2)); conn.serializeToJsonLine("wrong type!"); 368 conn.serializeToJsonLine(42); id = readCall(); conn.serializeToJsonLine( 369 ResponseType.result); conn.serializeToJsonLine(ResultMessage(id, 370 true)); conn.serializeToJsonLine(42); // Client expects int result 371 id = readCall(); conn.serializeToJsonLine(ResponseType.result); 372 conn.serializeToJsonLine(ResultMessage(id, true)); conn.serializeToJsonLine( 373 "0"); // Client expects void result 374 id = readCall(); conn.serializeToJsonLine(ResponseType.result); 375 conn.serializeToJsonLine(ResultMessage(id, true)); conn.serializeToJsonLine( 376 42);}); 377 378 runTask( & clientMain); 379 runEventLoop(); 380 } 381 382 // Test error handling in client, invalid protocol 383 unittest 384 { 385 TCPListener[] listener; 386 387 int clientsDone = 0; 388 389 void clientMain(int mode)() 390 { 391 try 392 { 393 scope (exit) 394 { 395 if (++clientsDone == 4) 396 { 397 foreach (l; listener) 398 l.stopListening(); 399 exitEventLoop(); 400 } 401 } 402 403 auto client = clientTCP!(API)("localhost", 8030); 404 bool called = false; 405 client.onDisconnect ~= (RPCClient!(API, TCPConnection)) 406 { 407 called = true; 408 }; 409 410 static if (mode == 0) 411 { 412 // Server sends invalid message type 413 assertThrown(client.request1(mode)); 414 } 415 static if (mode == 1) 416 { 417 // Server sends invalid event body type 418 assertThrown(client.request1(mode)); 419 } 420 static if (mode == 2) 421 { 422 // Server sends invalid message body 423 assertThrown(client.request1(mode)); 424 } 425 static if (mode == 3) 426 { 427 // Server sends invalid error body 428 assertThrown(client.request1(mode)); 429 } 430 assert(called); 431 assert(!client.connected); 432 433 client.closeTCP(); 434 } 435 catch (Exception e) 436 assert(false, "Should not throw: " ~ e.toString()); 437 } 438 439 listener = listenTCP(8030, delegate(TCPConnection conn) 440 { 441 import vibe.rpcchannel.protocol; 442 443 uint mode = 0; uint readCall() 444 { 445 auto type = conn.deserializeJsonLine!RequestType(); auto call = conn 446 .deserializeJsonLine!CallMessage(); mode = conn.deserializeJsonLine!int(); 447 return call.id;} 448 449 // Client expects void result 450 auto id = readCall(); switch (mode) 451 { 452 case 0 : conn.serializeToJsonLine(42); break; case 1 : conn.serializeToJsonLine( 453 ResponseType.event); conn.serializeToJsonLine("Hello"); 454 break; case 2 : conn.serializeToJsonLine(ResponseType.result); 455 conn.serializeToJsonLine("Hello"); break; case 3 : conn.serializeToJsonLine( 456 ResponseType.error); conn.serializeToJsonLine("Hello"); 457 break; default : break;} 458 }); 459 460 runTask( & clientMain!0); 461 runTask( & clientMain!1); 462 runTask( & clientMain!2); 463 runTask( & clientMain!3); 464 runEventLoop(); 465 } 466 467 // Test disconnects 468 // Server disconnect, from external task 469 unittest 470 { 471 void clientMain() 472 { 473 try 474 { 475 scope (exit) 476 { 477 exitEventLoop(); 478 } 479 480 auto addr = resolveHost("127.0.0.1"); 481 addr.port = 8030; 482 bool called = false; 483 auto client = clientTCP!(API)(addr); 484 client.onDisconnect ~= (RPCClient!(API, TCPConnection)) 485 { 486 called = true; 487 }; 488 server.stop(); 489 sleep(10.msecs); 490 assert(called); 491 assertThrown!DisconnectedException(client.getCounter()); 492 called = false; 493 client.closeTCP(); 494 assert(!called); 495 } 496 catch (Exception e) 497 assert(false, "Should not throw: " ~ e.toString()); 498 } 499 500 server = serveTCP!(APIServer, API)(8030, "127.0.0.1"); 501 runTask( & clientMain); 502 runEventLoop(); 503 } 504 505 // Server disconnect, from same task 506 unittest 507 { 508 void clientMain() 509 { 510 try 511 { 512 scope (exit) 513 { 514 exitEventLoop(); 515 } 516 517 bool called = false; 518 auto client = clientTCP!(API)("localhost", 8030); 519 client.onDisconnect ~= (RPCClient!(API, TCPConnection)) 520 { 521 called = true; 522 }; 523 // Disconnects before server sends result 524 assertThrown!DisconnectedException(client.stopServer()); 525 sleep(10.msecs); 526 assert(called); 527 assertThrown!DisconnectedException(client.getCounter()); 528 called = false; 529 client.closeTCP(); 530 assert(!called); 531 } 532 catch (Exception e) 533 assert(false, "Should not throw: " ~ e.toString()); 534 } 535 536 server = serveTCP!(APIServer, API)(8030); 537 runTask( & clientMain); 538 runEventLoop(); 539 } 540 541 // Server disconnect and exception, from same task 542 unittest 543 { 544 void clientMain() 545 { 546 try 547 { 548 scope (exit) 549 { 550 exitEventLoop(); 551 } 552 553 bool called = false; 554 auto client = clientTCP!(API)("localhost", 8030); 555 client.onDisconnect ~= (RPCClient!(API, TCPConnection)) 556 { 557 called = true; 558 }; 559 // Disconnects before server sends result 560 assertThrown!DisconnectedException(client.stopServerException()); 561 sleep(10.msecs); 562 assert(called); 563 assertThrown!DisconnectedException(client.getCounter()); 564 called = false; 565 client.closeTCP(); 566 assert(!called); 567 } 568 catch (Exception e) 569 assert(false, "Should not throw: " ~ e.toString()); 570 } 571 572 server = serveTCP!(APIServer, API)(8030); 573 runTask( & clientMain); 574 runEventLoop(); 575 } 576 577 // Test error handling in server, invalid client requests 578 unittest 579 { 580 import vibe.rpcchannel.protocol; 581 582 void clientMain() 583 { 584 try 585 { 586 scope (exit) 587 { 588 server.stop(); 589 exitEventLoop(); 590 } 591 592 auto conn = connectTCP("127.0.0.1", 8030); 593 594 void readError() 595 { 596 auto type = conn.deserializeJsonLine!ResponseType(); 597 assert(type == ResponseType.error); 598 auto err = conn.deserializeJsonLine!ErrorMessage(); 599 } 600 601 // Wrong number of parameters 602 conn.serializeToJsonLine(RequestType.call); 603 conn.serializeToJsonLine(CallMessage(0, "request1", 604 "FiZi", 0)); 605 readError(); 606 607 // Wrong parameter type 608 conn.serializeToJsonLine(RequestType.call); 609 conn.serializeToJsonLine(CallMessage(0, "request1", 610 "FiZi", 1)); 611 conn.serializeToJsonLine("Hello"); 612 readError(); 613 614 // Unknown mangle 615 conn.serializeToJsonLine(RequestType.call); 616 conn.serializeToJsonLine(CallMessage(0, "request1", 617 "abcd", 3)); 618 conn.serializeToJsonLine("Hello"); 619 conn.serializeToJsonLine(42); 620 conn.serializeToJsonLine(ErrorMessage.init); 621 readError(); 622 623 // Unknown function 624 conn.serializeToJsonLine(RequestType.call); 625 conn.serializeToJsonLine(CallMessage(0, 626 "request1abcd", "abcd", 3)); 627 conn.serializeToJsonLine("Hello"); 628 conn.serializeToJsonLine(42); 629 conn.serializeToJsonLine(ErrorMessage.init); 630 readError(); 631 632 conn.close(); 633 } 634 catch (Exception e) 635 assert(false, "Should not throw: " ~ e.toString()); 636 } 637 638 server = serveTCP!(APIServer, API)(8030); 639 runTask( & clientMain); 640 runEventLoop(); 641 } 642 643 // Test error handling in server, invalid protocol 644 unittest 645 { 646 import vibe.rpcchannel.protocol; 647 648 size_t clientDone; 649 void clientMain(int mode)() 650 { 651 try 652 { 653 scope (exit) 654 { 655 if (++clientDone == 2) 656 { 657 assert(numDestroyed == 2); 658 server.stop(); 659 exitEventLoop(); 660 } 661 } 662 663 auto conn = connectTCP("127.0.0.1", 8030); 664 665 static if (mode == 0) 666 { 667 // Invalid request 668 conn.serializeToJsonLine(42); 669 collectException(conn.deserializeJsonLine!int()); 670 } 671 else 672 { 673 // Invalid call body 674 conn.serializeToJsonLine(RequestType.call); 675 conn.serializeToJsonLine("foo"); 676 } 677 678 conn.close(); 679 } 680 catch (Exception e) 681 assert(false, "Should not throw: " ~ e.toString()); 682 } 683 684 numDestroyed = 0; 685 server = serveTCP!(APIServer, API)(8030); 686 runTask( & clientMain!0); 687 runTask( & clientMain!1); 688 runEventLoop(); 689 } 690 691 // Test heavy event / call interleaving to make sure data does not get 692 // interleaved on the connection 693 // Test basic functions 694 unittest 695 { 696 uint done = 0; 697 698 void clientMain() 699 { 700 try 701 { 702 scope (exit) 703 { 704 done++; 705 if (done == 100) 706 { 707 // We eant to test that the client disconnects first! 708 sleep(10.msecs); 709 server.stop(); 710 exitEventLoop(); 711 } 712 } 713 714 auto client = clientTCP!(API)("localhost", 8030); 715 client.startEvents(); 716 for (size_t i = 0; i < 1000; i++) 717 { 718 client.request1(); 719 } 720 client.stopEvents(); 721 722 client.closeTCP(); 723 } 724 catch (Exception e) 725 assert(false, "Should not throw: " ~ e.toString()); 726 } 727 728 server = serveTCP!(APIServer, API)(8030); 729 for (size_t i = 0; i < 100; i++) 730 runTask( & clientMain); 731 runEventLoop(); 732 }