blob: 5e4d91c3351e3f43c941ddeafcfb93525df18bb0 [file] [log] [blame]
Jens Geyer7bb44a32014-02-07 22:24:37 +01001(*
Roger Meier3bef8c22012-10-06 06:58:00 +00002 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *)
19
20unit TestClient;
21
Jens Geyer06045cf2013-03-27 20:26:25 +020022{.$DEFINE StressTest} // activate to stress-test the server with frequent connects/disconnects
23{.$DEFINE PerfTest} // activate to activate the performance test
24
Roger Meier3bef8c22012-10-06 06:58:00 +000025interface
26
27uses
28 Windows, SysUtils, Classes,
29 DateUtils,
30 Generics.Collections,
31 TestConstants,
32 Thrift,
33 Thrift.Protocol.JSON,
34 Thrift.Protocol,
35 Thrift.Transport.Pipes,
36 Thrift.Transport,
37 Thrift.Stream,
38 Thrift.Test,
39 Thrift.Collections,
40 Thrift.Console;
41
42type
43 TThreadConsole = class
44 private
45 FThread : TThread;
46 public
47 procedure Write( const S : string);
48 procedure WriteLine( const S : string);
49 constructor Create( AThread: TThread);
50 end;
51
52 TClientThread = class( TThread )
53 private
54 FTransport : ITransport;
55 FProtocol : IProtocol;
56 FNumIteration : Integer;
57 FConsole : TThreadConsole;
58
59 // test reporting, will be refactored out into separate class later
60 FTestGroup : string;
61 FSuccesses : Integer;
62 FErrors : TStringList;
63 procedure StartTestGroup( const aGroup : string);
64 procedure Expect( aTestResult : Boolean; const aTestInfo : string);
65 procedure ReportResults;
66
67 procedure ClientTest;
68 procedure JSONProtocolReadWriteTest;
Jens Geyer718f6ee2013-09-06 21:02:34 +020069 {$IFDEF StressTest}
Jens Geyer06045cf2013-03-27 20:26:25 +020070 procedure StressTest(const client : TThriftTest.Iface);
Jens Geyer718f6ee2013-09-06 21:02:34 +020071 {$ENDIF}
Roger Meier3bef8c22012-10-06 06:58:00 +000072 protected
73 procedure Execute; override;
74 public
75 constructor Create( const ATransport: ITransport; const AProtocol : IProtocol; ANumIteration: Integer);
76 destructor Destroy; override;
77 end;
78
79 TTestClient = class
80 private
81 class var
82 FNumIteration : Integer;
83 FNumThread : Integer;
84 public
85 class procedure Execute( const args: array of string);
86 end;
87
88implementation
89
90function BoolToString( b : Boolean) : string;
91// overrides global BoolToString()
92begin
93 if b
94 then result := 'true'
95 else result := 'false';
96end;
97
98// not available in all versions, so make sure we have this one imported
99function IsDebuggerPresent: BOOL; stdcall; external KERNEL32 name 'IsDebuggerPresent';
100
101{ TTestClient }
102
103class procedure TTestClient.Execute(const args: array of string);
104var
105 i : Integer;
106 host : string;
107 port : Integer;
108 url : string;
109 bBuffered : Boolean;
110 bAnonPipe : Boolean;
111 bFramed : Boolean;
112 sPipeName : string;
113 hAnonRead, hAnonWrite : THandle;
114 s : string;
115 n : Integer;
116 threads : array of TThread;
117 dtStart : TDateTime;
118 test : Integer;
119 thread : TThread;
120 trans : ITransport;
121 prot : IProtocol;
122 streamtrans : IStreamTransport;
123 http : IHTTPClient;
124 protType, p : TKnownProtocol;
Jens Geyer0b20cc82013-03-07 20:47:01 +0100125const
126 // pipe timeouts to be used
127 DEBUG_TIMEOUT = 30 * 1000;
128 RELEASE_TIMEOUT = DEFAULT_THRIFT_PIPE_TIMEOUT;
129 TIMEOUT = RELEASE_TIMEOUT;
Roger Meier3bef8c22012-10-06 06:58:00 +0000130begin
131 bBuffered := False;;
132 bFramed := False;
133 protType := prot_Binary;
134 try
135 host := 'localhost';
136 port := 9090;
137 url := '';
138 sPipeName := '';
139 bAnonPipe := FALSE;
140 hAnonRead := INVALID_HANDLE_VALUE;
141 hAnonWrite := INVALID_HANDLE_VALUE;
142 i := 0;
143 try
144 while ( i < Length(args) ) do
145 begin
146
147 try
148 if ( args[i] = '-h') then
149 begin
150 Inc( i );
151 s := args[i];
152 n := Pos( ':', s);
153 if ( n > 0 ) then
154 begin
155 host := Copy( s, 1, n - 1);
156 port := StrToInt( Copy( s, n + 1, MaxInt));
157 end else
158 begin
159 host := s;
160 end;
161 end
162 else if (args[i] = '-u') then
163 begin
164 Inc( i );
165 url := args[i];
166 end
167 else if (args[i] = '-n') then
168 begin
169 Inc( i );
170 FNumIteration := StrToInt( args[i] );
171 end
172 else if (args[i] = '-b') then
173 begin
174 bBuffered := True;
175 Console.WriteLine('Buffered transport');
176 end
177 else if (args[i] = '-f' ) or ( args[i] = '-framed') then
178 begin
179 bFramed := True;
180 Console.WriteLine('Framed transport');
181 end
182 else if (args[i] = '-pipe') then // -pipe <name>
183 begin
184 Console.WriteLine('Named pipes transport');
185 Inc( i );
186 sPipeName := args[i];
187 end
188 else if (args[i] = '-anon') then // -anon <hReadPipe> <hWritePipe>
189 begin
Jens Geyerb64a7742013-01-23 20:58:47 +0100190 if Length(args) <= (i+2) then begin
191 Console.WriteLine('Invalid args: -anon <hRead> <hWrite> or use "server.exe -anon"');
192 Halt(1);
193 end;
Roger Meier3bef8c22012-10-06 06:58:00 +0000194 Console.WriteLine('Anonymous pipes transport');
195 Inc( i);
196 hAnonRead := THandle( StrToIntDef( args[i], Integer(INVALID_HANDLE_VALUE)));
197 Inc( i);
198 hAnonWrite := THandle( StrToIntDef( args[i], Integer(INVALID_HANDLE_VALUE)));
199 bAnonPipe := TRUE;
200 end
201 else if (args[i] = '-t') then
202 begin
203 Inc( i );
204 FNumThread := StrToInt( args[i] );
205 end
206 else if (args[i] = '-prot') then // -prot JSON|binary
207 begin
208 Inc( i );
209 s := args[i];
210 for p:= Low(TKnownProtocol) to High(TKnownProtocol) do begin
211 if SameText( s, KNOWN_PROTOCOLS[p]) then begin
212 protType := p;
213 Console.WriteLine('Using '+KNOWN_PROTOCOLS[protType]+' protocol');
214 Break;
215 end;
216 end;
217 end;
218 finally
219 Inc( i );
220 end;
221
222 end;
223
224 except
225 on E: Exception do
226 begin
227 Console.WriteLine( E.Message );
228 end;
229 end;
230
Roger Meier79655fb2012-10-20 20:59:41 +0000231 // In the anonymous pipes mode the client is launched by the test server
232 // -> behave nicely and allow for attaching a debugger to this process
233 if bAnonPipe and not IsDebuggerPresent
234 then MessageBox( 0, 'Attach Debugger and/or click OK to continue.',
235 'Thrift TestClient (Delphi)',
236 MB_OK or MB_ICONEXCLAMATION);
237
Roger Meier3bef8c22012-10-06 06:58:00 +0000238 SetLength( threads, FNumThread);
239 dtStart := Now;
240
241 for test := 0 to FNumThread - 1 do
242 begin
243 if url = '' then
244 begin
245 if sPipeName <> '' then begin
246 Console.WriteLine('Using named pipe ('+sPipeName+')');
Jens Geyer06045cf2013-03-27 20:26:25 +0200247 streamtrans := TNamedPipeTransportClientEndImpl.Create( sPipeName, 0, nil, TIMEOUT);
Roger Meier3bef8c22012-10-06 06:58:00 +0000248 end
249 else if bAnonPipe then begin
250 Console.WriteLine('Using anonymous pipes ('+IntToStr(Integer(hAnonRead))+' and '+IntToStr(Integer(hAnonWrite))+')');
Jens Geyer06045cf2013-03-27 20:26:25 +0200251 streamtrans := TAnonymousPipeTransportImpl.Create( hAnonRead, hAnonWrite, FALSE);
Roger Meier3bef8c22012-10-06 06:58:00 +0000252 end
253 else begin
254 Console.WriteLine('Using sockets ('+host+' port '+IntToStr(port)+')');
255 streamtrans := TSocketImpl.Create( host, port );
256 end;
257
258 trans := streamtrans;
259
260 if bBuffered then begin
Jens Geyer0b20cc82013-03-07 20:47:01 +0100261 trans := TBufferedTransportImpl.Create( streamtrans, 32); // small buffer to test read()
Roger Meier3bef8c22012-10-06 06:58:00 +0000262 Console.WriteLine('Using buffered transport');
263 end;
264
265 if bFramed then begin
266 trans := TFramedTransportImpl.Create( trans );
267 Console.WriteLine('Using framed transport');
268 end;
269
270 end
271 else begin
272 Console.WriteLine('Using HTTPClient');
273 http := THTTPClientImpl.Create( url );
274 trans := http;
275 end;
276
277 // create protocol instance, default to BinaryProtocol
278 case protType of
Jens Geyer0b20cc82013-03-07 20:47:01 +0100279 prot_Binary: prot := TBinaryProtocolImpl.Create( trans, BINARY_STRICT_READ, BINARY_STRICT_WRITE);
Roger Meier3bef8c22012-10-06 06:58:00 +0000280 prot_JSON : prot := TJSONProtocolImpl.Create( trans);
281 else
282 ASSERT( FALSE); // unhandled case!
Jens Geyer0b20cc82013-03-07 20:47:01 +0100283 prot := TBinaryProtocolImpl.Create( trans, BINARY_STRICT_READ, BINARY_STRICT_WRITE); // use default
Roger Meier3bef8c22012-10-06 06:58:00 +0000284 end;
285
286 thread := TClientThread.Create( trans, prot, FNumIteration);
287 threads[test] := thread;
288{$WARN SYMBOL_DEPRECATED OFF}
289 thread.Resume;
290{$WARN SYMBOL_DEPRECATED ON}
291 end;
292
293 for test := 0 to FNumThread - 1 do
294 begin
295 threads[test].WaitFor;
296 end;
297
298 for test := 0 to FNumThread - 1 do
299 begin
300 threads[test].Free;
301 end;
302
303 Console.Write('Total time: ' + IntToStr( MilliSecondsBetween(Now, dtStart)));
304
305 except
306 on E: Exception do
307 begin
308 Console.WriteLine( E.Message + ' ST: ' + E.StackTrace );
309 end;
310 end;
311
312 Console.WriteLine('');
313 Console.WriteLine('done!');
314end;
315
316{ TClientThread }
317
318procedure TClientThread.ClientTest;
319var
320 client : TThriftTest.Iface;
321 s : string;
322 i8 : ShortInt;
323 i32 : Integer;
324 i64 : Int64;
325 dub : Double;
326 o : IXtruct;
327 o2 : IXtruct2;
328 i : IXtruct;
329 i2 : IXtruct2;
330 mapout : IThriftDictionary<Integer,Integer>;
331 mapin : IThriftDictionary<Integer,Integer>;
332 strmapout : IThriftDictionary<string,string>;
333 strmapin : IThriftDictionary<string,string>;
334 j : Integer;
335 first : Boolean;
336 key : Integer;
337 strkey : string;
338 listout : IThriftList<Integer>;
339 listin : IThriftList<Integer>;
340 setout : IHashSet<Integer>;
341 setin : IHashSet<Integer>;
342 ret : TNumberz;
343 uid : Int64;
344 mm : IThriftDictionary<Integer, IThriftDictionary<Integer, Integer>>;
345 pos : IThriftDictionary<Integer, Integer>;
346 neg : IThriftDictionary<Integer, Integer>;
347 m2 : IThriftDictionary<Integer, Integer>;
348 k2 : Integer;
349 insane : IInsanity;
350 truck : IXtruct;
351 whoa : IThriftDictionary<Int64, IThriftDictionary<TNumberz, IInsanity>>;
352 key64 : Int64;
353 val : IThriftDictionary<TNumberz, IInsanity>;
354 k2_2 : TNumberz;
355 k3 : TNumberz;
356 v2 : IInsanity;
357 userMap : IThriftDictionary<TNumberz, Int64>;
358 xtructs : IThriftList<IXtruct>;
359 x : IXtruct;
360 arg0 : ShortInt;
361 arg1 : Integer;
362 arg2 : Int64;
363 arg3 : IThriftDictionary<SmallInt, string>;
364 arg4 : TNumberz;
365 arg5 : Int64;
366 StartTick : Cardinal;
367 k : Integer;
368 proc : TThreadProcedure;
369 hello, goodbye : IXtruct;
370 crazy : IInsanity;
371 looney : IInsanity;
372 first_map : IThriftDictionary<TNumberz, IInsanity>;
373 second_map : IThriftDictionary<TNumberz, IInsanity>;
374
375begin
376 client := TThriftTest.TClient.Create( FProtocol);
377 FTransport.Open;
378
Jens Geyer06045cf2013-03-27 20:26:25 +0200379 {$IFDEF StressTest}
380 StressTest( client);
381 {$ENDIF StressTest}
382
Roger Meier3bef8c22012-10-06 06:58:00 +0000383 // in-depth exception test
384 // (1) do we get an exception at all?
385 // (2) do we get the right exception?
386 // (3) does the exception contain the expected data?
387 StartTestGroup( 'testException');
388 // case 1: exception type declared in IDL at the function call
389 try
390 client.testException('Xception');
391 Expect( FALSE, 'testException(''Xception''): must trow an exception');
392 except
393 on e:TXception do begin
394 Expect( e.ErrorCode = 1001, 'error code');
395 Expect( e.Message_ = 'Xception', 'error message');
396 Console.WriteLine( ' = ' + IntToStr(e.ErrorCode) + ', ' + e.Message_ );
397 end;
398 on e:TTransportException do Expect( FALSE, 'Unexpected : "'+e.ToString+'"');
399 on e:Exception do Expect( FALSE, 'Unexpected exception type "'+e.ClassName+'"');
400 end;
401
402 // case 2: exception type NOT declared in IDL at the function call
403 // this will close the connection
404 try
405 client.testException('TException');
406 Expect( FALSE, 'testException(''TException''): must trow an exception');
407 except
408 on e:TTransportException do begin
409 Console.WriteLine( e.ClassName+' = '+e.Message); // this is what we get
410 if FTransport.IsOpen then FTransport.Close;
411 FTransport.Open; // re-open connection, server has already closed
412 end;
Jens Geyer6bbbf192014-09-07 01:45:56 +0200413 on e:TApplicationException do begin
414 Console.WriteLine( e.ClassName+' = '+e.Message); // this is what we get
415 if FTransport.IsOpen then FTransport.Close;
416 FTransport.Open; // re-open connection, server has already closed
417 end;
Roger Meier3bef8c22012-10-06 06:58:00 +0000418 on e:TException do Expect( FALSE, 'Unexpected exception type "'+e.ClassName+'"');
419 on e:Exception do Expect( FALSE, 'Unexpected exception type "'+e.ClassName+'"');
420 end;
421
422 // case 3: no exception
423 try
424 client.testException('something');
425 Expect( TRUE, 'testException(''something''): must not trow an exception');
426 except
427 on e:TTransportException do Expect( FALSE, 'Unexpected : "'+e.ToString+'"');
428 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
429 end;
430
431
432 // simple things
433 StartTestGroup( 'simple Thrift calls');
434 client.testVoid();
435 Expect( TRUE, 'testVoid()'); // success := no exception
436
437 s := client.testString('Test');
438 Expect( s = 'Test', 'testString(''Test'') = "'+s+'"');
439
Jens Geyer06045cf2013-03-27 20:26:25 +0200440 s := client.testString(HUGE_TEST_STRING);
441 Expect( length(s) = length(HUGE_TEST_STRING),
442 'testString( lenght(HUGE_TEST_STRING) = '+IntToStr(Length(HUGE_TEST_STRING))+') '
443 +'=> length(result) = '+IntToStr(Length(s)));
444
Roger Meier3bef8c22012-10-06 06:58:00 +0000445 i8 := client.testByte(1);
446 Expect( i8 = 1, 'testByte(1) = ' + IntToStr( i8 ));
447
448 i32 := client.testI32(-1);
449 Expect( i32 = -1, 'testI32(-1) = ' + IntToStr(i32));
450
451 Console.WriteLine('testI64(-34359738368)');
452 i64 := client.testI64(-34359738368);
453 Expect( i64 = -34359738368, 'testI64(-34359738368) = ' + IntToStr( i64));
454
455 Console.WriteLine('testDouble(5.325098235)');
456 dub := client.testDouble(5.325098235);
457 Expect( abs(dub-5.325098235) < 1e-14, 'testDouble(5.325098235) = ' + FloatToStr( dub));
458
459 // structs
460 StartTestGroup( 'testStruct');
461 Console.WriteLine('testStruct({''Zero'', 1, -3, -5})');
462 o := TXtructImpl.Create;
463 o.String_thing := 'Zero';
464 o.Byte_thing := 1;
465 o.I32_thing := -3;
466 o.I64_thing := -5;
467 i := client.testStruct(o);
468 Expect( i.String_thing = 'Zero', 'i.String_thing = "'+i.String_thing+'"');
469 Expect( i.Byte_thing = 1, 'i.Byte_thing = '+IntToStr(i.Byte_thing));
470 Expect( i.I32_thing = -3, 'i.I32_thing = '+IntToStr(i.I32_thing));
471 Expect( i.I64_thing = -5, 'i.I64_thing = '+IntToStr(i.I64_thing));
472 Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
473 Expect( i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
474 Expect( i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
475 Expect( i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
476
477 // nested structs
478 StartTestGroup( 'testNest');
479 Console.WriteLine('testNest({1, {''Zero'', 1, -3, -5}, 5})');
480 o2 := TXtruct2Impl.Create;
481 o2.Byte_thing := 1;
482 o2.Struct_thing := o;
483 o2.I32_thing := 5;
484 i2 := client.testNest(o2);
485 i := i2.Struct_thing;
486 Expect( i.String_thing = 'Zero', 'i.String_thing = "'+i.String_thing+'"');
487 Expect( i.Byte_thing = 1, 'i.Byte_thing = '+IntToStr(i.Byte_thing));
488 Expect( i.I32_thing = -3, 'i.I32_thing = '+IntToStr(i.I32_thing));
489 Expect( i.I64_thing = -5, 'i.I64_thing = '+IntToStr(i.I64_thing));
490 Expect( i2.Byte_thing = 1, 'i2.Byte_thing = '+IntToStr(i2.Byte_thing));
491 Expect( i2.I32_thing = 5, 'i2.I32_thing = '+IntToStr(i2.I32_thing));
492 Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
493 Expect( i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
494 Expect( i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
495 Expect( i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
496 Expect( i2.__isset_Byte_thing, 'i2.__isset_Byte_thing');
497 Expect( i2.__isset_I32_thing, 'i2.__isset_I32_thing');
498
499 // map<type1,type2>: A map of strictly unique keys to values.
500 // Translates to an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc.
501 StartTestGroup( 'testMap');
502 mapout := TThriftDictionaryImpl<Integer,Integer>.Create;
503 for j := 0 to 4 do
504 begin
505 mapout.AddOrSetValue( j, j - 10);
506 end;
507 Console.Write('testMap({');
508 first := True;
509 for key in mapout.Keys do
510 begin
511 if first
512 then first := False
513 else Console.Write( ', ' );
514 Console.Write( IntToStr( key) + ' => ' + IntToStr( mapout[key]));
515 end;
516 Console.WriteLine('})');
517
518 mapin := client.testMap( mapout );
519 Expect( mapin.Count = mapout.Count, 'testMap: mapin.Count = mapout.Count');
520 for j := 0 to 4 do
521 begin
522 Expect( mapout.ContainsKey(j), 'testMap: mapout.ContainsKey('+IntToStr(j)+') = '+BoolToString(mapout.ContainsKey(j)));
523 end;
524 for key in mapin.Keys do
525 begin
526 Expect( mapin[key] = mapout[key], 'testMap: '+IntToStr(key) + ' => ' + IntToStr( mapin[key]));
527 Expect( mapin[key] = key - 10, 'testMap: mapin['+IntToStr(key)+'] = '+IntToStr( mapin[key]));
528 end;
529
530
531 // map<type1,type2>: A map of strictly unique keys to values.
532 // Translates to an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc.
533 StartTestGroup( 'testStringMap');
534 strmapout := TThriftDictionaryImpl<string,string>.Create;
535 for j := 0 to 4 do
536 begin
537 strmapout.AddOrSetValue( IntToStr(j), IntToStr(j - 10));
538 end;
539 Console.Write('testStringMap({');
540 first := True;
541 for strkey in strmapout.Keys do
542 begin
543 if first
544 then first := False
545 else Console.Write( ', ' );
546 Console.Write( strkey + ' => ' + strmapout[strkey]);
547 end;
548 Console.WriteLine('})');
549
550 strmapin := client.testStringMap( strmapout );
551 Expect( strmapin.Count = strmapout.Count, 'testStringMap: strmapin.Count = strmapout.Count');
552 for j := 0 to 4 do
553 begin
554 Expect( strmapout.ContainsKey(IntToStr(j)),
555 'testStringMap: strmapout.ContainsKey('+IntToStr(j)+') = '
556 + BoolToString(strmapout.ContainsKey(IntToStr(j))));
557 end;
558 for strkey in strmapin.Keys do
559 begin
560 Expect( strmapin[strkey] = strmapout[strkey], 'testStringMap: '+strkey + ' => ' + strmapin[strkey]);
561 Expect( strmapin[strkey] = IntToStr( StrToInt(strkey) - 10), 'testStringMap: strmapin['+strkey+'] = '+strmapin[strkey]);
562 end;
563
564
565 // set<type>: An unordered set of unique elements.
566 // Translates to an STL set, Java HashSet, set in Python, etc.
567 // Note: PHP does not support sets, so it is treated similar to a List
568 StartTestGroup( 'testSet');
569 setout := THashSetImpl<Integer>.Create;
570 for j := -2 to 2 do
571 begin
572 setout.Add( j );
573 end;
574 Console.Write('testSet({');
575 first := True;
576 for j in setout do
577 begin
578 if first
579 then first := False
580 else Console.Write(', ');
581 Console.Write(IntToStr( j));
582 end;
583 Console.WriteLine('})');
584
585 setin := client.testSet(setout);
586 Expect( setin.Count = setout.Count, 'testSet: setin.Count = setout.Count');
587 Expect( setin.Count = 5, 'testSet: setin.Count = '+IntToStr(setin.Count));
588 for j := -2 to 2 do // unordered, we can't rely on the order => test for known elements only
589 begin
590 Expect( setin.Contains(j), 'testSet: setin.Contains('+IntToStr(j)+') => '+BoolToString(setin.Contains(j)));
591 end;
592
593 // list<type>: An ordered list of elements.
594 // Translates to an STL vector, Java ArrayList, native arrays in scripting languages, etc.
595 StartTestGroup( 'testList');
596 listout := TThriftListImpl<Integer>.Create;
597 listout.Add( +1);
598 listout.Add( -2);
599 listout.Add( +3);
600 listout.Add( -4);
601 listout.Add( 0);
602 Console.Write('testList({');
603 first := True;
604 for j in listout do
605 begin
606 if first
607 then first := False
608 else Console.Write(', ');
609 Console.Write(IntToStr( j));
610 end;
611 Console.WriteLine('})');
612
613 listin := client.testList(listout);
614 Expect( listin.Count = listout.Count, 'testList: listin.Count = listout.Count');
615 Expect( listin.Count = 5, 'testList: listin.Count = '+IntToStr(listin.Count));
616 Expect( listin[0] = +1, 'listin[0] = '+IntToStr( listin[0]));
617 Expect( listin[1] = -2, 'listin[1] = '+IntToStr( listin[1]));
618 Expect( listin[2] = +3, 'listin[2] = '+IntToStr( listin[2]));
619 Expect( listin[3] = -4, 'listin[3] = '+IntToStr( listin[3]));
620 Expect( listin[4] = 0, 'listin[4] = '+IntToStr( listin[4]));
621
622 // enums
623 ret := client.testEnum(TNumberz.ONE);
624 Expect( ret = TNumberz.ONE, 'testEnum(ONE) = '+IntToStr(Ord(ret)));
625
626 ret := client.testEnum(TNumberz.TWO);
627 Expect( ret = TNumberz.TWO, 'testEnum(TWO) = '+IntToStr(Ord(ret)));
628
629 ret := client.testEnum(TNumberz.THREE);
630 Expect( ret = TNumberz.THREE, 'testEnum(THREE) = '+IntToStr(Ord(ret)));
631
632 ret := client.testEnum(TNumberz.FIVE);
633 Expect( ret = TNumberz.FIVE, 'testEnum(FIVE) = '+IntToStr(Ord(ret)));
634
635 ret := client.testEnum(TNumberz.EIGHT);
636 Expect( ret = TNumberz.EIGHT, 'testEnum(EIGHT) = '+IntToStr(Ord(ret)));
637
638
639 // typedef
640 uid := client.testTypedef(309858235082523);
641 Expect( uid = 309858235082523, 'testTypedef(309858235082523) = '+IntToStr(uid));
642
643
644 // maps of maps
645 StartTestGroup( 'testMapMap(1)');
646 mm := client.testMapMap(1);
647 Console.Write(' = {');
648 for key in mm.Keys do
649 begin
650 Console.Write( IntToStr( key) + ' => {');
651 m2 := mm[key];
652 for k2 in m2.Keys do
653 begin
654 Console.Write( IntToStr( k2) + ' => ' + IntToStr( m2[k2]) + ', ');
655 end;
656 Console.Write('}, ');
657 end;
658 Console.WriteLine('}');
659
660 // verify result data
661 Expect( mm.Count = 2, 'mm.Count = '+IntToStr(mm.Count));
662 pos := mm[4];
663 neg := mm[-4];
664 for j := 1 to 4 do
665 begin
666 Expect( pos[j] = j, 'pos[j] = '+IntToStr(pos[j]));
667 Expect( neg[-j] = -j, 'neg[-j] = '+IntToStr(neg[-j]));
668 end;
669
670
671
672 // insanity
673 StartTestGroup( 'testInsanity');
674 insane := TInsanityImpl.Create;
675 insane.UserMap := TThriftDictionaryImpl<TNumberz, Int64>.Create;
676 insane.UserMap.AddOrSetValue( TNumberz.FIVE, 5000);
677 truck := TXtructImpl.Create;
678 truck.String_thing := 'Truck';
679 truck.Byte_thing := 8;
680 truck.I32_thing := 8;
681 truck.I64_thing := 8;
682 insane.Xtructs := TThriftListImpl<IXtruct>.Create;
683 insane.Xtructs.Add( truck );
684 whoa := client.testInsanity( insane );
685 Console.Write(' = {');
686 for key64 in whoa.Keys do
687 begin
688 val := whoa[key64];
689 Console.Write( IntToStr( key64) + ' => {');
690 for k2_2 in val.Keys do
691 begin
692 v2 := val[k2_2];
693 Console.Write( IntToStr( Integer( k2_2)) + ' => {');
694 userMap := v2.UserMap;
695 Console.Write('{');
696 if userMap <> nil then
697 begin
698 for k3 in userMap.Keys do
699 begin
700 Console.Write( IntToStr( Integer( k3)) + ' => ' + IntToStr( userMap[k3]) + ', ');
701 end;
702 end else
703 begin
704 Console.Write('null');
705 end;
706 Console.Write('}, ');
707 xtructs := v2.Xtructs;
708 Console.Write('{');
709
710 if xtructs <> nil then
711 begin
712 for x in xtructs do
713 begin
714 Console.Write('{"' + x.String_thing + '", ' +
715 IntToStr( x.Byte_thing) + ', ' +
716 IntToStr( x.I32_thing) + ', ' +
717 IntToStr( x.I32_thing) + '}, ');
718 end;
719 end else
720 begin
721 Console.Write('null');
722 end;
723 Console.Write('}');
724 Console.Write('}, ');
725 end;
726 Console.Write('}, ');
727 end;
728 Console.WriteLine('}');
729
730 // verify result data
731 Expect( whoa.Count = 2, 'whoa.Count = '+IntToStr(whoa.Count));
732 //
733 first_map := whoa[1];
734 second_map := whoa[2];
735 Expect( first_map.Count = 2, 'first_map.Count = '+IntToStr(first_map.Count));
736 Expect( second_map.Count = 1, 'second_map.Count = '+IntToStr(second_map.Count));
737 //
738 looney := second_map[TNumberz.SIX];
739 Expect( Assigned(looney), 'Assigned(looney) = '+BoolToString(Assigned(looney)));
740 Expect( not looney.__isset_UserMap, 'looney.__isset_UserMap = '+BoolToString(looney.__isset_UserMap));
741 Expect( not looney.__isset_Xtructs, 'looney.__isset_Xtructs = '+BoolToString(looney.__isset_Xtructs));
742 //
743 for ret in [TNumberz.TWO, TNumberz.THREE] do begin
744 crazy := first_map[ret];
745 Console.WriteLine('first_map['+intToStr(Ord(ret))+']');
746
747 Expect( crazy.__isset_UserMap, 'crazy.__isset_UserMap = '+BoolToString(crazy.__isset_UserMap));
748 Expect( crazy.__isset_Xtructs, 'crazy.__isset_Xtructs = '+BoolToString(crazy.__isset_Xtructs));
749
750 Expect( crazy.UserMap.Count = 2, 'crazy.UserMap.Count = '+IntToStr(crazy.UserMap.Count));
751 Expect( crazy.UserMap[TNumberz.FIVE] = 5, 'crazy.UserMap[TNumberz.FIVE] = '+IntToStr(crazy.UserMap[TNumberz.FIVE]));
752 Expect( crazy.UserMap[TNumberz.EIGHT] = 8, 'crazy.UserMap[TNumberz.EIGHT] = '+IntToStr(crazy.UserMap[TNumberz.EIGHT]));
753
754 Expect( crazy.Xtructs.Count = 2, 'crazy.Xtructs.Count = '+IntToStr(crazy.Xtructs.Count));
755 goodbye := crazy.Xtructs[0]; // lists are ordered, so we are allowed to assume this order
756 hello := crazy.Xtructs[1];
757
758 Expect( goodbye.String_thing = 'Goodbye4', 'goodbye.String_thing = "'+goodbye.String_thing+'"');
759 Expect( goodbye.Byte_thing = 4, 'goodbye.Byte_thing = '+IntToStr(goodbye.Byte_thing));
760 Expect( goodbye.I32_thing = 4, 'goodbye.I32_thing = '+IntToStr(goodbye.I32_thing));
761 Expect( goodbye.I64_thing = 4, 'goodbye.I64_thing = '+IntToStr(goodbye.I64_thing));
762 Expect( goodbye.__isset_String_thing, 'goodbye.__isset_String_thing = '+BoolToString(goodbye.__isset_String_thing));
763 Expect( goodbye.__isset_Byte_thing, 'goodbye.__isset_Byte_thing = '+BoolToString(goodbye.__isset_Byte_thing));
764 Expect( goodbye.__isset_I32_thing, 'goodbye.__isset_I32_thing = '+BoolToString(goodbye.__isset_I32_thing));
765 Expect( goodbye.__isset_I64_thing, 'goodbye.__isset_I64_thing = '+BoolToString(goodbye.__isset_I64_thing));
766
767 Expect( hello.String_thing = 'Hello2', 'hello.String_thing = "'+hello.String_thing+'"');
768 Expect( hello.Byte_thing = 2, 'hello.Byte_thing = '+IntToStr(hello.Byte_thing));
769 Expect( hello.I32_thing = 2, 'hello.I32_thing = '+IntToStr(hello.I32_thing));
770 Expect( hello.I64_thing = 2, 'hello.I64_thing = '+IntToStr(hello.I64_thing));
771 Expect( hello.__isset_String_thing, 'hello.__isset_String_thing = '+BoolToString(hello.__isset_String_thing));
772 Expect( hello.__isset_Byte_thing, 'hello.__isset_Byte_thing = '+BoolToString(hello.__isset_Byte_thing));
773 Expect( hello.__isset_I32_thing, 'hello.__isset_I32_thing = '+BoolToString(hello.__isset_I32_thing));
774 Expect( hello.__isset_I64_thing, 'hello.__isset_I64_thing = '+BoolToString(hello.__isset_I64_thing));
775 end;
776
777
778 // multi args
779 StartTestGroup( 'testMulti');
780 arg0 := 1;
781 arg1 := 2;
782 arg2 := High(Int64);
783 arg3 := TThriftDictionaryImpl<SmallInt, string>.Create;
784 arg3.AddOrSetValue( 1, 'one');
785 arg4 := TNumberz.FIVE;
786 arg5 := 5000000;
787 Console.WriteLine('Test Multi(' + IntToStr( arg0) + ',' +
788 IntToStr( arg1) + ',' + IntToStr( arg2) + ',' +
789 arg3.ToString + ',' + IntToStr( Integer( arg4)) + ',' +
790 IntToStr( arg5) + ')');
791
792 i := client.testMulti( arg0, arg1, arg2, arg3, arg4, arg5);
793 Expect( i.String_thing = 'Hello2', 'testMulti: i.String_thing = "'+i.String_thing+'"');
794 Expect( i.Byte_thing = arg0, 'testMulti: i.Byte_thing = '+IntToStr(i.Byte_thing));
795 Expect( i.I32_thing = arg1, 'testMulti: i.I32_thing = '+IntToStr(i.I32_thing));
796 Expect( i.I64_thing = arg2, 'testMulti: i.I64_thing = '+IntToStr(i.I64_thing));
797 Expect( i.__isset_String_thing, 'testMulti: i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
798 Expect( i.__isset_Byte_thing, 'testMulti: i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
799 Expect( i.__isset_I32_thing, 'testMulti: i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
800 Expect( i.__isset_I64_thing, 'testMulti: i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
801
802 // multi exception
803 StartTestGroup( 'testMultiException(1)');
804 try
805 i := client.testMultiException( 'need more pizza', 'run out of beer');
806 Expect( i.String_thing = 'run out of beer', 'i.String_thing = "' +i.String_thing+ '"');
807 Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
Jens Geyer6bbbf192014-09-07 01:45:56 +0200808 { this is not necessarily true, these fields are default-serialized
809 Expect( not i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
Roger Meier3bef8c22012-10-06 06:58:00 +0000810 Expect( not i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
811 Expect( not i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
Jens Geyer6bbbf192014-09-07 01:45:56 +0200812 }
Roger Meier3bef8c22012-10-06 06:58:00 +0000813 except
814 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
815 end;
816
817 StartTestGroup( 'testMultiException(Xception)');
818 try
819 i := client.testMultiException( 'Xception', 'second test');
820 Expect( FALSE, 'testMultiException(''Xception''): must trow an exception');
821 except
822 on x:TXception do begin
823 Expect( x.__isset_ErrorCode, 'x.__isset_ErrorCode = '+BoolToString(x.__isset_ErrorCode));
824 Expect( x.__isset_Message_, 'x.__isset_Message_ = '+BoolToString(x.__isset_Message_));
825 Expect( x.ErrorCode = 1001, 'x.ErrorCode = '+IntToStr(x.ErrorCode));
826 Expect( x.Message_ = 'This is an Xception', 'x.Message = "'+x.Message_+'"');
827 end;
828 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
829 end;
830
831 StartTestGroup( 'testMultiException(Xception2)');
832 try
833 i := client.testMultiException( 'Xception2', 'third test');
834 Expect( FALSE, 'testMultiException(''Xception2''): must trow an exception');
835 except
836 on x:TXception2 do begin
837 Expect( x.__isset_ErrorCode, 'x.__isset_ErrorCode = '+BoolToString(x.__isset_ErrorCode));
838 Expect( x.__isset_Struct_thing, 'x.__isset_Struct_thing = '+BoolToString(x.__isset_Struct_thing));
839 Expect( x.ErrorCode = 2002, 'x.ErrorCode = '+IntToStr(x.ErrorCode));
840 Expect( x.Struct_thing.String_thing = 'This is an Xception2', 'x.Struct_thing.String_thing = "'+x.Struct_thing.String_thing+'"');
841 Expect( x.Struct_thing.__isset_String_thing, 'x.Struct_thing.__isset_String_thing = '+BoolToString(x.Struct_thing.__isset_String_thing));
Jens Geyer6bbbf192014-09-07 01:45:56 +0200842 { this is not necessarily true, these fields are default-serialized
Roger Meier3bef8c22012-10-06 06:58:00 +0000843 Expect( not x.Struct_thing.__isset_Byte_thing, 'x.Struct_thing.__isset_Byte_thing = '+BoolToString(x.Struct_thing.__isset_Byte_thing));
844 Expect( not x.Struct_thing.__isset_I32_thing, 'x.Struct_thing.__isset_I32_thing = '+BoolToString(x.Struct_thing.__isset_I32_thing));
845 Expect( not x.Struct_thing.__isset_I64_thing, 'x.Struct_thing.__isset_I64_thing = '+BoolToString(x.Struct_thing.__isset_I64_thing));
Jens Geyer6bbbf192014-09-07 01:45:56 +0200846 }
Roger Meier3bef8c22012-10-06 06:58:00 +0000847 end;
848 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
849 end;
850
851
852 // oneway functions
853 StartTestGroup( 'Test Oneway(1)');
854 client.testOneway(1);
855 Expect( TRUE, 'Test Oneway(1)'); // success := no exception
856
857 // call time
Jens Geyer06045cf2013-03-27 20:26:25 +0200858 {$IFDEF PerfTest}
Roger Meier3bef8c22012-10-06 06:58:00 +0000859 StartTestGroup( 'Test Calltime()');
860 StartTick := GetTIckCount;
861 for k := 0 to 1000 - 1 do
862 begin
863 client.testVoid();
864 end;
865 Console.WriteLine(' = ' + FloatToStr( (GetTickCount - StartTick) / 1000 ) + ' ms a testVoid() call' );
Jens Geyer06045cf2013-03-27 20:26:25 +0200866 {$ENDIF PerfTest}
Roger Meier3bef8c22012-10-06 06:58:00 +0000867
868 // no more tests here
869 StartTestGroup( '');
870end;
871
872
Jens Geyer718f6ee2013-09-06 21:02:34 +0200873{$IFDEF StressTest}
Jens Geyer06045cf2013-03-27 20:26:25 +0200874procedure TClientThread.StressTest(const client : TThriftTest.Iface);
875begin
876 while TRUE do begin
877 try
878 if not FTransport.IsOpen then FTransport.Open; // re-open connection, server has already closed
879 try
880 client.testString('Test');
881 Write('.');
882 finally
883 if FTransport.IsOpen then FTransport.Close;
884 end;
885 except
886 on e:Exception do Writeln(#10+e.message);
887 end;
888 end;
889end;
Jens Geyer718f6ee2013-09-06 21:02:34 +0200890{$ENDIF}
Jens Geyer06045cf2013-03-27 20:26:25 +0200891
Roger Meier3bef8c22012-10-06 06:58:00 +0000892procedure TClientThread.JSONProtocolReadWriteTest;
893// Tests only then read/write procedures of the JSON protocol
894// All tests succeed, if we can read what we wrote before
895// Note that passing this test does not imply, that our JSON is really compatible to what
896// other clients or servers expect as the real JSON. This is beyond the scope of this test.
897var prot : IProtocol;
898 stm : TStringStream;
899 list : IList;
900 binary, binRead : TBytes;
901 i,iErr : Integer;
902const
903 TEST_SHORT = ShortInt( $FE);
904 TEST_SMALL = SmallInt( $FEDC);
905 TEST_LONG = LongInt( $FEDCBA98);
906 TEST_I64 = Int64( $FEDCBA9876543210);
907 TEST_DOUBLE = -1.234e-56;
908 DELTA_DOUBLE = TEST_DOUBLE * 1e-14;
909 TEST_STRING = 'abc-'#$00E4#$00f6#$00fc; // german umlauts (en-us: "funny chars")
Jens Geyer7bb44a32014-02-07 22:24:37 +0100910 // Test THRIFT-2336 with 'Русское Название';
911 RUSSIAN_TEXT = #$0420#$0443#$0441#$0441#$043a#$043e#$0435' '#$041d#$0430#$0437#$0432#$0430#$043d#$0438#$0435;
912 RUSSIAN_JSON = '"\u0420\u0443\u0441\u0441\u043a\u043e\u0435 \u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435"';
Jens Geyer21366942013-12-30 22:04:51 +0100913 // test both possible solidus encodings
914 SOLIDUS_JSON_DATA = '"one/two\/three"';
915 SOLIDUS_EXCPECTED = 'one/two/three';
Roger Meier3bef8c22012-10-06 06:58:00 +0000916begin
917 stm := TStringStream.Create;
918 try
919 StartTestGroup( 'JsonProtocolTest'); // no more tests here
920
921 // prepare binary data
922 SetLength( binary, $100);
923 for i := Low(binary) to High(binary) do binary[i] := i;
924
925 // output setup
926 prot := TJSONProtocolImpl.Create(
927 TStreamTransportImpl.Create(
928 nil, TThriftStreamAdapterDelphi.Create( stm, FALSE)));
929
930 // write
931 prot.WriteListBegin( TListImpl.Create( TType.String_, 9));
932 prot.WriteBool( TRUE);
933 prot.WriteBool( FALSE);
934 prot.WriteByte( TEST_SHORT);
935 prot.WriteI16( TEST_SMALL);
936 prot.WriteI32( TEST_LONG);
937 prot.WriteI64( TEST_I64);
938 prot.WriteDouble( TEST_DOUBLE);
939 prot.WriteString( TEST_STRING);
940 prot.WriteBinary( binary);
941 prot.WriteListEnd;
942
943 // input setup
944 Expect( stm.Position = stm.Size, 'Stream position/length after write');
945 stm.Position := 0;
946 prot := TJSONProtocolImpl.Create(
947 TStreamTransportImpl.Create(
948 TThriftStreamAdapterDelphi.Create( stm, FALSE), nil));
949
950 // read and compare
951 list := prot.ReadListBegin;
952 Expect( list.ElementType = TType.String_, 'list element type');
953 Expect( list.Count = 9, 'list element count');
954 Expect( prot.ReadBool, 'WriteBool/ReadBool: TRUE');
955 Expect( not prot.ReadBool, 'WriteBool/ReadBool: FALSE');
956 Expect( prot.ReadByte = TEST_SHORT, 'WriteByte/ReadByte');
957 Expect( prot.ReadI16 = TEST_SMALL, 'WriteI16/ReadI16');
958 Expect( prot.ReadI32 = TEST_LONG, 'WriteI32/ReadI32');
959 Expect( prot.ReadI64 = TEST_I64, 'WriteI64/ReadI64');
960 Expect( abs(prot.ReadDouble-TEST_DOUBLE) < abs(DELTA_DOUBLE), 'WriteDouble/ReadDouble');
961 Expect( prot.ReadString = TEST_STRING, 'WriteString/ReadString');
962 binRead := prot.ReadBinary;
963 prot.ReadListEnd;
964
965 // test binary data
966 Expect( Length(binary) = Length(binRead), 'Binary data length check');
967 iErr := -1;
968 for i := Low(binary) to High(binary) do begin
969 if binary[i] <> binRead[i] then begin
970 iErr := i;
971 Break;
972 end;
973 end;
974 if iErr < 0
975 then Expect( TRUE, 'Binary data check ('+IntToStr(Length(binary))+' Bytes)')
976 else Expect( FALSE, 'Binary data check at offset '+IntToStr(iErr));
977
978 Expect( stm.Position = stm.Size, 'Stream position after read');
979
Jens Geyer7bb44a32014-02-07 22:24:37 +0100980
Jens Geyer21366942013-12-30 22:04:51 +0100981 // Solidus can be encoded in two ways. Make sure we can read both
982 stm.Position := 0;
983 stm.Size := 0;
984 stm.WriteString(SOLIDUS_JSON_DATA);
985 stm.Position := 0;
986 prot := TJSONProtocolImpl.Create(
987 TStreamTransportImpl.Create(
988 TThriftStreamAdapterDelphi.Create( stm, FALSE), nil));
989 Expect( prot.ReadString = SOLIDUS_EXCPECTED, 'Solidus encoding');
990
991
Jens Geyer7bb44a32014-02-07 22:24:37 +0100992 // Widechars should work too. Do they?
993 // After writing, we ensure that we are able to read it back
994 // We can't assume hex-encoding, since (nearly) any Unicode char is valid JSON
995 stm.Position := 0;
996 stm.Size := 0;
997 prot := TJSONProtocolImpl.Create(
998 TStreamTransportImpl.Create(
999 nil, TThriftStreamAdapterDelphi.Create( stm, FALSE)));
1000 prot.WriteString( RUSSIAN_TEXT);
1001 stm.Position := 0;
1002 prot := TJSONProtocolImpl.Create(
1003 TStreamTransportImpl.Create(
1004 TThriftStreamAdapterDelphi.Create( stm, FALSE), nil));
1005 Expect( prot.ReadString = RUSSIAN_TEXT, 'Writing JSON with chars > 8 bit');
1006
1007 // Widechars should work with hex-encoding too. Do they?
1008 stm.Position := 0;
1009 stm.Size := 0;
1010 stm.WriteString( RUSSIAN_JSON);
1011 stm.Position := 0;
1012 prot := TJSONProtocolImpl.Create(
1013 TStreamTransportImpl.Create(
1014 TThriftStreamAdapterDelphi.Create( stm, FALSE), nil));
1015 Expect( prot.ReadString = RUSSIAN_TEXT, 'Reading JSON with chars > 8 bit');
1016
1017
Roger Meier3bef8c22012-10-06 06:58:00 +00001018 finally
1019 stm.Free;
1020 prot := nil; //-> Release
1021 StartTestGroup( ''); // no more tests here
1022 end;
1023end;
1024
1025
1026procedure TClientThread.StartTestGroup( const aGroup : string);
1027begin
1028 FTestGroup := aGroup;
1029 if FTestGroup <> '' then begin
1030 Console.WriteLine('');
1031 Console.WriteLine( aGroup+' tests');
1032 Console.WriteLine( StringOfChar('-',60));
1033 end;
1034end;
1035
1036
1037procedure TClientThread.Expect( aTestResult : Boolean; const aTestInfo : string);
1038begin
1039 if aTestResult then begin
1040 Inc(FSuccesses);
1041 Console.WriteLine( aTestInfo+': passed');
1042 end
1043 else begin
1044 FErrors.Add( FTestGroup+': '+aTestInfo);
1045 Console.WriteLine( aTestInfo+': *** FAILED ***');
1046
1047 // We have a failed test!
1048 // -> issue DebugBreak ONLY if a debugger is attached,
1049 // -> unhandled DebugBreaks would cause Windows to terminate the app otherwise
1050 if IsDebuggerPresent then asm int 3 end;
1051 end;
1052end;
1053
1054
1055procedure TClientThread.ReportResults;
1056var nTotal : Integer;
1057 sLine : string;
1058begin
1059 // prevent us from stupid DIV/0 errors
1060 nTotal := FSuccesses + FErrors.Count;
1061 if nTotal = 0 then begin
1062 Console.WriteLine('No results logged');
1063 Exit;
1064 end;
1065
1066 Console.WriteLine('');
1067 Console.WriteLine( StringOfChar('=',60));
1068 Console.WriteLine( IntToStr(nTotal)+' tests performed');
1069 Console.WriteLine( IntToStr(FSuccesses)+' tests succeeded ('+IntToStr(round(100*FSuccesses/nTotal))+'%)');
1070 Console.WriteLine( IntToStr(FErrors.Count)+' tests failed ('+IntToStr(round(100*FErrors.Count/nTotal))+'%)');
1071 Console.WriteLine( StringOfChar('=',60));
1072 if FErrors.Count > 0 then begin
1073 Console.WriteLine('FAILED TESTS:');
1074 for sLine in FErrors do Console.WriteLine('- '+sLine);
1075 Console.WriteLine( StringOfChar('=',60));
1076 InterlockedIncrement( ExitCode); // return <> 0 on errors
1077 end;
1078 Console.WriteLine('');
1079end;
1080
1081
1082constructor TClientThread.Create( const ATransport: ITransport; const AProtocol : IProtocol; ANumIteration: Integer);
1083begin
1084 inherited Create( True );
1085 FNumIteration := ANumIteration;
1086 FTransport := ATransport;
1087 FProtocol := AProtocol;
1088 FConsole := TThreadConsole.Create( Self );
1089
1090 // error list: keep correct order, allow for duplicates
1091 FErrors := TStringList.Create;
1092 FErrors.Sorted := FALSE;
1093 FErrors.Duplicates := dupAccept;
1094end;
1095
1096destructor TClientThread.Destroy;
1097begin
1098 FreeAndNil( FConsole);
1099 FreeAndNil( FErrors);
1100 inherited;
1101end;
1102
1103procedure TClientThread.Execute;
1104var
1105 i : Integer;
1106 proc : TThreadProcedure;
1107begin
1108 // perform all tests
1109 try
Jens Geyer7bb44a32014-02-07 22:24:37 +01001110 JSONProtocolReadWriteTest;
Roger Meier3bef8c22012-10-06 06:58:00 +00001111 for i := 0 to FNumIteration - 1 do
1112 begin
1113 ClientTest;
Roger Meier3bef8c22012-10-06 06:58:00 +00001114 end;
1115 except
1116 on e:Exception do Expect( FALSE, 'unexpected exception: "'+e.message+'"');
1117 end;
1118
1119 // report the outcome
1120 ReportResults;
1121
1122 // shutdown
1123 proc := procedure
1124 begin
1125 if FTransport <> nil then
1126 begin
1127 FTransport.Close;
1128 FTransport := nil;
1129 end;
1130 end;
1131
1132 Synchronize( proc );
1133end;
1134
1135{ TThreadConsole }
1136
1137constructor TThreadConsole.Create(AThread: TThread);
1138begin
Jens Geyer718f6ee2013-09-06 21:02:34 +02001139 inherited Create;
Roger Meier3bef8c22012-10-06 06:58:00 +00001140 FThread := AThread;
1141end;
1142
1143procedure TThreadConsole.Write(const S: string);
1144var
1145 proc : TThreadProcedure;
1146begin
1147 proc := procedure
1148 begin
1149 Console.Write( S );
1150 end;
1151 TThread.Synchronize( FThread, proc);
1152end;
1153
1154procedure TThreadConsole.WriteLine(const S: string);
1155var
1156 proc : TThreadProcedure;
1157begin
1158 proc := procedure
1159 begin
1160 Console.WriteLine( S );
1161 end;
1162 TThread.Synchronize( FThread, proc);
1163end;
1164
1165initialization
1166begin
1167 TTestClient.FNumIteration := 1;
1168 TTestClient.FNumThread := 1;
1169end;
1170
1171end.