1 unit ToolsUnit;
2
3 {$IFDEF FPC}
4 {$mode objfpc}{$H+}
5 {$ENDIF}
6
7 interface
8
9 uses
10 Classes, SysUtils, DB, testdecorator, fpcunit;
11
12 Const
13 // Number of "N" test datasets (as opposed to FieldDatasets) that will be created
14 // The connectors should have these records prepared in their Create*Dataset procedures.
15 MaxDataSet = 35;
16 // Number of records in a trace dataset:
17 NForTraceDataset = 15;
18
19 type
20
21 { TDBConnector }
22
23 TDBConnectorClass = class of TDBConnector;
24 TDBConnector = class(TPersistent)
25 private
26 FLogTimeFormat: TFormatSettings; //for error logging only
27 FFormatSettings: TFormatSettings;
28 FChangedFieldDataset : boolean;
GetCharSizenull29 function GetCharSize: integer;
30 protected
31 FChangedDatasets : array[0..MaxDataSet] of boolean;
32 FUsedDatasets : TFPList;
33 procedure SetTestUniDirectional(const AValue: boolean); virtual;
GetTestUniDirectionalnull34 function GetTestUniDirectional: boolean; virtual;
35 // These methods should be implemented by all descendents
36 // They are called each time a test needs a TDataset descendent
37 // n: the dataset index to return (also number of records in set)
38 // Presupposes that Create*Dataset(s) has been called already.
InternalGetNDatasetnull39 Function InternalGetNDataset(n : integer) : TDataset; virtual; abstract;
InternalGetFieldDatasetnull40 Function InternalGetFieldDataset : TDataSet; virtual; abstract;
41
42 // These methods should be implemented by all descendents
43 // They are called e.g. in the constructor. They can be used
44 // to create the tables on disk, or on a DB server
45 procedure CreateNDatasets; virtual; abstract;
46 procedure CreateFieldDataset; virtual; abstract;
47
48 // These methods are called after each test in which a dataset is used
49 // by calling GetXXXDataset with Achange=true
50 // They should reset all data to their right/initial values.
51 procedure ResetNDatasets; virtual;
52 procedure ResetFieldDataset; virtual;
53
54 // These methods are called e.g. in the destructor.
55 // They should clean up all mess, like tables on disk or on a DB server
56 procedure DropNDatasets; virtual; abstract;
57 procedure DropFieldDataset; virtual; abstract;
58
59 // If logging is enabled, writes Message to log file and flushes
60 // Logging uses tab-separated columns
61 procedure LogMessage(Category,Message: string);
62 public
63 constructor Create; virtual;
64 destructor Destroy; override;
65
66 procedure DataEvent(dataset :TDataset);
67
GetNDatasetnull68 Function GetNDataset(n : integer) : TDataset; overload;
GetNDatasetnull69 Function GetNDataset(AChange : Boolean; n : integer) : TDataset; overload;
GetFieldDatasetnull70 Function GetFieldDataset : TDataSet; overload;
GetFieldDatasetnull71 Function GetFieldDataset(AChange : Boolean) : TDataSet; overload;
72
73 // Gets a dataset that tracks calculation of calculated fields etc.
GetTraceDatasetnull74 Function GetTraceDataset(AChange : Boolean) : TDataset; virtual;
75
76 // Run before a test is started
77 procedure StartTest(TestName: string);
78 // Run after a test is stopped
79 procedure StopTest(TestName: string);
80 property TestUniDirectional: boolean read GetTestUniDirectional write SetTestUniDirectional;
81 property FormatSettings: TFormatSettings read FFormatSettings;
82 property CharSize: integer read GetCharSize;
83 end;
84
85 { TTestDataLink }
86
87 TTestDataLink = class(TDataLink)
88 protected
89 procedure DataSetScrolled(Distance: Integer); override;
90 procedure DataSetChanged; override;
91 {$IFDEF fpc}
92 procedure DataEvent(Event: TDataEvent; Info: Ptrint); override;
93 {$ELSE}
94 procedure DataEvent(Event: TDataEvent; Info: longint); override;
95 {$ENDIF}
96 end;
97
98 { TDBBasicsTestSetup }
99
100 TDBBasicsTestSetup = class(TTestSetup)
101 protected
102 procedure OneTimeSetup; override;
103 procedure OneTimeTearDown; override;
104 end;
105
106 { TDBBasicsTestCase }
107 TDBBasicsTestCase = class(TTestCase)
108 protected
109 procedure SetUp; override;
110 procedure TearDown; override;
111 // Verify whether all values in FieldDataset are present and correct
112 procedure CheckFieldDatasetValues(ADataSet: TDataSet);
113 // Verify whether all values in NDataset are present and correct
114 procedure CheckNDatasetValues(ADataSet: TDataSet; n: integer);
115 end;
116
117
118 const
119 DataEventnames : Array [TDataEvent] of String[21] =
120 ('deFieldChange', 'deRecordChange', 'deDataSetChange', 'deDataSetScroll',
121 'deLayoutChange', 'deUpdateRecord', 'deUpdateState', 'deCheckBrowseMode',
122 'dePropertyChange', 'deFieldListChange', 'deFocusControl' ,'deParentScroll',
123 'deConnectChange', 'deReconcileError', 'deDisabledStateChange');
124
125
126 const
127 testValuesCount = 25;
128 testFloatValues : Array[0..testValuesCount-1] of double = (-maxSmallint-1,-maxSmallint,-256,-255,-128,-127,-1,0,1,127,128,255,256,maxSmallint,maxSmallint+1,0.123456,-0.123456,4.35,12.434E7,9.876e-5,123.45678,2.4,3.2,0.4,23);
129 testCurrencyValues : Array[0..testValuesCount-1] of currency = (-MaxLongInt-1,-MaxSmallint-1,-256,-255,-43.34,-2.5,-0.21,0,0.32,45.45,256,45,1234.56,12.34,0.12,MaxSmallInt+1,MaxLongInt+1,-6871947.67,68719476736,2748779069.44,922337203685.47,-92233720368547,99999999999999,-9223372036854.25,-9223372036854.7);
130 testFmtBCDValues : Array[0..testValuesCount-1] of string = ('-100','-65.5','-54.3333','-43.3334','-2.5','-0.234567','45.4','0.3','45.414585','127','128','255','256','45','0.3','45.4','127','128','255','256','45','1234.56789','43.23','43.500001','99.88');
131 testIntValues : Array[0..testValuesCount-1] of integer = (-maxInt,-maxInt+1,-maxSmallint-1,-maxSmallint,-256,-255,-128,-127,-1,0,1,127,128,255,256,maxSmallint,maxSmallint+1,MaxInt-1,MaxInt,100,130,150,-150,-132,234);
132 testWordValues : Array[0..testValuesCount-1] of Word = (1,2,3,4,5,6,7,8,0,1,127,128,255,256,maxSmallint,maxSmallint+1,maxSmallInt-1,maxSmallInt,65535,100,130,150,151,132,234);
133 testSmallIntValues : Array[0..testValuesCount-1] of smallint = (-maxSmallint,-maxSmallint+1,-256,-255,-128,-127,-1,0,1,127,128,255,256,maxSmallint,maxSmallint-1,100,110,120,130,150,-150,-132,234,231,42);
134 testLargeIntValues : Array[0..testValuesCount-1] of LargeInt = (-$7fffffffffffffff,-$7ffffffffffffffe,-maxInt-1,-maxInt+1,-maxSmallint,-maxSmallint+1,-256,-255,-128,-127,-1,0,1,127,128,255,256,maxSmallint,maxSmallint-1,maxSmallint+1,MaxInt-1,MaxInt,$7fffffffffffffff-1,$7fffffffffffffff,235253244);
135 testBooleanValues : Array[0..testValuesCount-1] of boolean = (true,false,false,true,true,false,false,true,false,true,true,true,false,false,false,false,true,true,true,true,false,true,true,false,false);
136 testStringValues : Array[0..testValuesCount-1] of string = (
137 '',
138 'a',
139 'ab',
140 'abc',
141 'abcd',
142 'abcde',
143 'abcdef',
144 'abcdefg',
145 'abcdefgh',
146 'abcdefghi',
147 'abcdefghij',
148 'lMnOpQrStU',
149 '1234567890',
150 '_!@#$%^&*(',
151 '_!@#$%^&*(',
152 ' ''quotes'' ',
153 ')-;:/?.<>',
154 '~`|{}- =', // note that there's no \ (backslash) since some db's uses that as escape-character
155 ' WRaP ',
156 'wRaP ',
157 ' wRAP',
158 'this',
159 // 'is',
160 'fun',
161 'VB7^',
162 'vdfbst'
163 );
164
165 testDateValues : Array[0..testValuesCount-1] of string = (
166 '2000-01-01',
167 '1999-12-31',
168 '2004-02-29',
169 '2004-03-01',
170 '1991-02-28',
171 '1991-03-01',
172 '1997-11-29',
173 '2040-10-16',
174 '1977-09-29',
175 '1977-12-31',
176 '1917-12-29',
177 '1900-01-01',
178 '1899-12-31',
179 '1899-12-30',
180 '1899-12-29',
181 '1800-03-30',
182 '1754-06-04',
183 '1753-01-01',
184 '1650-05-10',
185 '0904-04-12',
186 '0199-07-09',
187 '0079-11-29',
188 '0031-11-02',
189 '0001-12-31',
190 '0001-01-01'
191 );
192
193 testTimeValues : Array[0..testValuesCount-1] of string = (
194 '10:45:12.000',
195 '00:00:00.000',
196 '24:00:00.000',
197 '33:25:15.000',
198 '04:59:16.000',
199 '05:45:59.000',
200 '11:45:12.000',
201 '12:45:12.000',
202 '14:45:14.000',
203 '14:45:52.000',
204 '15:35:12.000',
205 '16:35:42.000',
206 '16:45:12.000',
207 '18:45:22.000',
208 '19:45:12.000',
209 '16:45:12.010',
210 '13:55:12.200',
211 '13:46:12.543',
212 '15:35:12.000',
213 '17:25:12.530',
214 '19:45:12.003',
215 '10:54:12.999',
216 '12:25:12.000',
217 '20:15:12.758',
218 '23:59:59.000'
219 );
220
221
222 var dbtype,
223 dbconnectorname,
224 dbconnectorparams,
225 dbname,
226 dbuser,
227 dbhostname,
228 dbpassword,
229 dbcharset,
230 dblogfilename,
231 dbQuoteChars : string;
232 dblogfile : TextFile;
233 DataEvents : string;
234 DBConnector : TDBConnector;
235 testValues : Array [TFieldType,0..testvaluescount -1] of string;
236
237
238 procedure InitialiseDBConnector;
239 procedure FreeDBConnector;
240
DateTimeToTimeStringnull241 function DateTimeToTimeString(d: tdatetime) : string;
TimeStringToDateTimenull242 function TimeStringToDateTime(d: String): TDateTime;
StringToByteArraynull243 function StringToByteArray(const s: ansistring): Variant;
244
245
246 implementation
247
248 uses
249 inifiles, FmtBCD, Variants;
250
251 var DBConnectorRefCount: integer;
252
253 { TDBConnector }
254
255 constructor TDBConnector.Create;
256 begin
257 FFormatSettings.DecimalSeparator:='.';
258 FFormatSettings.ThousandSeparator:=#0;
259 FFormatSettings.DateSeparator:='-';
260 FFormatSettings.TimeSeparator:=':';
261 FFormatSettings.ShortDateFormat:='yyyy/mm/dd';
262 FFormatSettings.LongTimeFormat:='hh:nn:ss.zzz';
263
264 // Set up time format for logging output:
265 // ISO 8601 type date string so logging is uniform across machines
266 FLogTimeFormat.DecimalSeparator:='.';
267 FLogTimeFormat.ThousandSeparator:=#0;
268 FLogTimeFormat.DateSeparator:='-';
269 FLogTimeFormat.TimeSeparator:=':';
270 FLogTimeFormat.ShortDateFormat:='yyyy-mm-dd';
271 FLogTimeFormat.LongTimeFormat:='hh:nn:ss';
272
273
274 FUsedDatasets := TFPList.Create;
275 CreateFieldDataset;
276 CreateNDatasets;
277 end;
278
279 destructor TDBConnector.Destroy;
280 begin
281 if assigned(FUsedDatasets) then FUsedDatasets.Destroy;
282 DropNDatasets;
283 DropFieldDataset;
284 Inherited;
285 end;
286
GetTestUniDirectionalnull287 function TDBConnector.GetTestUniDirectional: boolean;
288 begin
289 result := false;
290 end;
291
292 procedure TDBConnector.SetTestUniDirectional(const AValue: boolean);
293 begin
294 raise exception.create('Connector does not support tests for unidirectional datasets');
295 end;
296
297 procedure TDBConnector.DataEvent(dataset: TDataset);
298 begin
299 DataEvents := DataEvents + 'DataEvent' + ';';
300 end;
301
302 procedure TDBConnector.ResetNDatasets;
303 begin
304 DropNDatasets;
305 CreateNDatasets;
306 end;
307
308 procedure TDBConnector.ResetFieldDataset;
309 begin
310 DropFieldDataset;
311 CreateFieldDataset;
312 end;
313
TDBConnector.GetNDatasetnull314 function TDBConnector.GetNDataset(n: integer): TDataset;
315 begin
316 Result := GetNDataset(False,n);
317 end;
318
TDBConnector.GetNDatasetnull319 function TDBConnector.GetNDataset(AChange : Boolean; n: integer): TDataset;
320 begin
321 if AChange then FChangedDatasets[n] := True;
322 Result := InternalGetNDataset(n);
323 FUsedDatasets.Add(Result);
324 end;
325
GetFieldDatasetnull326 function TDBConnector.GetFieldDataset: TDataSet;
327 begin
328 Result := GetFieldDataset(False);
329 end;
330
GetFieldDatasetnull331 function TDBConnector.GetFieldDataset(AChange: Boolean): TDataSet;
332 begin
333 if AChange then FChangedFieldDataset := True;
334 Result := InternalGetFieldDataset;
335 FUsedDatasets.Add(Result);
336 end;
337
TDBConnector.GetTraceDatasetnull338 function TDBConnector.GetTraceDataset(AChange: Boolean): TDataset;
339 begin
340 result := GetNDataset(AChange,NForTraceDataset);
341 end;
342
343 procedure TDBConnector.StartTest(TestName: string);
344 begin
345 // Log if necessary
346 LogMessage('Test','Starting test '+TestName);
347 end;
348
349 procedure TDBConnector.StopTest(TestName: string);
350 var i : integer;
351 ds : TDataset;
352 begin
353 LogMessage('Test','Stopping test '+TestName);
354 for i := 0 to FUsedDatasets.Count -1 do
355 begin
356 ds := tdataset(FUsedDatasets[i]);
357 if ds.active then ds.Close;
358 ds.Free;
359 end;
360 FUsedDatasets.Clear;
361 if FChangedFieldDataset then ResetFieldDataset;
362 for i := 0 to MaxDataSet do if FChangedDatasets[i] then
363 begin
364 ResetNDatasets;
365 fillchar(FChangedDatasets,sizeof(FChangedDatasets),ord(False));
366 break;
367 end;
368 end;
369
370 procedure TDBConnector.LogMessage(Category,Message: string);
371 begin
372 if dblogfilename<>'' then //double check: only if logging enabled
373 begin
374 try
375 Message:=StringReplace(Message, #9, '\t', [rfReplaceAll, rfIgnoreCase]);
376 Message:=StringReplace(Message, LineEnding, '\n', [rfReplaceAll, rfIgnoreCase]);
377 writeln(dbLogFile, TimeToStr(Now(), FLogTimeFormat) + #9 +
378 Category + #9 +
379 Message);
380 Flush(dbLogFile); //in case tests crash
381 except
382 // ignore log file errors
383 end;
384 end;
385 end;
386
TDBConnector.GetCharSizenull387 function TDBConnector.GetCharSize: integer;
388 begin
389 case LowerCase(dbcharset) of
390 'utf8','utf-8','utf8mb4':
391 Result := 4;
392 else
393 Result := 1;
394 end;
395 end;
396
397
398 { TTestDataLink }
399
400 procedure TTestDataLink.DataSetScrolled(Distance: Integer);
401 begin
402 DataEvents := DataEvents + 'DataSetScrolled' + ':' + inttostr(Distance) + ';';
403 inherited DataSetScrolled(Distance);
404 end;
405
406 procedure TTestDataLink.DataSetChanged;
407 begin
408 DataEvents := DataEvents + 'DataSetChanged;';
409 inherited DataSetChanged;
410 end;
411
412 {$IFDEF FPC}
413 procedure TTestDataLink.DataEvent(Event: TDataEvent; Info: Ptrint);
414 {$ELSE}
415 procedure TTestDataLink.DataEvent(Event: TDataEvent; Info: Longint);
416 {$ENDIF}
417 begin
418 if Event <> deFieldChange then
419 DataEvents := DataEvents + DataEventnames[Event] + ':' + inttostr(info) + ';'
420 else
421 DataEvents := DataEvents + DataEventnames[Event] + ':' + TField(info).FieldName + ';';
422 inherited DataEvent(Event, Info);
423 end;
424
425
426 { TDBBasicsTestSetup }
427
428 procedure TDBBasicsTestSetup.OneTimeSetup;
429 begin
430 InitialiseDBConnector;
431 end;
432
433 procedure TDBBasicsTestSetup.OneTimeTearDown;
434 begin
435 FreeDBConnector;
436 end;
437
438 { TDBBasicsTestCase }
439
440 procedure TDBBasicsTestCase.SetUp;
441 begin
442 inherited SetUp;
443 DBConnector.StartTest(TestName);
444 end;
445
446 procedure TDBBasicsTestCase.TearDown;
447 begin
448 DBConnector.StopTest(TestName);
449 inherited TearDown;
450 end;
451
452 procedure TDBBasicsTestCase.CheckFieldDatasetValues(ADataSet: TDataSet);
453 var i: integer;
454 begin
455 with ADataSet do
456 begin
457 First;
458 for i := 0 to testValuesCount-1 do
459 begin
460 CheckEquals(i, FieldByName('ID').AsInteger, 'ID');
461 CheckEquals(testStringValues[i], FieldByName('FSTRING').AsString, 'FSTRING');
462 CheckEquals(testIntValues[i], FieldByName('FINTEGER').AsInteger, 'FINTEGER');
463 CheckEquals(testLargeIntValues[i], FieldByName('FLARGEINT').AsLargeInt, 'FLARGEINT');
464 Next;
465 end;
466 CheckTrue(Eof, 'Eof');
467 end;
468 end;
469
470 procedure TDBBasicsTestCase.CheckNDatasetValues(ADataSet: TDataSet; n: integer);
471 var i: integer;
472 begin
473 with ADataSet do
474 begin
475 First;
476 for i := 1 to n do
477 begin
478 CheckEquals(i, FieldByName('ID').AsInteger, 'ID');
479 CheckEquals('TestName' + inttostr(i), FieldByName('NAME').AsString, 'NAME');
480 Next;
481 end;
482 CheckTrue(Eof, 'Eof');
483 end;
484 end;
485
486
487 procedure ReadIniFile;
488
489 var IniFile : TIniFile;
490
491 begin
492 IniFile := TIniFile.Create(GetCurrentDir + PathDelim + 'database.ini');
493 dbtype:='';
494 if ParamCount>0 then
495 dbtype := ParamStr(1);
496 if (dbtype='') or not IniFile.SectionExists(dbtype) then
497 dbtype := IniFile.ReadString('Database','Type','');
498 dbconnectorname := IniFile.ReadString(dbtype,'Connector','');
499 dbname := IniFile.ReadString(dbtype,'Name','');
500 dbuser := IniFile.ReadString(dbtype,'User','');
501 dbhostname := IniFile.ReadString(dbtype,'Hostname','');
502 dbpassword := IniFile.ReadString(dbtype,'Password','');
503 dbcharset := IniFile.ReadString(dbtype,'CharSet','');
504 dbconnectorparams := IniFile.ReadString(dbtype,'ConnectorParams','');
505 dblogfilename := IniFile.ReadString(dbtype,'LogFile','');
506 dbquotechars := IniFile.ReadString(dbtype,'QuoteChars','"');
507
508 IniFile.Free;
509 end;
510
511 procedure SetupLog;
512 begin
513 if dblogfilename<>'' then
514 begin
515 try
516 AssignFile(dblogfile,dblogfilename);
517 if not(FileExists(dblogfilename)) then
518 begin
519 ReWrite(dblogfile);
520 CloseFile(dblogfile);
521 end;
522 Append(dblogfile);
523 except
524 dblogfilename:=''; //rest of code relies on this as a log switch
525 end;
526 end;
527 end;
528
529 procedure CloseLog;
530 begin
531 if dblogfilename<>'' then
532 begin
533 try
534 CloseFile(dbLogFile);
535 except
536 // Ignore log file errors
537 end;
538 end;
539 end;
540
541 procedure InitialiseDBConnector;
542
543 var DBConnectorClass : TPersistentClass;
544 i : integer;
545 FormatSettings : TFormatSettings;
546 begin
547 if DBConnectorRefCount>0 then exit;
548
549 FormatSettings.DecimalSeparator:='.';
550 FormatSettings.ThousandSeparator:=#0;
551
552 testValues[ftString] := testStringValues;
553 testValues[ftFixedChar] := testStringValues;
554 testValues[ftTime] := testTimeValues;
555 testValues[ftDate] := testDateValues;
556 testValues[ftBlob] := testStringValues;
557 testValues[ftMemo] := testStringValues;
558 testValues[ftWideString] := testStringValues;
559 testValues[ftWideMemo] := testStringValues;
560 testValues[ftFMTBcd] := testFmtBCDValues;
561 for i := 0 to testValuesCount-1 do
562 begin
563 testValues[ftBoolean,i] := BoolToStr(testBooleanValues[i], True);
564 testValues[ftFloat,i] := FloatToStr(testFloatValues[i],FormatSettings);
565 testValues[ftSmallint,i] := IntToStr(testSmallIntValues[i]);
566 testValues[ftInteger,i] := IntToStr(testIntValues[i]);
567 testValues[ftWord,i] := IntToStr(testWordValues[i]);
568 testValues[ftLargeint,i] := IntToStr(testLargeIntValues[i]);
569 testValues[ftCurrency,i] := CurrToStr(testCurrencyValues[i],FormatSettings);
570 testValues[ftBCD,i] := CurrToStr(testCurrencyValues[i],FormatSettings);
571 // For date '0001-01-01' other time-part like '00:00:00' causes "Invalid variant type cast", because of < MinDateTime constant
572 if (testDateValues[i]>'0001-01-01') and (testTimeValues[i]>='00:00:01') and (testTimeValues[i]<'24:00:00') then
573 testValues[ftDateTime,i] := testDateValues[i] + ' ' + testTimeValues[i]
574 else
575 testValues[ftDateTime,i] := testDateValues[i];
576 end;
577
578 if dbconnectorname = '' then raise Exception.Create('There is no db connector specified');
579 DBConnectorClass := GetClass('T'+dbconnectorname+'DBConnector');
580 if assigned(DBConnectorClass) then
581 DBConnector := TDBConnectorClass(DBConnectorClass).create
582 else Raise Exception.Create('Unknown db connector specified: ' + 'T'+dbconnectorname+'DBConnector');
583 inc(DBConnectorRefCount);
584 end;
585
586 procedure FreeDBConnector;
587 begin
588 dec(DBConnectorRefCount);
589 if DBConnectorRefCount=0 then
590 FreeAndNil(DBConnector);
591 end;
592
DateTimeToTimeStringnull593 function DateTimeToTimeString(d: tdatetime): string;
594 var
595 millisecond: word;
596 second : word;
597 minute : word;
598 hour : word;
599 begin
600 // Format the datetime in the format hh:nn:ss.zzz, where the hours can be bigger then 23.
601 DecodeTime(d,hour,minute,second,millisecond);
602 hour := hour + (trunc(d) * 24);
603 result := Format('%.2d:%.2d:%.2d.%.3d',[hour,minute,second,millisecond]);
604 end;
605
TimeStringToDateTimenull606 function TimeStringToDateTime(d: String): TDateTime;
607 var
608 millisecond: word;
609 second : word;
610 minute : word;
611 hour : word;
612 days : word;
613 begin
614 // Convert the string in the format hh:nn:ss.zzz to a datetime.
615 hour := strtoint(copy(d,1,2));
616 minute := strtoint(copy(d,4,2));
617 second := strtoint(copy(d,7,2));
618 millisecond := strtoint(copy(d,10,3));
619
620 days := hour div 24;
621 hour := hour mod 24;
622
623 result := ComposeDateTime(days,EncodeTime(hour,minute,second,millisecond));
624 end;
625
StringToByteArraynull626 function StringToByteArray(const s: ansistring): Variant;
627 var P: Pointer;
628 Len: integer;
629 begin
630 Len := Length(s) * SizeOf(AnsiChar);
631 Result := VarArrayCreate([0, Len-1], varByte);
632 P := VarArrayLock(Result);
633 try
634 Move(s[1], P^, Len);
635 finally
636 VarArrayUnlock(Result);
637 end;
638 end;
639
640
641 initialization
642 ReadIniFile;
643 SetupLog;
644 DBConnectorRefCount:=0;
645
646 finalization
647 CloseLog;
648 end.
649
650