toolsunit.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. unit ToolsUnit;
  2. {$IFDEF FPC}
  3. {$mode objfpc}{$H+}
  4. {$ENDIF}
  5. interface
  6. uses
  7. Classes, SysUtils, DB, testdecorator, fpcunit;
  8. Const
  9. // Number of "N" test datasets (as opposed to FieldDatasets) that will be created
  10. // The connectors should have these records prepared in their Create*Dataset procedures.
  11. MaxDataSet = 35;
  12. // Number of records in a trace dataset:
  13. NForTraceDataset = 15;
  14. type
  15. { TDBConnector }
  16. TDBConnectorClass = class of TDBConnector;
  17. TDBConnector = class(TPersistent)
  18. private
  19. FFormatSettings: TFormatSettings;
  20. FChangedFieldDataset : boolean;
  21. protected
  22. FChangedDatasets : array[0..MaxDataSet] of boolean;
  23. FUsedDatasets : TFPList;
  24. procedure SetTestUniDirectional(const AValue: boolean); virtual;
  25. function GetTestUniDirectional: boolean; virtual;
  26. // These methods should be implemented by all descendents
  27. // They are called each time a test needs a TDataset descendent
  28. // n: the dataset index to return (also number of records in set)
  29. // Presupposes that Create*Dataset(s) has been called already.
  30. Function InternalGetNDataset(n : integer) : TDataset; virtual; abstract;
  31. Function InternalGetFieldDataset : TDataSet; virtual; abstract;
  32. // These methods should be implemented by all descendents
  33. // They are called e.g. in the constructor. They can be used
  34. // to create the tables on disk, or on a DB server
  35. procedure CreateNDatasets; virtual; abstract;
  36. procedure CreateFieldDataset; virtual; abstract;
  37. // These methods are called after each test in which a dataset is used
  38. // by calling GetXXXDataset with Achange=true
  39. // They should reset all data to their right/initial values.
  40. procedure ResetNDatasets; virtual;
  41. procedure ResetFieldDataset; virtual;
  42. // These methods are called e.g. in the destructor.
  43. // They should clean up all mess, like tables on disk or on a DB server
  44. procedure DropNDatasets; virtual; abstract;
  45. procedure DropFieldDataset; virtual; abstract;
  46. public
  47. constructor Create; virtual;
  48. destructor Destroy; override;
  49. procedure DataEvent(dataset :TDataset);
  50. Function GetNDataset(n : integer) : TDataset; overload;
  51. Function GetNDataset(AChange : Boolean; n : integer) : TDataset; overload;
  52. Function GetFieldDataset : TDataSet; overload;
  53. Function GetFieldDataset(AChange : Boolean) : TDataSet; overload;
  54. // Gets a dataset that tracks calculation of calculated fields etc.
  55. Function GetTraceDataset(AChange : Boolean) : TDataset; virtual;
  56. procedure StartTest;
  57. procedure StopTest;
  58. property TestUniDirectional: boolean read GetTestUniDirectional write SetTestUniDirectional;
  59. property FormatSettings: TFormatSettings read FFormatSettings;
  60. end;
  61. { TTestDataLink }
  62. TTestDataLink = class(TDataLink)
  63. protected
  64. procedure DataSetScrolled(Distance: Integer); override;
  65. procedure DataSetChanged; override;
  66. {$IFDEF fpc}
  67. procedure DataEvent(Event: TDataEvent; Info: Ptrint); override;
  68. {$ELSE}
  69. procedure DataEvent(Event: TDataEvent; Info: longint); override;
  70. {$ENDIF}
  71. end;
  72. { TDBBasicsTestSetup }
  73. TDBBasicsTestSetup = class(TTestSetup)
  74. protected
  75. procedure OneTimeSetup; override;
  76. procedure OneTimeTearDown; override;
  77. end;
  78. { TDBBasicsTestCase }
  79. TDBBasicsTestCase = class(TTestCase)
  80. protected
  81. procedure SetUp; override;
  82. procedure TearDown; override;
  83. // Verify whether all values in FieldDataset are present and correct
  84. procedure CheckFieldDatasetValues(ADataSet: TDataSet);
  85. // Verify whether all values in NDataset are present and correct
  86. procedure CheckNDatasetValues(ADataSet: TDataSet; n: integer);
  87. end;
  88. const
  89. DataEventnames : Array [TDataEvent] of String[21] =
  90. ('deFieldChange', 'deRecordChange', 'deDataSetChange', 'deDataSetScroll',
  91. 'deLayoutChange', 'deUpdateRecord', 'deUpdateState', 'deCheckBrowseMode',
  92. 'dePropertyChange', 'deFieldListChange', 'deFocusControl' ,'deParentScroll',
  93. 'deConnectChange', 'deReconcileError', 'deDisabledStateChange');
  94. const
  95. testValuesCount = 25;
  96. 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);
  97. testCurrencyValues : Array[0..testValuesCount-1] of currency = (-100,-65.5,-54.34,-43.34,-2.50,-0.2,45.40,0.3,45.4,127,128,255,256,45,0.3,45.4,127,128,255,256,45,1234.56,43.23,43.43,99.88);
  98. 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');
  99. 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);
  100. 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);
  101. 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);
  102. 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);
  103. 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);
  104. testStringValues : Array[0..testValuesCount-1] of string = (
  105. '',
  106. 'a',
  107. 'ab',
  108. 'abc',
  109. 'abcd',
  110. 'abcde',
  111. 'abcdef',
  112. 'abcdefg',
  113. 'abcdefgh',
  114. 'abcdefghi',
  115. 'abcdefghij',
  116. 'lMnOpQrStU',
  117. '1234567890',
  118. '_!@#$%^&*(',
  119. '_!@#$%^&*(',
  120. ' ''quotes'' ',
  121. ')-;:/?.<>',
  122. '~`|{}- =', // note that there's no \ (backslash) since some db's uses that as escape-character
  123. ' WRaP ',
  124. 'wRaP ',
  125. ' wRAP',
  126. 'this',
  127. // 'is',
  128. 'fun',
  129. 'VB7^',
  130. 'vdfbst'
  131. );
  132. testDateValues : Array[0..testValuesCount-1] of string = (
  133. '2000-01-01',
  134. '1999-12-31',
  135. '2004-02-29',
  136. '2004-03-01',
  137. '1991-02-28',
  138. '1991-03-01',
  139. '1997-11-29',
  140. '2040-10-16',
  141. '1977-09-29',
  142. '1977-12-31',
  143. '1917-12-29',
  144. '1900-01-01',
  145. '1899-12-31',
  146. '1899-12-30',
  147. '1899-12-29',
  148. '1800-03-30',
  149. '1754-06-04',
  150. '1753-01-01',
  151. '1650-05-10',
  152. '0904-04-12',
  153. '0199-07-09',
  154. '0079-11-29',
  155. '0031-11-02',
  156. '0001-12-31',
  157. '0001-01-01'
  158. );
  159. testTimeValues : Array[0..testValuesCount-1] of string = (
  160. '10:45:12.000',
  161. '00:00:00.000',
  162. '24:00:00.000',
  163. '33:25:15.000',
  164. '04:59:16.000',
  165. '05:45:59.000',
  166. '16:35:42.000',
  167. '14:45:52.000',
  168. '12:45:12.000',
  169. '18:45:22.000',
  170. '19:45:12.000',
  171. '14:45:14.000',
  172. '16:45:12.000',
  173. '11:45:12.000',
  174. '15:35:12.000',
  175. '16:45:12.010',
  176. '13:55:12.200',
  177. '13:46:12.543',
  178. '15:35:12.000',
  179. '17:25:12.530',
  180. '19:45:12.003',
  181. '10:54:12.999',
  182. '12:25:12.000',
  183. '20:15:12.758',
  184. '12:25:12.000'
  185. );
  186. var dbtype,
  187. dbconnectorname,
  188. dbconnectorparams,
  189. dbname,
  190. dbuser,
  191. dbhostname,
  192. dbpassword,
  193. dbQuoteChars : string;
  194. DataEvents : string;
  195. DBConnector : TDBConnector;
  196. testValues : Array [TFieldType,0..testvaluescount -1] of string;
  197. procedure InitialiseDBConnector;
  198. procedure FreeDBConnector;
  199. function DateTimeToTimeString(d: tdatetime) : string;
  200. function TimeStringToDateTime(d: String): TDateTime;
  201. function StringToByteArray(s: ansistring): Variant;
  202. implementation
  203. uses
  204. inifiles, FmtBCD, Variants;
  205. var DBConnectorRefCount: integer;
  206. { TDBConnector }
  207. constructor TDBConnector.Create;
  208. begin
  209. FFormatSettings.DecimalSeparator:='.';
  210. FFormatSettings.ThousandSeparator:=#0;
  211. FFormatSettings.DateSeparator:='-';
  212. FFormatSettings.TimeSeparator:=':';
  213. FFormatSettings.ShortDateFormat:='yyyy/mm/dd';
  214. FFormatSettings.LongTimeFormat:='hh:nn:ss.zzz';
  215. FUsedDatasets := TFPList.Create;
  216. CreateFieldDataset;
  217. CreateNDatasets;
  218. end;
  219. destructor TDBConnector.Destroy;
  220. begin
  221. if assigned(FUsedDatasets) then FUsedDatasets.Destroy;
  222. DropNDatasets;
  223. DropFieldDataset;
  224. Inherited;
  225. end;
  226. function TDBConnector.GetTestUniDirectional: boolean;
  227. begin
  228. result := false;
  229. end;
  230. procedure TDBConnector.SetTestUniDirectional(const AValue: boolean);
  231. begin
  232. raise exception.create('Connector does not support tests for unidirectional datasets');
  233. end;
  234. procedure TDBConnector.DataEvent(dataset : tdataset);
  235. begin
  236. DataEvents := DataEvents + 'DataEvent' + ';';
  237. end;
  238. procedure TDBConnector.ResetNDatasets;
  239. begin
  240. DropNDatasets;
  241. CreateNDatasets;
  242. end;
  243. procedure TDBConnector.ResetFieldDataset;
  244. begin
  245. DropFieldDataset;
  246. CreateFieldDataset;
  247. end;
  248. function TDBConnector.GetNDataset(n: integer): TDataset;
  249. begin
  250. Result := GetNDataset(False,n);
  251. end;
  252. function TDBConnector.GetNDataset(AChange : Boolean; n: integer): TDataset;
  253. begin
  254. if AChange then FChangedDatasets[n] := True;
  255. Result := InternalGetNDataset(n);
  256. FUsedDatasets.Add(Result);
  257. end;
  258. function TDBConnector.GetFieldDataset: TDataSet;
  259. begin
  260. Result := GetFieldDataset(False);
  261. end;
  262. function TDBConnector.GetFieldDataset(AChange: Boolean): TDataSet;
  263. begin
  264. if AChange then FChangedFieldDataset := True;
  265. Result := InternalGetFieldDataset;
  266. FUsedDatasets.Add(Result);
  267. end;
  268. function TDBConnector.GetTraceDataset(AChange: Boolean): TDataset;
  269. begin
  270. result := GetNDataset(AChange,NForTraceDataset);
  271. end;
  272. procedure TDBConnector.StartTest;
  273. begin
  274. // Do nothing?
  275. end;
  276. procedure TDBConnector.StopTest;
  277. var i : integer;
  278. ds : TDataset;
  279. begin
  280. for i := 0 to FUsedDatasets.Count -1 do
  281. begin
  282. ds := tdataset(FUsedDatasets[i]);
  283. if ds.active then ds.Close;
  284. ds.Free;
  285. end;
  286. FUsedDatasets.Clear;
  287. if FChangedFieldDataset then ResetFieldDataset;
  288. for i := 0 to MaxDataSet do if FChangedDatasets[i] then
  289. begin
  290. ResetNDatasets;
  291. fillchar(FChangedDatasets,sizeof(FChangedDatasets),ord(False));
  292. break;
  293. end;
  294. end;
  295. { TTestDataLink }
  296. procedure TTestDataLink.DataSetScrolled(Distance: Integer);
  297. begin
  298. DataEvents := DataEvents + 'DataSetScrolled' + ':' + inttostr(Distance) + ';';
  299. inherited DataSetScrolled(Distance);
  300. end;
  301. procedure TTestDataLink.DataSetChanged;
  302. begin
  303. DataEvents := DataEvents + 'DataSetChanged;';
  304. inherited DataSetChanged;
  305. end;
  306. {$IFDEF FPC}
  307. procedure TTestDataLink.DataEvent(Event: TDataEvent; Info: Ptrint);
  308. {$ELSE}
  309. procedure TTestDataLink.DataEvent(Event: TDataEvent; Info: Longint);
  310. {$ENDIF}
  311. begin
  312. if Event <> deFieldChange then
  313. DataEvents := DataEvents + DataEventnames[Event] + ':' + inttostr(info) + ';'
  314. else
  315. DataEvents := DataEvents + DataEventnames[Event] + ':' + TField(info).FieldName + ';';
  316. inherited DataEvent(Event, Info);
  317. end;
  318. { TDBBasicsTestSetup }
  319. procedure TDBBasicsTestSetup.OneTimeSetup;
  320. begin
  321. InitialiseDBConnector;
  322. end;
  323. procedure TDBBasicsTestSetup.OneTimeTearDown;
  324. begin
  325. FreeDBConnector;
  326. end;
  327. { TDBBasicsTestCase }
  328. procedure TDBBasicsTestCase.SetUp;
  329. begin
  330. inherited SetUp;
  331. DBConnector.StartTest;
  332. end;
  333. procedure TDBBasicsTestCase.TearDown;
  334. begin
  335. DBConnector.StopTest;
  336. inherited TearDown;
  337. end;
  338. procedure TDBBasicsTestCase.CheckFieldDatasetValues(ADataSet: TDataSet);
  339. var i: integer;
  340. begin
  341. with ADataSet do
  342. begin
  343. First;
  344. for i := 0 to testValuesCount-1 do
  345. begin
  346. CheckEquals(i, FieldByName('ID').AsInteger, 'ID');
  347. CheckEquals(testStringValues[i], FieldByName('FSTRING').AsString, 'FSTRING');
  348. CheckEquals(testIntValues[i], FieldByName('FINTEGER').AsInteger, 'FINTEGER');
  349. CheckEquals(testLargeIntValues[i], FieldByName('FLARGEINT').AsLargeInt, 'FLARGEINT');
  350. Next;
  351. end;
  352. CheckTrue(Eof, 'Eof');
  353. end;
  354. end;
  355. procedure TDBBasicsTestCase.CheckNDatasetValues(ADataSet: TDataSet; n: integer);
  356. var i: integer;
  357. begin
  358. with ADataSet do
  359. begin
  360. First;
  361. for i := 1 to n do
  362. begin
  363. CheckEquals(i, FieldByName('ID').AsInteger, 'ID');
  364. CheckEquals('TestName' + inttostr(i), FieldByName('NAME').AsString, 'NAME');
  365. Next;
  366. end;
  367. CheckTrue(Eof, 'Eof');
  368. end;
  369. end;
  370. procedure ReadIniFile;
  371. var IniFile : TIniFile;
  372. begin
  373. IniFile := TIniFile.Create(getcurrentdir + PathDelim + 'database.ini');
  374. dbtype:='';
  375. if Paramcount>0 then
  376. dbtype := ParamStr(1);
  377. if (dbtype='') or not inifile.SectionExists(dbtype) then
  378. dbtype := IniFile.ReadString('Database','Type','');
  379. dbconnectorname := IniFile.ReadString(dbtype,'Connector','');
  380. dbname := IniFile.ReadString(dbtype,'Name','');
  381. dbuser := IniFile.ReadString(dbtype,'User','');
  382. dbhostname := IniFile.ReadString(dbtype,'Hostname','');
  383. dbpassword := IniFile.ReadString(dbtype,'Password','');
  384. dbconnectorparams := IniFile.ReadString(dbtype,'ConnectorParams','');
  385. dbquotechars := IniFile.ReadString(dbtype,'QuoteChars','"');
  386. IniFile.Free;
  387. end;
  388. procedure InitialiseDBConnector;
  389. const B: array[boolean] of char=('0','1'); // should be exported from some main db unit, as SQL true/false?
  390. var DBConnectorClass : TPersistentClass;
  391. i : integer;
  392. FormatSettings : TFormatSettings;
  393. begin
  394. if DBConnectorRefCount>0 then exit;
  395. FormatSettings.DecimalSeparator:='.';
  396. FormatSettings.ThousandSeparator:=#0;
  397. testValues[ftString] := testStringValues;
  398. testValues[ftFixedChar] := testStringValues;
  399. testValues[ftTime] := testTimeValues;
  400. testValues[ftDate] := testDateValues;
  401. testValues[ftBlob] := testStringValues;
  402. testValues[ftMemo] := testStringValues;
  403. testValues[ftWideString] := testStringValues;
  404. testValues[ftWideMemo] := testStringValues;
  405. testValues[ftFMTBcd] := testFmtBCDValues;
  406. for i := 0 to testValuesCount-1 do
  407. begin
  408. testValues[ftBoolean,i] := B[testBooleanValues[i]];
  409. testValues[ftFloat,i] := FloatToStr(testFloatValues[i],FormatSettings);
  410. testValues[ftSmallint,i] := IntToStr(testSmallIntValues[i]);
  411. testValues[ftInteger,i] := IntToStr(testIntValues[i]);
  412. testValues[ftWord,i] := IntToStr(testWordValues[i]);
  413. testValues[ftLargeint,i] := IntToStr(testLargeIntValues[i]);
  414. testValues[ftCurrency,i] := CurrToStr(testCurrencyValues[i],FormatSettings);
  415. testValues[ftBCD,i] := CurrToStr(testCurrencyValues[i],FormatSettings);
  416. // For date '0001-01-01' other time-part like '00:00:00' causes "Invalid variant type cast", because of < MinDateTime constant
  417. if (testDateValues[i]>'0001-01-01') and (testTimeValues[i]>='00:00:01') and (testTimeValues[i]<'24:00:00') then
  418. testValues[ftDateTime,i] := testDateValues[i] + ' ' + testTimeValues[i]
  419. else
  420. testValues[ftDateTime,i] := testDateValues[i];
  421. end;
  422. if dbconnectorname = '' then raise Exception.Create('There is no db connector specified');
  423. DBConnectorClass := GetClass('T'+dbconnectorname+'DBConnector');
  424. if assigned(DBConnectorClass) then
  425. DBConnector := TDBConnectorClass(DBConnectorClass).create
  426. else Raise Exception.Create('Unknown db connector specified: ' + 'T'+dbconnectorname+'DBConnector');
  427. inc(DBConnectorRefCount);
  428. end;
  429. procedure FreeDBConnector;
  430. begin
  431. dec(DBConnectorRefCount);
  432. if DBConnectorRefCount=0 then
  433. FreeAndNil(DBConnector);
  434. end;
  435. function DateTimeToTimeString(d: tdatetime): string;
  436. var
  437. millisecond: word;
  438. second : word;
  439. minute : word;
  440. hour : word;
  441. begin
  442. // Format the datetime in the format hh:nn:ss.zzz, where the hours can be bigger then 23.
  443. DecodeTime(d,hour,minute,second,millisecond);
  444. hour := hour + (trunc(d) * 24);
  445. result := Format('%.2d:%.2d:%.2d.%.3d',[hour,minute,second,millisecond]);
  446. end;
  447. function TimeStringToDateTime(d: String): TDateTime;
  448. var
  449. millisecond: word;
  450. second : word;
  451. minute : word;
  452. hour : word;
  453. days : word;
  454. begin
  455. // Convert the string in the format hh:nn:ss.zzz to a datetime.
  456. hour := strtoint(copy(d,1,2));
  457. minute := strtoint(copy(d,4,2));
  458. second := strtoint(copy(d,7,2));
  459. millisecond := strtoint(copy(d,10,3));
  460. days := hour div 24;
  461. hour := hour mod 24;
  462. result := ComposeDateTime(days,EncodeTime(hour,minute,second,millisecond));
  463. end;
  464. function StringToByteArray(s: ansistring): Variant;
  465. var P: Pointer;
  466. Len: integer;
  467. begin
  468. Len := Length(s) * SizeOf(AnsiChar);
  469. Result := VarArrayCreate([0, Len-1], varByte);
  470. P := VarArrayLock(Result);
  471. try
  472. Move(s[1], P^, Len);
  473. finally
  474. VarArrayUnlock(Result);
  475. end;
  476. end;
  477. initialization
  478. ReadIniFile;
  479. DBConnectorRefCount:=0;
  480. end.