blob: 37fe7d7c5caa1be0e8f33db6dcdf28f54d4d843a [file] [log] [blame]
Roger Meier3bef8c22012-10-06 06:58:00 +00001(*
2 * 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 Geyer06045cf2013-03-27 20:26:25 +020069 procedure StressTest(const client : TThriftTest.Iface);
Roger Meier3bef8c22012-10-06 06:58:00 +000070 protected
71 procedure Execute; override;
72 public
73 constructor Create( const ATransport: ITransport; const AProtocol : IProtocol; ANumIteration: Integer);
74 destructor Destroy; override;
75 end;
76
77 TTestClient = class
78 private
79 class var
80 FNumIteration : Integer;
81 FNumThread : Integer;
82 public
83 class procedure Execute( const args: array of string);
84 end;
85
86implementation
87
88function BoolToString( b : Boolean) : string;
89// overrides global BoolToString()
90begin
91 if b
92 then result := 'true'
93 else result := 'false';
94end;
95
96// not available in all versions, so make sure we have this one imported
97function IsDebuggerPresent: BOOL; stdcall; external KERNEL32 name 'IsDebuggerPresent';
98
99{ TTestClient }
100
101class procedure TTestClient.Execute(const args: array of string);
102var
103 i : Integer;
104 host : string;
105 port : Integer;
106 url : string;
107 bBuffered : Boolean;
108 bAnonPipe : Boolean;
109 bFramed : Boolean;
110 sPipeName : string;
111 hAnonRead, hAnonWrite : THandle;
112 s : string;
113 n : Integer;
114 threads : array of TThread;
115 dtStart : TDateTime;
116 test : Integer;
117 thread : TThread;
118 trans : ITransport;
119 prot : IProtocol;
120 streamtrans : IStreamTransport;
121 http : IHTTPClient;
122 protType, p : TKnownProtocol;
Jens Geyer0b20cc82013-03-07 20:47:01 +0100123const
124 // pipe timeouts to be used
125 DEBUG_TIMEOUT = 30 * 1000;
126 RELEASE_TIMEOUT = DEFAULT_THRIFT_PIPE_TIMEOUT;
127 TIMEOUT = RELEASE_TIMEOUT;
Roger Meier3bef8c22012-10-06 06:58:00 +0000128begin
129 bBuffered := False;;
130 bFramed := False;
131 protType := prot_Binary;
132 try
133 host := 'localhost';
134 port := 9090;
135 url := '';
136 sPipeName := '';
137 bAnonPipe := FALSE;
138 hAnonRead := INVALID_HANDLE_VALUE;
139 hAnonWrite := INVALID_HANDLE_VALUE;
140 i := 0;
141 try
142 while ( i < Length(args) ) do
143 begin
144
145 try
146 if ( args[i] = '-h') then
147 begin
148 Inc( i );
149 s := args[i];
150 n := Pos( ':', s);
151 if ( n > 0 ) then
152 begin
153 host := Copy( s, 1, n - 1);
154 port := StrToInt( Copy( s, n + 1, MaxInt));
155 end else
156 begin
157 host := s;
158 end;
159 end
160 else if (args[i] = '-u') then
161 begin
162 Inc( i );
163 url := args[i];
164 end
165 else if (args[i] = '-n') then
166 begin
167 Inc( i );
168 FNumIteration := StrToInt( args[i] );
169 end
170 else if (args[i] = '-b') then
171 begin
172 bBuffered := True;
173 Console.WriteLine('Buffered transport');
174 end
175 else if (args[i] = '-f' ) or ( args[i] = '-framed') then
176 begin
177 bFramed := True;
178 Console.WriteLine('Framed transport');
179 end
180 else if (args[i] = '-pipe') then // -pipe <name>
181 begin
182 Console.WriteLine('Named pipes transport');
183 Inc( i );
184 sPipeName := args[i];
185 end
186 else if (args[i] = '-anon') then // -anon <hReadPipe> <hWritePipe>
187 begin
Jens Geyerb64a7742013-01-23 20:58:47 +0100188 if Length(args) <= (i+2) then begin
189 Console.WriteLine('Invalid args: -anon <hRead> <hWrite> or use "server.exe -anon"');
190 Halt(1);
191 end;
Roger Meier3bef8c22012-10-06 06:58:00 +0000192 Console.WriteLine('Anonymous pipes transport');
193 Inc( i);
194 hAnonRead := THandle( StrToIntDef( args[i], Integer(INVALID_HANDLE_VALUE)));
195 Inc( i);
196 hAnonWrite := THandle( StrToIntDef( args[i], Integer(INVALID_HANDLE_VALUE)));
197 bAnonPipe := TRUE;
198 end
199 else if (args[i] = '-t') then
200 begin
201 Inc( i );
202 FNumThread := StrToInt( args[i] );
203 end
204 else if (args[i] = '-prot') then // -prot JSON|binary
205 begin
206 Inc( i );
207 s := args[i];
208 for p:= Low(TKnownProtocol) to High(TKnownProtocol) do begin
209 if SameText( s, KNOWN_PROTOCOLS[p]) then begin
210 protType := p;
211 Console.WriteLine('Using '+KNOWN_PROTOCOLS[protType]+' protocol');
212 Break;
213 end;
214 end;
215 end;
216 finally
217 Inc( i );
218 end;
219
220 end;
221
222 except
223 on E: Exception do
224 begin
225 Console.WriteLine( E.Message );
226 end;
227 end;
228
Roger Meier79655fb2012-10-20 20:59:41 +0000229 // In the anonymous pipes mode the client is launched by the test server
230 // -> behave nicely and allow for attaching a debugger to this process
231 if bAnonPipe and not IsDebuggerPresent
232 then MessageBox( 0, 'Attach Debugger and/or click OK to continue.',
233 'Thrift TestClient (Delphi)',
234 MB_OK or MB_ICONEXCLAMATION);
235
Roger Meier3bef8c22012-10-06 06:58:00 +0000236 SetLength( threads, FNumThread);
237 dtStart := Now;
238
239 for test := 0 to FNumThread - 1 do
240 begin
241 if url = '' then
242 begin
243 if sPipeName <> '' then begin
244 Console.WriteLine('Using named pipe ('+sPipeName+')');
Jens Geyer06045cf2013-03-27 20:26:25 +0200245 streamtrans := TNamedPipeTransportClientEndImpl.Create( sPipeName, 0, nil, TIMEOUT);
Roger Meier3bef8c22012-10-06 06:58:00 +0000246 end
247 else if bAnonPipe then begin
248 Console.WriteLine('Using anonymous pipes ('+IntToStr(Integer(hAnonRead))+' and '+IntToStr(Integer(hAnonWrite))+')');
Jens Geyer06045cf2013-03-27 20:26:25 +0200249 streamtrans := TAnonymousPipeTransportImpl.Create( hAnonRead, hAnonWrite, FALSE);
Roger Meier3bef8c22012-10-06 06:58:00 +0000250 end
251 else begin
252 Console.WriteLine('Using sockets ('+host+' port '+IntToStr(port)+')');
253 streamtrans := TSocketImpl.Create( host, port );
254 end;
255
256 trans := streamtrans;
257
258 if bBuffered then begin
Jens Geyer0b20cc82013-03-07 20:47:01 +0100259 trans := TBufferedTransportImpl.Create( streamtrans, 32); // small buffer to test read()
Roger Meier3bef8c22012-10-06 06:58:00 +0000260 Console.WriteLine('Using buffered transport');
261 end;
262
263 if bFramed then begin
264 trans := TFramedTransportImpl.Create( trans );
265 Console.WriteLine('Using framed transport');
266 end;
267
268 end
269 else begin
270 Console.WriteLine('Using HTTPClient');
271 http := THTTPClientImpl.Create( url );
272 trans := http;
273 end;
274
275 // create protocol instance, default to BinaryProtocol
276 case protType of
Jens Geyer0b20cc82013-03-07 20:47:01 +0100277 prot_Binary: prot := TBinaryProtocolImpl.Create( trans, BINARY_STRICT_READ, BINARY_STRICT_WRITE);
Roger Meier3bef8c22012-10-06 06:58:00 +0000278 prot_JSON : prot := TJSONProtocolImpl.Create( trans);
279 else
280 ASSERT( FALSE); // unhandled case!
Jens Geyer0b20cc82013-03-07 20:47:01 +0100281 prot := TBinaryProtocolImpl.Create( trans, BINARY_STRICT_READ, BINARY_STRICT_WRITE); // use default
Roger Meier3bef8c22012-10-06 06:58:00 +0000282 end;
283
284 thread := TClientThread.Create( trans, prot, FNumIteration);
285 threads[test] := thread;
286{$WARN SYMBOL_DEPRECATED OFF}
287 thread.Resume;
288{$WARN SYMBOL_DEPRECATED ON}
289 end;
290
291 for test := 0 to FNumThread - 1 do
292 begin
293 threads[test].WaitFor;
294 end;
295
296 for test := 0 to FNumThread - 1 do
297 begin
298 threads[test].Free;
299 end;
300
301 Console.Write('Total time: ' + IntToStr( MilliSecondsBetween(Now, dtStart)));
302
303 except
304 on E: Exception do
305 begin
306 Console.WriteLine( E.Message + ' ST: ' + E.StackTrace );
307 end;
308 end;
309
310 Console.WriteLine('');
311 Console.WriteLine('done!');
312end;
313
314{ TClientThread }
315
316procedure TClientThread.ClientTest;
317var
318 client : TThriftTest.Iface;
319 s : string;
320 i8 : ShortInt;
321 i32 : Integer;
322 i64 : Int64;
323 dub : Double;
324 o : IXtruct;
325 o2 : IXtruct2;
326 i : IXtruct;
327 i2 : IXtruct2;
328 mapout : IThriftDictionary<Integer,Integer>;
329 mapin : IThriftDictionary<Integer,Integer>;
330 strmapout : IThriftDictionary<string,string>;
331 strmapin : IThriftDictionary<string,string>;
332 j : Integer;
333 first : Boolean;
334 key : Integer;
335 strkey : string;
336 listout : IThriftList<Integer>;
337 listin : IThriftList<Integer>;
338 setout : IHashSet<Integer>;
339 setin : IHashSet<Integer>;
340 ret : TNumberz;
341 uid : Int64;
342 mm : IThriftDictionary<Integer, IThriftDictionary<Integer, Integer>>;
343 pos : IThriftDictionary<Integer, Integer>;
344 neg : IThriftDictionary<Integer, Integer>;
345 m2 : IThriftDictionary<Integer, Integer>;
346 k2 : Integer;
347 insane : IInsanity;
348 truck : IXtruct;
349 whoa : IThriftDictionary<Int64, IThriftDictionary<TNumberz, IInsanity>>;
350 key64 : Int64;
351 val : IThriftDictionary<TNumberz, IInsanity>;
352 k2_2 : TNumberz;
353 k3 : TNumberz;
354 v2 : IInsanity;
355 userMap : IThriftDictionary<TNumberz, Int64>;
356 xtructs : IThriftList<IXtruct>;
357 x : IXtruct;
358 arg0 : ShortInt;
359 arg1 : Integer;
360 arg2 : Int64;
361 arg3 : IThriftDictionary<SmallInt, string>;
362 arg4 : TNumberz;
363 arg5 : Int64;
364 StartTick : Cardinal;
365 k : Integer;
366 proc : TThreadProcedure;
367 hello, goodbye : IXtruct;
368 crazy : IInsanity;
369 looney : IInsanity;
370 first_map : IThriftDictionary<TNumberz, IInsanity>;
371 second_map : IThriftDictionary<TNumberz, IInsanity>;
372
373begin
374 client := TThriftTest.TClient.Create( FProtocol);
375 FTransport.Open;
376
Jens Geyer06045cf2013-03-27 20:26:25 +0200377 {$IFDEF StressTest}
378 StressTest( client);
379 {$ENDIF StressTest}
380
Roger Meier3bef8c22012-10-06 06:58:00 +0000381 // in-depth exception test
382 // (1) do we get an exception at all?
383 // (2) do we get the right exception?
384 // (3) does the exception contain the expected data?
385 StartTestGroup( 'testException');
386 // case 1: exception type declared in IDL at the function call
387 try
388 client.testException('Xception');
389 Expect( FALSE, 'testException(''Xception''): must trow an exception');
390 except
391 on e:TXception do begin
392 Expect( e.ErrorCode = 1001, 'error code');
393 Expect( e.Message_ = 'Xception', 'error message');
394 Console.WriteLine( ' = ' + IntToStr(e.ErrorCode) + ', ' + e.Message_ );
395 end;
396 on e:TTransportException do Expect( FALSE, 'Unexpected : "'+e.ToString+'"');
397 on e:Exception do Expect( FALSE, 'Unexpected exception type "'+e.ClassName+'"');
398 end;
399
400 // case 2: exception type NOT declared in IDL at the function call
401 // this will close the connection
402 try
403 client.testException('TException');
404 Expect( FALSE, 'testException(''TException''): must trow an exception');
405 except
406 on e:TTransportException do begin
407 Console.WriteLine( e.ClassName+' = '+e.Message); // this is what we get
408 if FTransport.IsOpen then FTransport.Close;
409 FTransport.Open; // re-open connection, server has already closed
410 end;
411 on e:TException do Expect( FALSE, 'Unexpected exception type "'+e.ClassName+'"');
412 on e:Exception do Expect( FALSE, 'Unexpected exception type "'+e.ClassName+'"');
413 end;
414
415 // case 3: no exception
416 try
417 client.testException('something');
418 Expect( TRUE, 'testException(''something''): must not trow an exception');
419 except
420 on e:TTransportException do Expect( FALSE, 'Unexpected : "'+e.ToString+'"');
421 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
422 end;
423
424
425 // simple things
426 StartTestGroup( 'simple Thrift calls');
427 client.testVoid();
428 Expect( TRUE, 'testVoid()'); // success := no exception
429
430 s := client.testString('Test');
431 Expect( s = 'Test', 'testString(''Test'') = "'+s+'"');
432
Jens Geyer06045cf2013-03-27 20:26:25 +0200433 s := client.testString(HUGE_TEST_STRING);
434 Expect( length(s) = length(HUGE_TEST_STRING),
435 'testString( lenght(HUGE_TEST_STRING) = '+IntToStr(Length(HUGE_TEST_STRING))+') '
436 +'=> length(result) = '+IntToStr(Length(s)));
437
Roger Meier3bef8c22012-10-06 06:58:00 +0000438 i8 := client.testByte(1);
439 Expect( i8 = 1, 'testByte(1) = ' + IntToStr( i8 ));
440
441 i32 := client.testI32(-1);
442 Expect( i32 = -1, 'testI32(-1) = ' + IntToStr(i32));
443
444 Console.WriteLine('testI64(-34359738368)');
445 i64 := client.testI64(-34359738368);
446 Expect( i64 = -34359738368, 'testI64(-34359738368) = ' + IntToStr( i64));
447
448 Console.WriteLine('testDouble(5.325098235)');
449 dub := client.testDouble(5.325098235);
450 Expect( abs(dub-5.325098235) < 1e-14, 'testDouble(5.325098235) = ' + FloatToStr( dub));
451
452 // structs
453 StartTestGroup( 'testStruct');
454 Console.WriteLine('testStruct({''Zero'', 1, -3, -5})');
455 o := TXtructImpl.Create;
456 o.String_thing := 'Zero';
457 o.Byte_thing := 1;
458 o.I32_thing := -3;
459 o.I64_thing := -5;
460 i := client.testStruct(o);
461 Expect( i.String_thing = 'Zero', 'i.String_thing = "'+i.String_thing+'"');
462 Expect( i.Byte_thing = 1, 'i.Byte_thing = '+IntToStr(i.Byte_thing));
463 Expect( i.I32_thing = -3, 'i.I32_thing = '+IntToStr(i.I32_thing));
464 Expect( i.I64_thing = -5, 'i.I64_thing = '+IntToStr(i.I64_thing));
465 Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
466 Expect( i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
467 Expect( i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
468 Expect( i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
469
470 // nested structs
471 StartTestGroup( 'testNest');
472 Console.WriteLine('testNest({1, {''Zero'', 1, -3, -5}, 5})');
473 o2 := TXtruct2Impl.Create;
474 o2.Byte_thing := 1;
475 o2.Struct_thing := o;
476 o2.I32_thing := 5;
477 i2 := client.testNest(o2);
478 i := i2.Struct_thing;
479 Expect( i.String_thing = 'Zero', 'i.String_thing = "'+i.String_thing+'"');
480 Expect( i.Byte_thing = 1, 'i.Byte_thing = '+IntToStr(i.Byte_thing));
481 Expect( i.I32_thing = -3, 'i.I32_thing = '+IntToStr(i.I32_thing));
482 Expect( i.I64_thing = -5, 'i.I64_thing = '+IntToStr(i.I64_thing));
483 Expect( i2.Byte_thing = 1, 'i2.Byte_thing = '+IntToStr(i2.Byte_thing));
484 Expect( i2.I32_thing = 5, 'i2.I32_thing = '+IntToStr(i2.I32_thing));
485 Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
486 Expect( i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
487 Expect( i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
488 Expect( i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
489 Expect( i2.__isset_Byte_thing, 'i2.__isset_Byte_thing');
490 Expect( i2.__isset_I32_thing, 'i2.__isset_I32_thing');
491
492 // map<type1,type2>: A map of strictly unique keys to values.
493 // Translates to an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc.
494 StartTestGroup( 'testMap');
495 mapout := TThriftDictionaryImpl<Integer,Integer>.Create;
496 for j := 0 to 4 do
497 begin
498 mapout.AddOrSetValue( j, j - 10);
499 end;
500 Console.Write('testMap({');
501 first := True;
502 for key in mapout.Keys do
503 begin
504 if first
505 then first := False
506 else Console.Write( ', ' );
507 Console.Write( IntToStr( key) + ' => ' + IntToStr( mapout[key]));
508 end;
509 Console.WriteLine('})');
510
511 mapin := client.testMap( mapout );
512 Expect( mapin.Count = mapout.Count, 'testMap: mapin.Count = mapout.Count');
513 for j := 0 to 4 do
514 begin
515 Expect( mapout.ContainsKey(j), 'testMap: mapout.ContainsKey('+IntToStr(j)+') = '+BoolToString(mapout.ContainsKey(j)));
516 end;
517 for key in mapin.Keys do
518 begin
519 Expect( mapin[key] = mapout[key], 'testMap: '+IntToStr(key) + ' => ' + IntToStr( mapin[key]));
520 Expect( mapin[key] = key - 10, 'testMap: mapin['+IntToStr(key)+'] = '+IntToStr( mapin[key]));
521 end;
522
523
524 // map<type1,type2>: A map of strictly unique keys to values.
525 // Translates to an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc.
526 StartTestGroup( 'testStringMap');
527 strmapout := TThriftDictionaryImpl<string,string>.Create;
528 for j := 0 to 4 do
529 begin
530 strmapout.AddOrSetValue( IntToStr(j), IntToStr(j - 10));
531 end;
532 Console.Write('testStringMap({');
533 first := True;
534 for strkey in strmapout.Keys do
535 begin
536 if first
537 then first := False
538 else Console.Write( ', ' );
539 Console.Write( strkey + ' => ' + strmapout[strkey]);
540 end;
541 Console.WriteLine('})');
542
543 strmapin := client.testStringMap( strmapout );
544 Expect( strmapin.Count = strmapout.Count, 'testStringMap: strmapin.Count = strmapout.Count');
545 for j := 0 to 4 do
546 begin
547 Expect( strmapout.ContainsKey(IntToStr(j)),
548 'testStringMap: strmapout.ContainsKey('+IntToStr(j)+') = '
549 + BoolToString(strmapout.ContainsKey(IntToStr(j))));
550 end;
551 for strkey in strmapin.Keys do
552 begin
553 Expect( strmapin[strkey] = strmapout[strkey], 'testStringMap: '+strkey + ' => ' + strmapin[strkey]);
554 Expect( strmapin[strkey] = IntToStr( StrToInt(strkey) - 10), 'testStringMap: strmapin['+strkey+'] = '+strmapin[strkey]);
555 end;
556
557
558 // set<type>: An unordered set of unique elements.
559 // Translates to an STL set, Java HashSet, set in Python, etc.
560 // Note: PHP does not support sets, so it is treated similar to a List
561 StartTestGroup( 'testSet');
562 setout := THashSetImpl<Integer>.Create;
563 for j := -2 to 2 do
564 begin
565 setout.Add( j );
566 end;
567 Console.Write('testSet({');
568 first := True;
569 for j in setout do
570 begin
571 if first
572 then first := False
573 else Console.Write(', ');
574 Console.Write(IntToStr( j));
575 end;
576 Console.WriteLine('})');
577
578 setin := client.testSet(setout);
579 Expect( setin.Count = setout.Count, 'testSet: setin.Count = setout.Count');
580 Expect( setin.Count = 5, 'testSet: setin.Count = '+IntToStr(setin.Count));
581 for j := -2 to 2 do // unordered, we can't rely on the order => test for known elements only
582 begin
583 Expect( setin.Contains(j), 'testSet: setin.Contains('+IntToStr(j)+') => '+BoolToString(setin.Contains(j)));
584 end;
585
586 // list<type>: An ordered list of elements.
587 // Translates to an STL vector, Java ArrayList, native arrays in scripting languages, etc.
588 StartTestGroup( 'testList');
589 listout := TThriftListImpl<Integer>.Create;
590 listout.Add( +1);
591 listout.Add( -2);
592 listout.Add( +3);
593 listout.Add( -4);
594 listout.Add( 0);
595 Console.Write('testList({');
596 first := True;
597 for j in listout do
598 begin
599 if first
600 then first := False
601 else Console.Write(', ');
602 Console.Write(IntToStr( j));
603 end;
604 Console.WriteLine('})');
605
606 listin := client.testList(listout);
607 Expect( listin.Count = listout.Count, 'testList: listin.Count = listout.Count');
608 Expect( listin.Count = 5, 'testList: listin.Count = '+IntToStr(listin.Count));
609 Expect( listin[0] = +1, 'listin[0] = '+IntToStr( listin[0]));
610 Expect( listin[1] = -2, 'listin[1] = '+IntToStr( listin[1]));
611 Expect( listin[2] = +3, 'listin[2] = '+IntToStr( listin[2]));
612 Expect( listin[3] = -4, 'listin[3] = '+IntToStr( listin[3]));
613 Expect( listin[4] = 0, 'listin[4] = '+IntToStr( listin[4]));
614
615 // enums
616 ret := client.testEnum(TNumberz.ONE);
617 Expect( ret = TNumberz.ONE, 'testEnum(ONE) = '+IntToStr(Ord(ret)));
618
619 ret := client.testEnum(TNumberz.TWO);
620 Expect( ret = TNumberz.TWO, 'testEnum(TWO) = '+IntToStr(Ord(ret)));
621
622 ret := client.testEnum(TNumberz.THREE);
623 Expect( ret = TNumberz.THREE, 'testEnum(THREE) = '+IntToStr(Ord(ret)));
624
625 ret := client.testEnum(TNumberz.FIVE);
626 Expect( ret = TNumberz.FIVE, 'testEnum(FIVE) = '+IntToStr(Ord(ret)));
627
628 ret := client.testEnum(TNumberz.EIGHT);
629 Expect( ret = TNumberz.EIGHT, 'testEnum(EIGHT) = '+IntToStr(Ord(ret)));
630
631
632 // typedef
633 uid := client.testTypedef(309858235082523);
634 Expect( uid = 309858235082523, 'testTypedef(309858235082523) = '+IntToStr(uid));
635
636
637 // maps of maps
638 StartTestGroup( 'testMapMap(1)');
639 mm := client.testMapMap(1);
640 Console.Write(' = {');
641 for key in mm.Keys do
642 begin
643 Console.Write( IntToStr( key) + ' => {');
644 m2 := mm[key];
645 for k2 in m2.Keys do
646 begin
647 Console.Write( IntToStr( k2) + ' => ' + IntToStr( m2[k2]) + ', ');
648 end;
649 Console.Write('}, ');
650 end;
651 Console.WriteLine('}');
652
653 // verify result data
654 Expect( mm.Count = 2, 'mm.Count = '+IntToStr(mm.Count));
655 pos := mm[4];
656 neg := mm[-4];
657 for j := 1 to 4 do
658 begin
659 Expect( pos[j] = j, 'pos[j] = '+IntToStr(pos[j]));
660 Expect( neg[-j] = -j, 'neg[-j] = '+IntToStr(neg[-j]));
661 end;
662
663
664
665 // insanity
666 StartTestGroup( 'testInsanity');
667 insane := TInsanityImpl.Create;
668 insane.UserMap := TThriftDictionaryImpl<TNumberz, Int64>.Create;
669 insane.UserMap.AddOrSetValue( TNumberz.FIVE, 5000);
670 truck := TXtructImpl.Create;
671 truck.String_thing := 'Truck';
672 truck.Byte_thing := 8;
673 truck.I32_thing := 8;
674 truck.I64_thing := 8;
675 insane.Xtructs := TThriftListImpl<IXtruct>.Create;
676 insane.Xtructs.Add( truck );
677 whoa := client.testInsanity( insane );
678 Console.Write(' = {');
679 for key64 in whoa.Keys do
680 begin
681 val := whoa[key64];
682 Console.Write( IntToStr( key64) + ' => {');
683 for k2_2 in val.Keys do
684 begin
685 v2 := val[k2_2];
686 Console.Write( IntToStr( Integer( k2_2)) + ' => {');
687 userMap := v2.UserMap;
688 Console.Write('{');
689 if userMap <> nil then
690 begin
691 for k3 in userMap.Keys do
692 begin
693 Console.Write( IntToStr( Integer( k3)) + ' => ' + IntToStr( userMap[k3]) + ', ');
694 end;
695 end else
696 begin
697 Console.Write('null');
698 end;
699 Console.Write('}, ');
700 xtructs := v2.Xtructs;
701 Console.Write('{');
702
703 if xtructs <> nil then
704 begin
705 for x in xtructs do
706 begin
707 Console.Write('{"' + x.String_thing + '", ' +
708 IntToStr( x.Byte_thing) + ', ' +
709 IntToStr( x.I32_thing) + ', ' +
710 IntToStr( x.I32_thing) + '}, ');
711 end;
712 end else
713 begin
714 Console.Write('null');
715 end;
716 Console.Write('}');
717 Console.Write('}, ');
718 end;
719 Console.Write('}, ');
720 end;
721 Console.WriteLine('}');
722
723 // verify result data
724 Expect( whoa.Count = 2, 'whoa.Count = '+IntToStr(whoa.Count));
725 //
726 first_map := whoa[1];
727 second_map := whoa[2];
728 Expect( first_map.Count = 2, 'first_map.Count = '+IntToStr(first_map.Count));
729 Expect( second_map.Count = 1, 'second_map.Count = '+IntToStr(second_map.Count));
730 //
731 looney := second_map[TNumberz.SIX];
732 Expect( Assigned(looney), 'Assigned(looney) = '+BoolToString(Assigned(looney)));
733 Expect( not looney.__isset_UserMap, 'looney.__isset_UserMap = '+BoolToString(looney.__isset_UserMap));
734 Expect( not looney.__isset_Xtructs, 'looney.__isset_Xtructs = '+BoolToString(looney.__isset_Xtructs));
735 //
736 for ret in [TNumberz.TWO, TNumberz.THREE] do begin
737 crazy := first_map[ret];
738 Console.WriteLine('first_map['+intToStr(Ord(ret))+']');
739
740 Expect( crazy.__isset_UserMap, 'crazy.__isset_UserMap = '+BoolToString(crazy.__isset_UserMap));
741 Expect( crazy.__isset_Xtructs, 'crazy.__isset_Xtructs = '+BoolToString(crazy.__isset_Xtructs));
742
743 Expect( crazy.UserMap.Count = 2, 'crazy.UserMap.Count = '+IntToStr(crazy.UserMap.Count));
744 Expect( crazy.UserMap[TNumberz.FIVE] = 5, 'crazy.UserMap[TNumberz.FIVE] = '+IntToStr(crazy.UserMap[TNumberz.FIVE]));
745 Expect( crazy.UserMap[TNumberz.EIGHT] = 8, 'crazy.UserMap[TNumberz.EIGHT] = '+IntToStr(crazy.UserMap[TNumberz.EIGHT]));
746
747 Expect( crazy.Xtructs.Count = 2, 'crazy.Xtructs.Count = '+IntToStr(crazy.Xtructs.Count));
748 goodbye := crazy.Xtructs[0]; // lists are ordered, so we are allowed to assume this order
749 hello := crazy.Xtructs[1];
750
751 Expect( goodbye.String_thing = 'Goodbye4', 'goodbye.String_thing = "'+goodbye.String_thing+'"');
752 Expect( goodbye.Byte_thing = 4, 'goodbye.Byte_thing = '+IntToStr(goodbye.Byte_thing));
753 Expect( goodbye.I32_thing = 4, 'goodbye.I32_thing = '+IntToStr(goodbye.I32_thing));
754 Expect( goodbye.I64_thing = 4, 'goodbye.I64_thing = '+IntToStr(goodbye.I64_thing));
755 Expect( goodbye.__isset_String_thing, 'goodbye.__isset_String_thing = '+BoolToString(goodbye.__isset_String_thing));
756 Expect( goodbye.__isset_Byte_thing, 'goodbye.__isset_Byte_thing = '+BoolToString(goodbye.__isset_Byte_thing));
757 Expect( goodbye.__isset_I32_thing, 'goodbye.__isset_I32_thing = '+BoolToString(goodbye.__isset_I32_thing));
758 Expect( goodbye.__isset_I64_thing, 'goodbye.__isset_I64_thing = '+BoolToString(goodbye.__isset_I64_thing));
759
760 Expect( hello.String_thing = 'Hello2', 'hello.String_thing = "'+hello.String_thing+'"');
761 Expect( hello.Byte_thing = 2, 'hello.Byte_thing = '+IntToStr(hello.Byte_thing));
762 Expect( hello.I32_thing = 2, 'hello.I32_thing = '+IntToStr(hello.I32_thing));
763 Expect( hello.I64_thing = 2, 'hello.I64_thing = '+IntToStr(hello.I64_thing));
764 Expect( hello.__isset_String_thing, 'hello.__isset_String_thing = '+BoolToString(hello.__isset_String_thing));
765 Expect( hello.__isset_Byte_thing, 'hello.__isset_Byte_thing = '+BoolToString(hello.__isset_Byte_thing));
766 Expect( hello.__isset_I32_thing, 'hello.__isset_I32_thing = '+BoolToString(hello.__isset_I32_thing));
767 Expect( hello.__isset_I64_thing, 'hello.__isset_I64_thing = '+BoolToString(hello.__isset_I64_thing));
768 end;
769
770
771 // multi args
772 StartTestGroup( 'testMulti');
773 arg0 := 1;
774 arg1 := 2;
775 arg2 := High(Int64);
776 arg3 := TThriftDictionaryImpl<SmallInt, string>.Create;
777 arg3.AddOrSetValue( 1, 'one');
778 arg4 := TNumberz.FIVE;
779 arg5 := 5000000;
780 Console.WriteLine('Test Multi(' + IntToStr( arg0) + ',' +
781 IntToStr( arg1) + ',' + IntToStr( arg2) + ',' +
782 arg3.ToString + ',' + IntToStr( Integer( arg4)) + ',' +
783 IntToStr( arg5) + ')');
784
785 i := client.testMulti( arg0, arg1, arg2, arg3, arg4, arg5);
786 Expect( i.String_thing = 'Hello2', 'testMulti: i.String_thing = "'+i.String_thing+'"');
787 Expect( i.Byte_thing = arg0, 'testMulti: i.Byte_thing = '+IntToStr(i.Byte_thing));
788 Expect( i.I32_thing = arg1, 'testMulti: i.I32_thing = '+IntToStr(i.I32_thing));
789 Expect( i.I64_thing = arg2, 'testMulti: i.I64_thing = '+IntToStr(i.I64_thing));
790 Expect( i.__isset_String_thing, 'testMulti: i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
791 Expect( i.__isset_Byte_thing, 'testMulti: i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
792 Expect( i.__isset_I32_thing, 'testMulti: i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
793 Expect( i.__isset_I64_thing, 'testMulti: i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
794
795 // multi exception
796 StartTestGroup( 'testMultiException(1)');
797 try
798 i := client.testMultiException( 'need more pizza', 'run out of beer');
799 Expect( i.String_thing = 'run out of beer', 'i.String_thing = "' +i.String_thing+ '"');
800 Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
801 Expect( not i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
802 Expect( not i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
803 Expect( not i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
804 except
805 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
806 end;
807
808 StartTestGroup( 'testMultiException(Xception)');
809 try
810 i := client.testMultiException( 'Xception', 'second test');
811 Expect( FALSE, 'testMultiException(''Xception''): must trow an exception');
812 except
813 on x:TXception do begin
814 Expect( x.__isset_ErrorCode, 'x.__isset_ErrorCode = '+BoolToString(x.__isset_ErrorCode));
815 Expect( x.__isset_Message_, 'x.__isset_Message_ = '+BoolToString(x.__isset_Message_));
816 Expect( x.ErrorCode = 1001, 'x.ErrorCode = '+IntToStr(x.ErrorCode));
817 Expect( x.Message_ = 'This is an Xception', 'x.Message = "'+x.Message_+'"');
818 end;
819 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
820 end;
821
822 StartTestGroup( 'testMultiException(Xception2)');
823 try
824 i := client.testMultiException( 'Xception2', 'third test');
825 Expect( FALSE, 'testMultiException(''Xception2''): must trow an exception');
826 except
827 on x:TXception2 do begin
828 Expect( x.__isset_ErrorCode, 'x.__isset_ErrorCode = '+BoolToString(x.__isset_ErrorCode));
829 Expect( x.__isset_Struct_thing, 'x.__isset_Struct_thing = '+BoolToString(x.__isset_Struct_thing));
830 Expect( x.ErrorCode = 2002, 'x.ErrorCode = '+IntToStr(x.ErrorCode));
831 Expect( x.Struct_thing.String_thing = 'This is an Xception2', 'x.Struct_thing.String_thing = "'+x.Struct_thing.String_thing+'"');
832 Expect( x.Struct_thing.__isset_String_thing, 'x.Struct_thing.__isset_String_thing = '+BoolToString(x.Struct_thing.__isset_String_thing));
833 Expect( not x.Struct_thing.__isset_Byte_thing, 'x.Struct_thing.__isset_Byte_thing = '+BoolToString(x.Struct_thing.__isset_Byte_thing));
834 Expect( not x.Struct_thing.__isset_I32_thing, 'x.Struct_thing.__isset_I32_thing = '+BoolToString(x.Struct_thing.__isset_I32_thing));
835 Expect( not x.Struct_thing.__isset_I64_thing, 'x.Struct_thing.__isset_I64_thing = '+BoolToString(x.Struct_thing.__isset_I64_thing));
836 end;
837 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
838 end;
839
840
841 // oneway functions
842 StartTestGroup( 'Test Oneway(1)');
843 client.testOneway(1);
844 Expect( TRUE, 'Test Oneway(1)'); // success := no exception
845
846 // call time
Jens Geyer06045cf2013-03-27 20:26:25 +0200847 {$IFDEF PerfTest}
Roger Meier3bef8c22012-10-06 06:58:00 +0000848 StartTestGroup( 'Test Calltime()');
849 StartTick := GetTIckCount;
850 for k := 0 to 1000 - 1 do
851 begin
852 client.testVoid();
853 end;
854 Console.WriteLine(' = ' + FloatToStr( (GetTickCount - StartTick) / 1000 ) + ' ms a testVoid() call' );
Jens Geyer06045cf2013-03-27 20:26:25 +0200855 {$ENDIF PerfTest}
Roger Meier3bef8c22012-10-06 06:58:00 +0000856
857 // no more tests here
858 StartTestGroup( '');
859end;
860
861
Jens Geyer06045cf2013-03-27 20:26:25 +0200862procedure TClientThread.StressTest(const client : TThriftTest.Iface);
863begin
864 while TRUE do begin
865 try
866 if not FTransport.IsOpen then FTransport.Open; // re-open connection, server has already closed
867 try
868 client.testString('Test');
869 Write('.');
870 finally
871 if FTransport.IsOpen then FTransport.Close;
872 end;
873 except
874 on e:Exception do Writeln(#10+e.message);
875 end;
876 end;
877end;
878
879
Roger Meier3bef8c22012-10-06 06:58:00 +0000880procedure TClientThread.JSONProtocolReadWriteTest;
881// Tests only then read/write procedures of the JSON protocol
882// All tests succeed, if we can read what we wrote before
883// Note that passing this test does not imply, that our JSON is really compatible to what
884// other clients or servers expect as the real JSON. This is beyond the scope of this test.
885var prot : IProtocol;
886 stm : TStringStream;
887 list : IList;
888 binary, binRead : TBytes;
889 i,iErr : Integer;
890const
891 TEST_SHORT = ShortInt( $FE);
892 TEST_SMALL = SmallInt( $FEDC);
893 TEST_LONG = LongInt( $FEDCBA98);
894 TEST_I64 = Int64( $FEDCBA9876543210);
895 TEST_DOUBLE = -1.234e-56;
896 DELTA_DOUBLE = TEST_DOUBLE * 1e-14;
897 TEST_STRING = 'abc-'#$00E4#$00f6#$00fc; // german umlauts (en-us: "funny chars")
898begin
899 stm := TStringStream.Create;
900 try
901 StartTestGroup( 'JsonProtocolTest'); // no more tests here
902
903 // prepare binary data
904 SetLength( binary, $100);
905 for i := Low(binary) to High(binary) do binary[i] := i;
906
907 // output setup
908 prot := TJSONProtocolImpl.Create(
909 TStreamTransportImpl.Create(
910 nil, TThriftStreamAdapterDelphi.Create( stm, FALSE)));
911
912 // write
913 prot.WriteListBegin( TListImpl.Create( TType.String_, 9));
914 prot.WriteBool( TRUE);
915 prot.WriteBool( FALSE);
916 prot.WriteByte( TEST_SHORT);
917 prot.WriteI16( TEST_SMALL);
918 prot.WriteI32( TEST_LONG);
919 prot.WriteI64( TEST_I64);
920 prot.WriteDouble( TEST_DOUBLE);
921 prot.WriteString( TEST_STRING);
922 prot.WriteBinary( binary);
923 prot.WriteListEnd;
924
925 // input setup
926 Expect( stm.Position = stm.Size, 'Stream position/length after write');
927 stm.Position := 0;
928 prot := TJSONProtocolImpl.Create(
929 TStreamTransportImpl.Create(
930 TThriftStreamAdapterDelphi.Create( stm, FALSE), nil));
931
932 // read and compare
933 list := prot.ReadListBegin;
934 Expect( list.ElementType = TType.String_, 'list element type');
935 Expect( list.Count = 9, 'list element count');
936 Expect( prot.ReadBool, 'WriteBool/ReadBool: TRUE');
937 Expect( not prot.ReadBool, 'WriteBool/ReadBool: FALSE');
938 Expect( prot.ReadByte = TEST_SHORT, 'WriteByte/ReadByte');
939 Expect( prot.ReadI16 = TEST_SMALL, 'WriteI16/ReadI16');
940 Expect( prot.ReadI32 = TEST_LONG, 'WriteI32/ReadI32');
941 Expect( prot.ReadI64 = TEST_I64, 'WriteI64/ReadI64');
942 Expect( abs(prot.ReadDouble-TEST_DOUBLE) < abs(DELTA_DOUBLE), 'WriteDouble/ReadDouble');
943 Expect( prot.ReadString = TEST_STRING, 'WriteString/ReadString');
944 binRead := prot.ReadBinary;
945 prot.ReadListEnd;
946
947 // test binary data
948 Expect( Length(binary) = Length(binRead), 'Binary data length check');
949 iErr := -1;
950 for i := Low(binary) to High(binary) do begin
951 if binary[i] <> binRead[i] then begin
952 iErr := i;
953 Break;
954 end;
955 end;
956 if iErr < 0
957 then Expect( TRUE, 'Binary data check ('+IntToStr(Length(binary))+' Bytes)')
958 else Expect( FALSE, 'Binary data check at offset '+IntToStr(iErr));
959
960 Expect( stm.Position = stm.Size, 'Stream position after read');
961
962 finally
963 stm.Free;
964 prot := nil; //-> Release
965 StartTestGroup( ''); // no more tests here
966 end;
967end;
968
969
970procedure TClientThread.StartTestGroup( const aGroup : string);
971begin
972 FTestGroup := aGroup;
973 if FTestGroup <> '' then begin
974 Console.WriteLine('');
975 Console.WriteLine( aGroup+' tests');
976 Console.WriteLine( StringOfChar('-',60));
977 end;
978end;
979
980
981procedure TClientThread.Expect( aTestResult : Boolean; const aTestInfo : string);
982begin
983 if aTestResult then begin
984 Inc(FSuccesses);
985 Console.WriteLine( aTestInfo+': passed');
986 end
987 else begin
988 FErrors.Add( FTestGroup+': '+aTestInfo);
989 Console.WriteLine( aTestInfo+': *** FAILED ***');
990
991 // We have a failed test!
992 // -> issue DebugBreak ONLY if a debugger is attached,
993 // -> unhandled DebugBreaks would cause Windows to terminate the app otherwise
994 if IsDebuggerPresent then asm int 3 end;
995 end;
996end;
997
998
999procedure TClientThread.ReportResults;
1000var nTotal : Integer;
1001 sLine : string;
1002begin
1003 // prevent us from stupid DIV/0 errors
1004 nTotal := FSuccesses + FErrors.Count;
1005 if nTotal = 0 then begin
1006 Console.WriteLine('No results logged');
1007 Exit;
1008 end;
1009
1010 Console.WriteLine('');
1011 Console.WriteLine( StringOfChar('=',60));
1012 Console.WriteLine( IntToStr(nTotal)+' tests performed');
1013 Console.WriteLine( IntToStr(FSuccesses)+' tests succeeded ('+IntToStr(round(100*FSuccesses/nTotal))+'%)');
1014 Console.WriteLine( IntToStr(FErrors.Count)+' tests failed ('+IntToStr(round(100*FErrors.Count/nTotal))+'%)');
1015 Console.WriteLine( StringOfChar('=',60));
1016 if FErrors.Count > 0 then begin
1017 Console.WriteLine('FAILED TESTS:');
1018 for sLine in FErrors do Console.WriteLine('- '+sLine);
1019 Console.WriteLine( StringOfChar('=',60));
1020 InterlockedIncrement( ExitCode); // return <> 0 on errors
1021 end;
1022 Console.WriteLine('');
1023end;
1024
1025
1026constructor TClientThread.Create( const ATransport: ITransport; const AProtocol : IProtocol; ANumIteration: Integer);
1027begin
1028 inherited Create( True );
1029 FNumIteration := ANumIteration;
1030 FTransport := ATransport;
1031 FProtocol := AProtocol;
1032 FConsole := TThreadConsole.Create( Self );
1033
1034 // error list: keep correct order, allow for duplicates
1035 FErrors := TStringList.Create;
1036 FErrors.Sorted := FALSE;
1037 FErrors.Duplicates := dupAccept;
1038end;
1039
1040destructor TClientThread.Destroy;
1041begin
1042 FreeAndNil( FConsole);
1043 FreeAndNil( FErrors);
1044 inherited;
1045end;
1046
1047procedure TClientThread.Execute;
1048var
1049 i : Integer;
1050 proc : TThreadProcedure;
1051begin
1052 // perform all tests
1053 try
1054 for i := 0 to FNumIteration - 1 do
1055 begin
1056 ClientTest;
1057 JSONProtocolReadWriteTest;
1058 end;
1059 except
1060 on e:Exception do Expect( FALSE, 'unexpected exception: "'+e.message+'"');
1061 end;
1062
1063 // report the outcome
1064 ReportResults;
1065
1066 // shutdown
1067 proc := procedure
1068 begin
1069 if FTransport <> nil then
1070 begin
1071 FTransport.Close;
1072 FTransport := nil;
1073 end;
1074 end;
1075
1076 Synchronize( proc );
1077end;
1078
1079{ TThreadConsole }
1080
1081constructor TThreadConsole.Create(AThread: TThread);
1082begin
1083 FThread := AThread;
1084end;
1085
1086procedure TThreadConsole.Write(const S: string);
1087var
1088 proc : TThreadProcedure;
1089begin
1090 proc := procedure
1091 begin
1092 Console.Write( S );
1093 end;
1094 TThread.Synchronize( FThread, proc);
1095end;
1096
1097procedure TThreadConsole.WriteLine(const S: string);
1098var
1099 proc : TThreadProcedure;
1100begin
1101 proc := procedure
1102 begin
1103 Console.WriteLine( S );
1104 end;
1105 TThread.Synchronize( FThread, proc);
1106end;
1107
1108initialization
1109begin
1110 TTestClient.FNumIteration := 1;
1111 TTestClient.FNumThread := 1;
1112end;
1113
1114end.