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                 }