toolsunit.pas 13 KB

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