toolsunit.pas 15 KB

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