blob: e63bd4cbc74c9d50110867a4fd8727fe813d493d [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 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;
413 on e:TException do Expect( FALSE, 'Unexpected exception type "'+e.ClassName+'"');
414 on e:Exception do Expect( FALSE, 'Unexpected exception type "'+e.ClassName+'"');
415 end;
416
417 // case 3: no exception
418 try
419 client.testException('something');
420 Expect( TRUE, 'testException(''something''): must not trow an exception');
421 except
422 on e:TTransportException do Expect( FALSE, 'Unexpected : "'+e.ToString+'"');
423 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
424 end;
425
426
427 // simple things
428 StartTestGroup( 'simple Thrift calls');
429 client.testVoid();
430 Expect( TRUE, 'testVoid()'); // success := no exception
431
432 s := client.testString('Test');
433 Expect( s = 'Test', 'testString(''Test'') = "'+s+'"');
434
Jens Geyer06045cf2013-03-27 20:26:25 +0200435 s := client.testString(HUGE_TEST_STRING);
436 Expect( length(s) = length(HUGE_TEST_STRING),
437 'testString( lenght(HUGE_TEST_STRING) = '+IntToStr(Length(HUGE_TEST_STRING))+') '
438 +'=> length(result) = '+IntToStr(Length(s)));
439
Roger Meier3bef8c22012-10-06 06:58:00 +0000440 i8 := client.testByte(1);
441 Expect( i8 = 1, 'testByte(1) = ' + IntToStr( i8 ));
442
443 i32 := client.testI32(-1);
444 Expect( i32 = -1, 'testI32(-1) = ' + IntToStr(i32));
445
446 Console.WriteLine('testI64(-34359738368)');
447 i64 := client.testI64(-34359738368);
448 Expect( i64 = -34359738368, 'testI64(-34359738368) = ' + IntToStr( i64));
449
450 Console.WriteLine('testDouble(5.325098235)');
451 dub := client.testDouble(5.325098235);
452 Expect( abs(dub-5.325098235) < 1e-14, 'testDouble(5.325098235) = ' + FloatToStr( dub));
453
454 // structs
455 StartTestGroup( 'testStruct');
456 Console.WriteLine('testStruct({''Zero'', 1, -3, -5})');
457 o := TXtructImpl.Create;
458 o.String_thing := 'Zero';
459 o.Byte_thing := 1;
460 o.I32_thing := -3;
461 o.I64_thing := -5;
462 i := client.testStruct(o);
463 Expect( i.String_thing = 'Zero', 'i.String_thing = "'+i.String_thing+'"');
464 Expect( i.Byte_thing = 1, 'i.Byte_thing = '+IntToStr(i.Byte_thing));
465 Expect( i.I32_thing = -3, 'i.I32_thing = '+IntToStr(i.I32_thing));
466 Expect( i.I64_thing = -5, 'i.I64_thing = '+IntToStr(i.I64_thing));
467 Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
468 Expect( i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
469 Expect( i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
470 Expect( i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
471
472 // nested structs
473 StartTestGroup( 'testNest');
474 Console.WriteLine('testNest({1, {''Zero'', 1, -3, -5}, 5})');
475 o2 := TXtruct2Impl.Create;
476 o2.Byte_thing := 1;
477 o2.Struct_thing := o;
478 o2.I32_thing := 5;
479 i2 := client.testNest(o2);
480 i := i2.Struct_thing;
481 Expect( i.String_thing = 'Zero', 'i.String_thing = "'+i.String_thing+'"');
482 Expect( i.Byte_thing = 1, 'i.Byte_thing = '+IntToStr(i.Byte_thing));
483 Expect( i.I32_thing = -3, 'i.I32_thing = '+IntToStr(i.I32_thing));
484 Expect( i.I64_thing = -5, 'i.I64_thing = '+IntToStr(i.I64_thing));
485 Expect( i2.Byte_thing = 1, 'i2.Byte_thing = '+IntToStr(i2.Byte_thing));
486 Expect( i2.I32_thing = 5, 'i2.I32_thing = '+IntToStr(i2.I32_thing));
487 Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
488 Expect( i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
489 Expect( i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
490 Expect( i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
491 Expect( i2.__isset_Byte_thing, 'i2.__isset_Byte_thing');
492 Expect( i2.__isset_I32_thing, 'i2.__isset_I32_thing');
493
494 // map<type1,type2>: A map of strictly unique keys to values.
495 // Translates to an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc.
496 StartTestGroup( 'testMap');
497 mapout := TThriftDictionaryImpl<Integer,Integer>.Create;
498 for j := 0 to 4 do
499 begin
500 mapout.AddOrSetValue( j, j - 10);
501 end;
502 Console.Write('testMap({');
503 first := True;
504 for key in mapout.Keys do
505 begin
506 if first
507 then first := False
508 else Console.Write( ', ' );
509 Console.Write( IntToStr( key) + ' => ' + IntToStr( mapout[key]));
510 end;
511 Console.WriteLine('})');
512
513 mapin := client.testMap( mapout );
514 Expect( mapin.Count = mapout.Count, 'testMap: mapin.Count = mapout.Count');
515 for j := 0 to 4 do
516 begin
517 Expect( mapout.ContainsKey(j), 'testMap: mapout.ContainsKey('+IntToStr(j)+') = '+BoolToString(mapout.ContainsKey(j)));
518 end;
519 for key in mapin.Keys do
520 begin
521 Expect( mapin[key] = mapout[key], 'testMap: '+IntToStr(key) + ' => ' + IntToStr( mapin[key]));
522 Expect( mapin[key] = key - 10, 'testMap: mapin['+IntToStr(key)+'] = '+IntToStr( mapin[key]));
523 end;
524
525
526 // map<type1,type2>: A map of strictly unique keys to values.
527 // Translates to an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc.
528 StartTestGroup( 'testStringMap');
529 strmapout := TThriftDictionaryImpl<string,string>.Create;
530 for j := 0 to 4 do
531 begin
532 strmapout.AddOrSetValue( IntToStr(j), IntToStr(j - 10));
533 end;
534 Console.Write('testStringMap({');
535 first := True;
536 for strkey in strmapout.Keys do
537 begin
538 if first
539 then first := False
540 else Console.Write( ', ' );
541 Console.Write( strkey + ' => ' + strmapout[strkey]);
542 end;
543 Console.WriteLine('})');
544
545 strmapin := client.testStringMap( strmapout );
546 Expect( strmapin.Count = strmapout.Count, 'testStringMap: strmapin.Count = strmapout.Count');
547 for j := 0 to 4 do
548 begin
549 Expect( strmapout.ContainsKey(IntToStr(j)),
550 'testStringMap: strmapout.ContainsKey('+IntToStr(j)+') = '
551 + BoolToString(strmapout.ContainsKey(IntToStr(j))));
552 end;
553 for strkey in strmapin.Keys do
554 begin
555 Expect( strmapin[strkey] = strmapout[strkey], 'testStringMap: '+strkey + ' => ' + strmapin[strkey]);
556 Expect( strmapin[strkey] = IntToStr( StrToInt(strkey) - 10), 'testStringMap: strmapin['+strkey+'] = '+strmapin[strkey]);
557 end;
558
559
560 // set<type>: An unordered set of unique elements.
561 // Translates to an STL set, Java HashSet, set in Python, etc.
562 // Note: PHP does not support sets, so it is treated similar to a List
563 StartTestGroup( 'testSet');
564 setout := THashSetImpl<Integer>.Create;
565 for j := -2 to 2 do
566 begin
567 setout.Add( j );
568 end;
569 Console.Write('testSet({');
570 first := True;
571 for j in setout do
572 begin
573 if first
574 then first := False
575 else Console.Write(', ');
576 Console.Write(IntToStr( j));
577 end;
578 Console.WriteLine('})');
579
580 setin := client.testSet(setout);
581 Expect( setin.Count = setout.Count, 'testSet: setin.Count = setout.Count');
582 Expect( setin.Count = 5, 'testSet: setin.Count = '+IntToStr(setin.Count));
583 for j := -2 to 2 do // unordered, we can't rely on the order => test for known elements only
584 begin
585 Expect( setin.Contains(j), 'testSet: setin.Contains('+IntToStr(j)+') => '+BoolToString(setin.Contains(j)));
586 end;
587
588 // list<type>: An ordered list of elements.
589 // Translates to an STL vector, Java ArrayList, native arrays in scripting languages, etc.
590 StartTestGroup( 'testList');
591 listout := TThriftListImpl<Integer>.Create;
592 listout.Add( +1);
593 listout.Add( -2);
594 listout.Add( +3);
595 listout.Add( -4);
596 listout.Add( 0);
597 Console.Write('testList({');
598 first := True;
599 for j in listout do
600 begin
601 if first
602 then first := False
603 else Console.Write(', ');
604 Console.Write(IntToStr( j));
605 end;
606 Console.WriteLine('})');
607
608 listin := client.testList(listout);
609 Expect( listin.Count = listout.Count, 'testList: listin.Count = listout.Count');
610 Expect( listin.Count = 5, 'testList: listin.Count = '+IntToStr(listin.Count));
611 Expect( listin[0] = +1, 'listin[0] = '+IntToStr( listin[0]));
612 Expect( listin[1] = -2, 'listin[1] = '+IntToStr( listin[1]));
613 Expect( listin[2] = +3, 'listin[2] = '+IntToStr( listin[2]));
614 Expect( listin[3] = -4, 'listin[3] = '+IntToStr( listin[3]));
615 Expect( listin[4] = 0, 'listin[4] = '+IntToStr( listin[4]));
616
617 // enums
618 ret := client.testEnum(TNumberz.ONE);
619 Expect( ret = TNumberz.ONE, 'testEnum(ONE) = '+IntToStr(Ord(ret)));
620
621 ret := client.testEnum(TNumberz.TWO);
622 Expect( ret = TNumberz.TWO, 'testEnum(TWO) = '+IntToStr(Ord(ret)));
623
624 ret := client.testEnum(TNumberz.THREE);
625 Expect( ret = TNumberz.THREE, 'testEnum(THREE) = '+IntToStr(Ord(ret)));
626
627 ret := client.testEnum(TNumberz.FIVE);
628 Expect( ret = TNumberz.FIVE, 'testEnum(FIVE) = '+IntToStr(Ord(ret)));
629
630 ret := client.testEnum(TNumberz.EIGHT);
631 Expect( ret = TNumberz.EIGHT, 'testEnum(EIGHT) = '+IntToStr(Ord(ret)));
632
633
634 // typedef
635 uid := client.testTypedef(309858235082523);
636 Expect( uid = 309858235082523, 'testTypedef(309858235082523) = '+IntToStr(uid));
637
638
639 // maps of maps
640 StartTestGroup( 'testMapMap(1)');
641 mm := client.testMapMap(1);
642 Console.Write(' = {');
643 for key in mm.Keys do
644 begin
645 Console.Write( IntToStr( key) + ' => {');
646 m2 := mm[key];
647 for k2 in m2.Keys do
648 begin
649 Console.Write( IntToStr( k2) + ' => ' + IntToStr( m2[k2]) + ', ');
650 end;
651 Console.Write('}, ');
652 end;
653 Console.WriteLine('}');
654
655 // verify result data
656 Expect( mm.Count = 2, 'mm.Count = '+IntToStr(mm.Count));
657 pos := mm[4];
658 neg := mm[-4];
659 for j := 1 to 4 do
660 begin
661 Expect( pos[j] = j, 'pos[j] = '+IntToStr(pos[j]));
662 Expect( neg[-j] = -j, 'neg[-j] = '+IntToStr(neg[-j]));
663 end;
664
665
666
667 // insanity
668 StartTestGroup( 'testInsanity');
669 insane := TInsanityImpl.Create;
670 insane.UserMap := TThriftDictionaryImpl<TNumberz, Int64>.Create;
671 insane.UserMap.AddOrSetValue( TNumberz.FIVE, 5000);
672 truck := TXtructImpl.Create;
673 truck.String_thing := 'Truck';
674 truck.Byte_thing := 8;
675 truck.I32_thing := 8;
676 truck.I64_thing := 8;
677 insane.Xtructs := TThriftListImpl<IXtruct>.Create;
678 insane.Xtructs.Add( truck );
679 whoa := client.testInsanity( insane );
680 Console.Write(' = {');
681 for key64 in whoa.Keys do
682 begin
683 val := whoa[key64];
684 Console.Write( IntToStr( key64) + ' => {');
685 for k2_2 in val.Keys do
686 begin
687 v2 := val[k2_2];
688 Console.Write( IntToStr( Integer( k2_2)) + ' => {');
689 userMap := v2.UserMap;
690 Console.Write('{');
691 if userMap <> nil then
692 begin
693 for k3 in userMap.Keys do
694 begin
695 Console.Write( IntToStr( Integer( k3)) + ' => ' + IntToStr( userMap[k3]) + ', ');
696 end;
697 end else
698 begin
699 Console.Write('null');
700 end;
701 Console.Write('}, ');
702 xtructs := v2.Xtructs;
703 Console.Write('{');
704
705 if xtructs <> nil then
706 begin
707 for x in xtructs do
708 begin
709 Console.Write('{"' + x.String_thing + '", ' +
710 IntToStr( x.Byte_thing) + ', ' +
711 IntToStr( x.I32_thing) + ', ' +
712 IntToStr( x.I32_thing) + '}, ');
713 end;
714 end else
715 begin
716 Console.Write('null');
717 end;
718 Console.Write('}');
719 Console.Write('}, ');
720 end;
721 Console.Write('}, ');
722 end;
723 Console.WriteLine('}');
724
725 // verify result data
726 Expect( whoa.Count = 2, 'whoa.Count = '+IntToStr(whoa.Count));
727 //
728 first_map := whoa[1];
729 second_map := whoa[2];
730 Expect( first_map.Count = 2, 'first_map.Count = '+IntToStr(first_map.Count));
731 Expect( second_map.Count = 1, 'second_map.Count = '+IntToStr(second_map.Count));
732 //
733 looney := second_map[TNumberz.SIX];
734 Expect( Assigned(looney), 'Assigned(looney) = '+BoolToString(Assigned(looney)));
735 Expect( not looney.__isset_UserMap, 'looney.__isset_UserMap = '+BoolToString(looney.__isset_UserMap));
736 Expect( not looney.__isset_Xtructs, 'looney.__isset_Xtructs = '+BoolToString(looney.__isset_Xtructs));
737 //
738 for ret in [TNumberz.TWO, TNumberz.THREE] do begin
739 crazy := first_map[ret];
740 Console.WriteLine('first_map['+intToStr(Ord(ret))+']');
741
742 Expect( crazy.__isset_UserMap, 'crazy.__isset_UserMap = '+BoolToString(crazy.__isset_UserMap));
743 Expect( crazy.__isset_Xtructs, 'crazy.__isset_Xtructs = '+BoolToString(crazy.__isset_Xtructs));
744
745 Expect( crazy.UserMap.Count = 2, 'crazy.UserMap.Count = '+IntToStr(crazy.UserMap.Count));
746 Expect( crazy.UserMap[TNumberz.FIVE] = 5, 'crazy.UserMap[TNumberz.FIVE] = '+IntToStr(crazy.UserMap[TNumberz.FIVE]));
747 Expect( crazy.UserMap[TNumberz.EIGHT] = 8, 'crazy.UserMap[TNumberz.EIGHT] = '+IntToStr(crazy.UserMap[TNumberz.EIGHT]));
748
749 Expect( crazy.Xtructs.Count = 2, 'crazy.Xtructs.Count = '+IntToStr(crazy.Xtructs.Count));
750 goodbye := crazy.Xtructs[0]; // lists are ordered, so we are allowed to assume this order
751 hello := crazy.Xtructs[1];
752
753 Expect( goodbye.String_thing = 'Goodbye4', 'goodbye.String_thing = "'+goodbye.String_thing+'"');
754 Expect( goodbye.Byte_thing = 4, 'goodbye.Byte_thing = '+IntToStr(goodbye.Byte_thing));
755 Expect( goodbye.I32_thing = 4, 'goodbye.I32_thing = '+IntToStr(goodbye.I32_thing));
756 Expect( goodbye.I64_thing = 4, 'goodbye.I64_thing = '+IntToStr(goodbye.I64_thing));
757 Expect( goodbye.__isset_String_thing, 'goodbye.__isset_String_thing = '+BoolToString(goodbye.__isset_String_thing));
758 Expect( goodbye.__isset_Byte_thing, 'goodbye.__isset_Byte_thing = '+BoolToString(goodbye.__isset_Byte_thing));
759 Expect( goodbye.__isset_I32_thing, 'goodbye.__isset_I32_thing = '+BoolToString(goodbye.__isset_I32_thing));
760 Expect( goodbye.__isset_I64_thing, 'goodbye.__isset_I64_thing = '+BoolToString(goodbye.__isset_I64_thing));
761
762 Expect( hello.String_thing = 'Hello2', 'hello.String_thing = "'+hello.String_thing+'"');
763 Expect( hello.Byte_thing = 2, 'hello.Byte_thing = '+IntToStr(hello.Byte_thing));
764 Expect( hello.I32_thing = 2, 'hello.I32_thing = '+IntToStr(hello.I32_thing));
765 Expect( hello.I64_thing = 2, 'hello.I64_thing = '+IntToStr(hello.I64_thing));
766 Expect( hello.__isset_String_thing, 'hello.__isset_String_thing = '+BoolToString(hello.__isset_String_thing));
767 Expect( hello.__isset_Byte_thing, 'hello.__isset_Byte_thing = '+BoolToString(hello.__isset_Byte_thing));
768 Expect( hello.__isset_I32_thing, 'hello.__isset_I32_thing = '+BoolToString(hello.__isset_I32_thing));
769 Expect( hello.__isset_I64_thing, 'hello.__isset_I64_thing = '+BoolToString(hello.__isset_I64_thing));
770 end;
771
772
773 // multi args
774 StartTestGroup( 'testMulti');
775 arg0 := 1;
776 arg1 := 2;
777 arg2 := High(Int64);
778 arg3 := TThriftDictionaryImpl<SmallInt, string>.Create;
779 arg3.AddOrSetValue( 1, 'one');
780 arg4 := TNumberz.FIVE;
781 arg5 := 5000000;
782 Console.WriteLine('Test Multi(' + IntToStr( arg0) + ',' +
783 IntToStr( arg1) + ',' + IntToStr( arg2) + ',' +
784 arg3.ToString + ',' + IntToStr( Integer( arg4)) + ',' +
785 IntToStr( arg5) + ')');
786
787 i := client.testMulti( arg0, arg1, arg2, arg3, arg4, arg5);
788 Expect( i.String_thing = 'Hello2', 'testMulti: i.String_thing = "'+i.String_thing+'"');
789 Expect( i.Byte_thing = arg0, 'testMulti: i.Byte_thing = '+IntToStr(i.Byte_thing));
790 Expect( i.I32_thing = arg1, 'testMulti: i.I32_thing = '+IntToStr(i.I32_thing));
791 Expect( i.I64_thing = arg2, 'testMulti: i.I64_thing = '+IntToStr(i.I64_thing));
792 Expect( i.__isset_String_thing, 'testMulti: i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
793 Expect( i.__isset_Byte_thing, 'testMulti: i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
794 Expect( i.__isset_I32_thing, 'testMulti: i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
795 Expect( i.__isset_I64_thing, 'testMulti: i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
796
797 // multi exception
798 StartTestGroup( 'testMultiException(1)');
799 try
800 i := client.testMultiException( 'need more pizza', 'run out of beer');
801 Expect( i.String_thing = 'run out of beer', 'i.String_thing = "' +i.String_thing+ '"');
802 Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing));
803 Expect( not i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing));
804 Expect( not i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing));
805 Expect( not i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing));
806 except
807 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
808 end;
809
810 StartTestGroup( 'testMultiException(Xception)');
811 try
812 i := client.testMultiException( 'Xception', 'second test');
813 Expect( FALSE, 'testMultiException(''Xception''): must trow an exception');
814 except
815 on x:TXception do begin
816 Expect( x.__isset_ErrorCode, 'x.__isset_ErrorCode = '+BoolToString(x.__isset_ErrorCode));
817 Expect( x.__isset_Message_, 'x.__isset_Message_ = '+BoolToString(x.__isset_Message_));
818 Expect( x.ErrorCode = 1001, 'x.ErrorCode = '+IntToStr(x.ErrorCode));
819 Expect( x.Message_ = 'This is an Xception', 'x.Message = "'+x.Message_+'"');
820 end;
821 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
822 end;
823
824 StartTestGroup( 'testMultiException(Xception2)');
825 try
826 i := client.testMultiException( 'Xception2', 'third test');
827 Expect( FALSE, 'testMultiException(''Xception2''): must trow an exception');
828 except
829 on x:TXception2 do begin
830 Expect( x.__isset_ErrorCode, 'x.__isset_ErrorCode = '+BoolToString(x.__isset_ErrorCode));
831 Expect( x.__isset_Struct_thing, 'x.__isset_Struct_thing = '+BoolToString(x.__isset_Struct_thing));
832 Expect( x.ErrorCode = 2002, 'x.ErrorCode = '+IntToStr(x.ErrorCode));
833 Expect( x.Struct_thing.String_thing = 'This is an Xception2', 'x.Struct_thing.String_thing = "'+x.Struct_thing.String_thing+'"');
834 Expect( x.Struct_thing.__isset_String_thing, 'x.Struct_thing.__isset_String_thing = '+BoolToString(x.Struct_thing.__isset_String_thing));
835 Expect( not x.Struct_thing.__isset_Byte_thing, 'x.Struct_thing.__isset_Byte_thing = '+BoolToString(x.Struct_thing.__isset_Byte_thing));
836 Expect( not x.Struct_thing.__isset_I32_thing, 'x.Struct_thing.__isset_I32_thing = '+BoolToString(x.Struct_thing.__isset_I32_thing));
837 Expect( not x.Struct_thing.__isset_I64_thing, 'x.Struct_thing.__isset_I64_thing = '+BoolToString(x.Struct_thing.__isset_I64_thing));
838 end;
839 on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'"');
840 end;
841
842
843 // oneway functions
844 StartTestGroup( 'Test Oneway(1)');
845 client.testOneway(1);
846 Expect( TRUE, 'Test Oneway(1)'); // success := no exception
847
848 // call time
Jens Geyer06045cf2013-03-27 20:26:25 +0200849 {$IFDEF PerfTest}
Roger Meier3bef8c22012-10-06 06:58:00 +0000850 StartTestGroup( 'Test Calltime()');
851 StartTick := GetTIckCount;
852 for k := 0 to 1000 - 1 do
853 begin
854 client.testVoid();
855 end;
856 Console.WriteLine(' = ' + FloatToStr( (GetTickCount - StartTick) / 1000 ) + ' ms a testVoid() call' );
Jens Geyer06045cf2013-03-27 20:26:25 +0200857 {$ENDIF PerfTest}
Roger Meier3bef8c22012-10-06 06:58:00 +0000858
859 // no more tests here
860 StartTestGroup( '');
861end;
862
863
Jens Geyer718f6ee2013-09-06 21:02:34 +0200864{$IFDEF StressTest}
Jens Geyer06045cf2013-03-27 20:26:25 +0200865procedure TClientThread.StressTest(const client : TThriftTest.Iface);
866begin
867 while TRUE do begin
868 try
869 if not FTransport.IsOpen then FTransport.Open; // re-open connection, server has already closed
870 try
871 client.testString('Test');
872 Write('.');
873 finally
874 if FTransport.IsOpen then FTransport.Close;
875 end;
876 except
877 on e:Exception do Writeln(#10+e.message);
878 end;
879 end;
880end;
Jens Geyer718f6ee2013-09-06 21:02:34 +0200881{$ENDIF}
Jens Geyer06045cf2013-03-27 20:26:25 +0200882
Roger Meier3bef8c22012-10-06 06:58:00 +0000883procedure TClientThread.JSONProtocolReadWriteTest;
884// Tests only then read/write procedures of the JSON protocol
885// All tests succeed, if we can read what we wrote before
886// Note that passing this test does not imply, that our JSON is really compatible to what
887// other clients or servers expect as the real JSON. This is beyond the scope of this test.
888var prot : IProtocol;
889 stm : TStringStream;
890 list : IList;
891 binary, binRead : TBytes;
892 i,iErr : Integer;
893const
894 TEST_SHORT = ShortInt( $FE);
895 TEST_SMALL = SmallInt( $FEDC);
896 TEST_LONG = LongInt( $FEDCBA98);
897 TEST_I64 = Int64( $FEDCBA9876543210);
898 TEST_DOUBLE = -1.234e-56;
899 DELTA_DOUBLE = TEST_DOUBLE * 1e-14;
900 TEST_STRING = 'abc-'#$00E4#$00f6#$00fc; // german umlauts (en-us: "funny chars")
901begin
902 stm := TStringStream.Create;
903 try
904 StartTestGroup( 'JsonProtocolTest'); // no more tests here
905
906 // prepare binary data
907 SetLength( binary, $100);
908 for i := Low(binary) to High(binary) do binary[i] := i;
909
910 // output setup
911 prot := TJSONProtocolImpl.Create(
912 TStreamTransportImpl.Create(
913 nil, TThriftStreamAdapterDelphi.Create( stm, FALSE)));
914
915 // write
916 prot.WriteListBegin( TListImpl.Create( TType.String_, 9));
917 prot.WriteBool( TRUE);
918 prot.WriteBool( FALSE);
919 prot.WriteByte( TEST_SHORT);
920 prot.WriteI16( TEST_SMALL);
921 prot.WriteI32( TEST_LONG);
922 prot.WriteI64( TEST_I64);
923 prot.WriteDouble( TEST_DOUBLE);
924 prot.WriteString( TEST_STRING);
925 prot.WriteBinary( binary);
926 prot.WriteListEnd;
927
928 // input setup
929 Expect( stm.Position = stm.Size, 'Stream position/length after write');
930 stm.Position := 0;
931 prot := TJSONProtocolImpl.Create(
932 TStreamTransportImpl.Create(
933 TThriftStreamAdapterDelphi.Create( stm, FALSE), nil));
934
935 // read and compare
936 list := prot.ReadListBegin;
937 Expect( list.ElementType = TType.String_, 'list element type');
938 Expect( list.Count = 9, 'list element count');
939 Expect( prot.ReadBool, 'WriteBool/ReadBool: TRUE');
940 Expect( not prot.ReadBool, 'WriteBool/ReadBool: FALSE');
941 Expect( prot.ReadByte = TEST_SHORT, 'WriteByte/ReadByte');
942 Expect( prot.ReadI16 = TEST_SMALL, 'WriteI16/ReadI16');
943 Expect( prot.ReadI32 = TEST_LONG, 'WriteI32/ReadI32');
944 Expect( prot.ReadI64 = TEST_I64, 'WriteI64/ReadI64');
945 Expect( abs(prot.ReadDouble-TEST_DOUBLE) < abs(DELTA_DOUBLE), 'WriteDouble/ReadDouble');
946 Expect( prot.ReadString = TEST_STRING, 'WriteString/ReadString');
947 binRead := prot.ReadBinary;
948 prot.ReadListEnd;
949
950 // test binary data
951 Expect( Length(binary) = Length(binRead), 'Binary data length check');
952 iErr := -1;
953 for i := Low(binary) to High(binary) do begin
954 if binary[i] <> binRead[i] then begin
955 iErr := i;
956 Break;
957 end;
958 end;
959 if iErr < 0
960 then Expect( TRUE, 'Binary data check ('+IntToStr(Length(binary))+' Bytes)')
961 else Expect( FALSE, 'Binary data check at offset '+IntToStr(iErr));
962
963 Expect( stm.Position = stm.Size, 'Stream position after read');
964
965 finally
966 stm.Free;
967 prot := nil; //-> Release
968 StartTestGroup( ''); // no more tests here
969 end;
970end;
971
972
973procedure TClientThread.StartTestGroup( const aGroup : string);
974begin
975 FTestGroup := aGroup;
976 if FTestGroup <> '' then begin
977 Console.WriteLine('');
978 Console.WriteLine( aGroup+' tests');
979 Console.WriteLine( StringOfChar('-',60));
980 end;
981end;
982
983
984procedure TClientThread.Expect( aTestResult : Boolean; const aTestInfo : string);
985begin
986 if aTestResult then begin
987 Inc(FSuccesses);
988 Console.WriteLine( aTestInfo+': passed');
989 end
990 else begin
991 FErrors.Add( FTestGroup+': '+aTestInfo);
992 Console.WriteLine( aTestInfo+': *** FAILED ***');
993
994 // We have a failed test!
995 // -> issue DebugBreak ONLY if a debugger is attached,
996 // -> unhandled DebugBreaks would cause Windows to terminate the app otherwise
997 if IsDebuggerPresent then asm int 3 end;
998 end;
999end;
1000
1001
1002procedure TClientThread.ReportResults;
1003var nTotal : Integer;
1004 sLine : string;
1005begin
1006 // prevent us from stupid DIV/0 errors
1007 nTotal := FSuccesses + FErrors.Count;
1008 if nTotal = 0 then begin
1009 Console.WriteLine('No results logged');
1010 Exit;
1011 end;
1012
1013 Console.WriteLine('');
1014 Console.WriteLine( StringOfChar('=',60));
1015 Console.WriteLine( IntToStr(nTotal)+' tests performed');
1016 Console.WriteLine( IntToStr(FSuccesses)+' tests succeeded ('+IntToStr(round(100*FSuccesses/nTotal))+'%)');
1017 Console.WriteLine( IntToStr(FErrors.Count)+' tests failed ('+IntToStr(round(100*FErrors.Count/nTotal))+'%)');
1018 Console.WriteLine( StringOfChar('=',60));
1019 if FErrors.Count > 0 then begin
1020 Console.WriteLine('FAILED TESTS:');
1021 for sLine in FErrors do Console.WriteLine('- '+sLine);
1022 Console.WriteLine( StringOfChar('=',60));
1023 InterlockedIncrement( ExitCode); // return <> 0 on errors
1024 end;
1025 Console.WriteLine('');
1026end;
1027
1028
1029constructor TClientThread.Create( const ATransport: ITransport; const AProtocol : IProtocol; ANumIteration: Integer);
1030begin
1031 inherited Create( True );
1032 FNumIteration := ANumIteration;
1033 FTransport := ATransport;
1034 FProtocol := AProtocol;
1035 FConsole := TThreadConsole.Create( Self );
1036
1037 // error list: keep correct order, allow for duplicates
1038 FErrors := TStringList.Create;
1039 FErrors.Sorted := FALSE;
1040 FErrors.Duplicates := dupAccept;
1041end;
1042
1043destructor TClientThread.Destroy;
1044begin
1045 FreeAndNil( FConsole);
1046 FreeAndNil( FErrors);
1047 inherited;
1048end;
1049
1050procedure TClientThread.Execute;
1051var
1052 i : Integer;
1053 proc : TThreadProcedure;
1054begin
1055 // perform all tests
1056 try
1057 for i := 0 to FNumIteration - 1 do
1058 begin
1059 ClientTest;
1060 JSONProtocolReadWriteTest;
1061 end;
1062 except
1063 on e:Exception do Expect( FALSE, 'unexpected exception: "'+e.message+'"');
1064 end;
1065
1066 // report the outcome
1067 ReportResults;
1068
1069 // shutdown
1070 proc := procedure
1071 begin
1072 if FTransport <> nil then
1073 begin
1074 FTransport.Close;
1075 FTransport := nil;
1076 end;
1077 end;
1078
1079 Synchronize( proc );
1080end;
1081
1082{ TThreadConsole }
1083
1084constructor TThreadConsole.Create(AThread: TThread);
1085begin
Jens Geyer718f6ee2013-09-06 21:02:34 +02001086 inherited Create;
Roger Meier3bef8c22012-10-06 06:58:00 +00001087 FThread := AThread;
1088end;
1089
1090procedure TThreadConsole.Write(const S: string);
1091var
1092 proc : TThreadProcedure;
1093begin
1094 proc := procedure
1095 begin
1096 Console.Write( S );
1097 end;
1098 TThread.Synchronize( FThread, proc);
1099end;
1100
1101procedure TThreadConsole.WriteLine(const S: string);
1102var
1103 proc : TThreadProcedure;
1104begin
1105 proc := procedure
1106 begin
1107 Console.WriteLine( S );
1108 end;
1109 TThread.Synchronize( FThread, proc);
1110end;
1111
1112initialization
1113begin
1114 TTestClient.FNumIteration := 1;
1115 TTestClient.FNumThread := 1;
1116end;
1117
1118end.