testspecifictdbf.pas 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. unit testspecifictdbf;
  2. {
  3. Unit tests which are specific to the tdbf dbase/foxpro units.
  4. }
  5. {$IFDEF FPC}
  6. {$mode Delphi}{$H+}
  7. {$ENDIF}
  8. interface
  9. uses
  10. {$IFDEF FPC}
  11. fpcunit, testutils, testregistry, testdecorator,
  12. {$ELSE FPC}
  13. TestFramework,
  14. {$ENDIF FPC}
  15. Classes, SysUtils,
  16. ToolsUnit, dbf;
  17. type
  18. { TTestSpecificTDBF }
  19. TTestSpecificTDBF = class(TTestCase)
  20. private
  21. procedure WriteReadbackTest(ADBFDataset: TDbf; AutoInc: boolean = false);
  22. protected
  23. procedure SetUp; override;
  24. procedure TearDown; override;
  25. published
  26. // Verifies that requested tablelevel is delivered:
  27. procedure TestTableLevel;
  28. // Verifies that writing to memory and writing to disk results in the same data
  29. procedure TestMemoryDBFEqualsDiskDBF;
  30. // Create fields using indexdefs:
  31. procedure TestCreateDatasetFromFielddefs;
  32. // Specifying fields from field objects
  33. procedure TestCreateDatasetFromFields;
  34. // Tries to open a dbf that has not been activated, which should fail:
  35. procedure TestOpenNonExistingDataset_Fails;
  36. // Tests creating a new database with calculated/lookup fields
  37. procedure TestCreationDatasetWithCalcFields;
  38. procedure TestAutoIncField;
  39. // Tests findfirst moves to first record
  40. procedure TestFindFirst;
  41. // Tests findlast moves to last record
  42. procedure TestFindLast;
  43. // Tests findnext moves to next record
  44. procedure TestFindNext;
  45. // Tests findprior
  46. procedure TestFindPrior;
  47. // Tests writing and reading a memo field
  48. procedure TestMemo;
  49. // Tests string field with 254 characters (max for DBase IV)
  50. procedure TestLargeString;
  51. end;
  52. implementation
  53. uses
  54. variants,
  55. FmtBCD,
  56. db, dbf_common, DBFToolsUnit;
  57. { TTestSpecificTDBF }
  58. procedure TTestSpecificTDBF.WriteReadbackTest(ADBFDataset: TDbf;
  59. AutoInc: boolean);
  60. var
  61. i : integer;
  62. begin
  63. // Add sample data
  64. for i := 1 to 10 do
  65. begin
  66. ADBFDataset.Append;
  67. if not AutoInc then
  68. ADBFDataset.FieldByName('ID').AsInteger := i;
  69. ADBFDataset.FieldByName('NAME').AsString := 'TestName' + inttostr(i);
  70. ADBFDataset.Post;
  71. end;
  72. ADBFDataset.first;
  73. for i := 1 to 10 do
  74. begin
  75. CheckEquals(i,ADBFDataset.fieldbyname('ID').asinteger);
  76. CheckEquals('TestName' + inttostr(i),ADBFDataset.fieldbyname('NAME').AsString);
  77. ADBFDataset.next;
  78. end;
  79. CheckTrue(ADBFDataset.EOF);
  80. end;
  81. procedure TTestSpecificTDBF.SetUp;
  82. begin
  83. DBConnector.StartTest;
  84. end;
  85. procedure TTestSpecificTDBF.TearDown;
  86. begin
  87. DBConnector.StopTest;
  88. end;
  89. procedure TTestSpecificTDBF.TestTableLevel;
  90. var
  91. ds : TDBF;
  92. begin
  93. if ((DS as TDBFAutoClean).UserRequestedTableLevel=25) then
  94. ignore('Foxpro (tablelevel 25) may write data out in dBase IV (tablelevel 4) format.');
  95. ds := TDBFAutoClean.Create(nil);
  96. DS.FieldDefs.Add('ID',ftInteger);
  97. DS.CreateTable;
  98. DS.Open;
  99. CheckEquals((DS as TDBFAutoClean).UserRequestedTableLevel,DS.TableLevel,'User specified tablelevel should match dbf tablelevel.');
  100. DS.Close;
  101. ds.free;
  102. end;
  103. procedure TTestSpecificTDBF.TestMemoryDBFEqualsDiskDBF;
  104. var
  105. dsfile: TDBF;
  106. dsmem: TDBF;
  107. backingstream: TMemoryStream;
  108. FileName: string;
  109. i: integer;
  110. thefile: TMemoryStream;
  111. begin
  112. backingstream:=TMemoryStream.Create;
  113. thefile:=TMemoryStream.Create;
  114. dsmem:=TDBF.Create(nil);
  115. dsfile:=TDBF.Create(nil);
  116. FileName:=GetTempFileName;
  117. dsfile.FilePathFull:=ExtractFilePath(FileName);
  118. dsfile.TableName:=ExtractFileName(FileName);
  119. dsmem.TableName:=ExtractFileName(FileName);
  120. dsmem.Storage:=stoMemory;
  121. dsmem.UserStream:=backingstream;
  122. // A small number of fields but should be enough
  123. dsfile.FieldDefs.Add('ID',ftInteger);
  124. dsmem.FieldDefs.Add('ID',ftInteger);
  125. dsfile.FieldDefs.Add('NAME',ftString,50);
  126. dsmem.FieldDefs.Add('NAME',ftString,50);
  127. dsfile.CreateTable;
  128. dsmem.CreateTable;
  129. dsfile.Open;
  130. dsmem.Open;
  131. // Some sample data
  132. for i := 1 to 101 do
  133. begin
  134. dsfile.Append;
  135. dsmem.Append;
  136. dsfile.FieldByName('ID').AsInteger := i;
  137. dsmem.FieldByName('ID').AsInteger := i;
  138. dsfile.FieldByName('NAME').AsString := 'TestName' + inttostr(i);
  139. dsmem.FieldByName('NAME').AsString := 'TestName' + inttostr(i);
  140. dsfile.Post;
  141. dsmem.Post;
  142. end;
  143. // By closing, we update the number of records in the header
  144. dsfile.close;
  145. dsmem.close;
  146. dsfile.free;
  147. // Keep dsmem; load file into stream:
  148. thefile.LoadfromFile(FileName);
  149. deletefile(FileName);
  150. CheckEquals(backingstream.size,thefile.size,'Memory backed dbf should have same size as file-backed dbf');
  151. // Now compare stream contents - thereby comparing the file with backingstream
  152. CheckEquals(true,comparemem(thefile.Memory,backingstream.Memory,thefile.size),'Memory backed dbf data should be the same as file-backed dbf');
  153. backingstream.free;
  154. thefile.free;
  155. end;
  156. procedure TTestSpecificTDBF.TestCreateDatasetFromFielddefs;
  157. var
  158. ds : TDBF;
  159. begin
  160. ds := TDBFAutoClean.Create(nil);
  161. DS.FieldDefs.Add('ID',ftInteger);
  162. DS.FieldDefs.Add('NAME',ftString,50);
  163. DS.CreateTable;
  164. DS.Open;
  165. WriteReadbackTest(ds);
  166. DS.Close;
  167. ds.free;
  168. end;
  169. procedure TTestSpecificTDBF.TestCreateDatasetFromFields;
  170. var
  171. ds : TDBF;
  172. f: TField;
  173. begin
  174. ds := TDBFAutoClean.Create(nil);
  175. DS.CreateTable;
  176. F := TIntegerField.Create(ds);
  177. F.FieldName:='ID';
  178. F.DataSet:=ds;
  179. F := TStringField.Create(ds);
  180. F.FieldName:='NAME';
  181. F.DataSet:=ds;
  182. F.Size:=50;
  183. DS.Open;
  184. ds.free;
  185. end;
  186. procedure TTestSpecificTDBF.TestOpenNonExistingDataset_Fails;
  187. var
  188. ds : TDBF;
  189. f: TField;
  190. begin
  191. ds := TDBFAutoClean.Create(nil);
  192. F := TIntegerField.Create(ds);
  193. F.FieldName:='ID';
  194. F.DataSet:=ds;
  195. CheckException(ds.Open,EDbfError);
  196. ds.Free;
  197. ds := TDBFAutoClean.Create(nil);
  198. DS.FieldDefs.Add('ID',ftInteger);
  199. CheckException(ds.Open,EDbfError);
  200. ds.Free;
  201. end;
  202. procedure TTestSpecificTDBF.TestCreationDatasetWithCalcFields;
  203. var
  204. ds : TDBF;
  205. f: TField;
  206. i: integer;
  207. begin
  208. //todo: find out which tablelevels support calculated/lookup fields
  209. ds := TDBFAutoClean.Create(nil);
  210. try
  211. ds.FieldDefs.Add('ID',ftInteger);
  212. ds.FieldDefs.Add('NAME',ftString,50);
  213. ds.CreateTable;
  214. for i:=0 to ds.FieldDefs.Count-1 do
  215. begin
  216. ds.FieldDefs[i].CreateField(ds); // make fields persistent
  217. end;
  218. F := TStringField.Create(ds);
  219. F.FieldKind:=fkCalculated;
  220. F.FieldName:='NAME_CALC';
  221. F.DataSet:=ds;
  222. F.Size:=50;
  223. F.ProviderFlags:=[];
  224. F := TStringField.Create(ds);
  225. F.FieldKind:=fkLookup;
  226. F.FieldName:='NAME_LKP';
  227. F.LookupDataSet:=DBConnector.GetNDataset(5);
  228. F.KeyFields:='ID';
  229. F.LookupKeyFields:='ID';
  230. F.LookupResultField:='NAME';
  231. F.DataSet:=ds;
  232. F.Size:=50;
  233. DS.Open;
  234. WriteReadbackTest(ds);
  235. for i := 0 to ds.FieldDefs.Count-1 do
  236. begin
  237. CheckNotEquals(ds.FieldDefs[i].Name,'NAME_CALC');
  238. CheckNotEquals(ds.FieldDefs[i].Name,'NAME_LKP');
  239. end;
  240. DS.Close;
  241. finally
  242. ds.Free;
  243. end;
  244. end;
  245. procedure TTestSpecificTDBF.TestAutoIncField;
  246. var
  247. ds : TDbf;
  248. f: TField;
  249. begin
  250. ds := TDbfAutoClean.Create(nil);
  251. if ds.TableLevel<7 then
  252. begin
  253. Ignore('Autoinc fields are only supported in tablelevel 7 and higher');
  254. end;
  255. F := TAutoIncField.Create(ds);
  256. F.FieldName:='ID';
  257. F.DataSet:=ds;
  258. F := TStringField.Create(ds);
  259. F.FieldName:='NAME';
  260. F.DataSet:=ds;
  261. F.Size:=50;
  262. DS.CreateTable;
  263. DS.Open;
  264. WriteReadbackTest(ds,True);
  265. DS.Close;
  266. ds.Free;
  267. end;
  268. procedure TTestSpecificTDBF.TestFindFirst;
  269. const
  270. NumRecs=8;
  271. var
  272. DS: TDataSet;
  273. begin
  274. DS:=DBConnector.GetNDataset(NumRecs);
  275. DS.Open;
  276. DS.Last;
  277. CheckEquals(true,DS.FindFirst,'Findfirst should return true');
  278. CheckEquals(1,DS.fieldbyname('ID').asinteger);
  279. end;
  280. procedure TTestSpecificTDBF.TestFindLast;
  281. const
  282. NumRecs=8;
  283. var
  284. DS: TDataSet;
  285. begin
  286. DS:=DBConnector.GetNDataset(NumRecs);
  287. DS.Open;
  288. DS.First;
  289. CheckEquals(true,DS.FindLast,'Findlast should return true');
  290. CheckEquals(NumRecs,DS.fieldbyname('ID').asinteger);
  291. end;
  292. procedure TTestSpecificTDBF.TestFindNext;
  293. const
  294. NumRecs=8;
  295. var
  296. DS: TDataSet;
  297. begin
  298. DS:=DBConnector.GetNDataset(NumRecs);
  299. DS.Open;
  300. DS.First;
  301. CheckEquals(true,DS.FindNext,'FindNext should return true');
  302. CheckEquals(2,DS.fieldbyname('ID').asinteger);
  303. end;
  304. procedure TTestSpecificTDBF.TestFindPrior;
  305. const
  306. NumRecs=8;
  307. var
  308. DS: TDataSet;
  309. begin
  310. DS:=DBConnector.GetNDataset(NumRecs);
  311. DS.Open;
  312. DS.Last;
  313. CheckEquals(true,DS.FindPrior,'FindPrior should return true');
  314. CheckEquals(NumRecs-1,DS.fieldbyname('ID').asinteger);
  315. end;
  316. procedure TTestSpecificTDBF.TestMemo;
  317. var
  318. ds : TDBF;
  319. begin
  320. ds := TDBFAutoClean.Create(nil);
  321. DS.FieldDefs.Add('ID',ftInteger);
  322. DS.FieldDefs.Add('NAME',ftMemo);
  323. DS.CreateTable;
  324. DS.Open;
  325. WriteReadbackTest(ds);
  326. DS.Close;
  327. ds.free;
  328. end;
  329. procedure TTestSpecificTDBF.TestLargeString;
  330. var
  331. ds : TDBF;
  332. MaxStringSize: integer;
  333. TestValue: string;
  334. begin
  335. ds := TDBFAutoClean.Create(nil);
  336. if (ds.TableLevel>=25) then
  337. // (Visual) FoxPro supports 32K
  338. MaxStringSize:=32767
  339. else
  340. // Dbase III..V,7
  341. MaxStringSize:=254;
  342. TestValue:=StringOfChar('a',MaxStringSize);
  343. DS.FieldDefs.Add('ID',ftInteger);
  344. DS.FieldDefs.Add('NAME',ftString,254);
  345. DS.CreateTable;
  346. DS.Open;
  347. // Write & readback test
  348. DS.Append;
  349. DS.FieldByName('ID').AsInteger := 1;
  350. DS.FieldByName('NAME').AsString := TestValue;
  351. DS.Post;
  352. DS.first;
  353. CheckEquals(1,DS.fieldbyname('ID').asinteger,'ID field must match record number');
  354. // If test fails, let's count the number of "a"s instead so we can report that instead of printing out the entire string
  355. CheckEquals(length(TestValue),length(DS.fieldbyname('NAME').AsString),'NAME field length must match test value length');
  356. CheckEquals(TestValue,DS.fieldbyname('NAME').AsString,'NAME field must match test value');
  357. DS.next;
  358. CheckTrue(DS.EOF,'Dataset EOF must be true');
  359. DS.Close;
  360. ds.free;
  361. end;
  362. initialization
  363. {$ifdef fpc}
  364. if uppercase(dbconnectorname)='DBF' then
  365. begin
  366. RegisterTestDecorator(TDBBasicsTestSetup, TTestSpecificTDBF);
  367. end;
  368. {$endif fpc}
  369. end.