testspecifictdbf.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  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, testregistry,
  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. // Writes data to dataset and verifies readback is correct
  22. procedure WriteReadbackTest(ADBFDataset: TDbf; AutoInc: boolean = false);
  23. protected
  24. procedure SetUp; override;
  25. procedure TearDown; override;
  26. published
  27. // Verifies that requested tablelevel is delivered:
  28. procedure TestTableLevel;
  29. // Verifies that writing to memory and writing to disk results in the same data
  30. procedure TestMemoryDBFEqualsDiskDBF;
  31. // Create fields using indexdefs:
  32. procedure TestCreateDatasetFromFielddefs;
  33. // Specifying fields from field objects
  34. procedure TestCreateDatasetFromFields;
  35. // Tries to open a dbf that has not been activated, which should fail:
  36. procedure TestOpenNonExistingDataset_Fails;
  37. // Tests creating a new database with calculated/lookup fields
  38. procedure TestCreationDatasetWithCalcFields;
  39. // Tests autoincrement field (only in tablelevels that support it)
  40. procedure TestAutoIncField;
  41. // Tests findfirst moves to first record
  42. procedure TestFindFirst;
  43. // Tests findlast moves to last record
  44. procedure TestFindLast;
  45. // Tests findnext moves to next record
  46. procedure TestFindNext;
  47. // Tests findprior
  48. procedure TestFindPrior;
  49. // Tests writing and reading a memo field
  50. procedure TestMemo;
  51. // Tests like TestMemo, but closes and reopens in memory file
  52. // in between. Data should still be there.
  53. procedure TestMemoClose;
  54. // Same as TestMemoClose except added index stream
  55. procedure TestIndexClose;
  56. // Tests string field with
  57. // 254 characters (max for DBase IV)
  58. // 32767 characters (FoxPro, Visual FoxPro)
  59. procedure TestLargeString;
  60. // Tests codepage in created dbf equals requested codepage
  61. procedure TestCodePage;
  62. end;
  63. implementation
  64. uses
  65. variants,
  66. FmtBCD,
  67. db, dbf_common, DBFToolsUnit;
  68. { TTestSpecificTDBF }
  69. procedure TTestSpecificTDBF.WriteReadbackTest(ADBFDataset: TDbf;
  70. AutoInc: boolean);
  71. const
  72. MaxRecs = 10;
  73. var
  74. i : integer;
  75. begin
  76. // Add sample data
  77. for i := 1 to MaxRecs do
  78. begin
  79. ADBFDataset.Append;
  80. if not AutoInc then
  81. ADBFDataset.FieldByName('ID').AsInteger := i;
  82. ADBFDataset.FieldByName('NAME').AsString := 'TestName' + inttostr(i);
  83. ADBFDataset.Post;
  84. end;
  85. // Verify sample data is correct
  86. ADBFDataset.first;
  87. for i := 1 to MaxRecs do
  88. begin
  89. CheckEquals(i,ADBFDataset.fieldbyname('ID').asinteger);
  90. CheckEquals('TestName' + inttostr(i),ADBFDataset.fieldbyname('NAME').AsString);
  91. ADBFDataset.next;
  92. end;
  93. CheckTrue(ADBFDataset.EOF,'After reading all records the dataset should show EOF');
  94. end;
  95. procedure TTestSpecificTDBF.SetUp;
  96. begin
  97. DBConnector.StartTest(TestName);
  98. end;
  99. procedure TTestSpecificTDBF.TearDown;
  100. begin
  101. DBConnector.StopTest(TestName);
  102. end;
  103. procedure TTestSpecificTDBF.TestTableLevel;
  104. var
  105. ds : TDBF;
  106. begin
  107. ds := TDBFAutoClean.Create(nil);
  108. if ((DS as TDBFAutoClean).UserRequestedTableLevel=25) then
  109. ignore('Foxpro (tablelevel 25) may write data out in dBase IV (tablelevel 4) format.');
  110. DS.FieldDefs.Add('ID',ftInteger);
  111. DS.CreateTable;
  112. DS.Open;
  113. CheckEquals((DS as TDBFAutoClean).UserRequestedTableLevel,DS.TableLevel,'User specified tablelevel should match dbf tablelevel.');
  114. DS.Close;
  115. ds.free;
  116. end;
  117. procedure TTestSpecificTDBF.TestMemoryDBFEqualsDiskDBF;
  118. var
  119. dsfile: TDBF;
  120. dsmem: TDBF;
  121. backingstream: TMemoryStream;
  122. FileName: string;
  123. i: integer;
  124. thefile: TMemoryStream;
  125. begin
  126. backingstream:=TMemoryStream.Create;
  127. thefile:=TMemoryStream.Create;
  128. dsmem:=TDBF.Create(nil);
  129. dsfile:=TDBF.Create(nil);
  130. FileName:=GetTempFileName;
  131. dsfile.FilePathFull:=ExtractFilePath(FileName);
  132. dsfile.TableName:=ExtractFileName(FileName);
  133. dsmem.TableName:=ExtractFileName(FileName);
  134. dsmem.Storage:=stoMemory;
  135. dsmem.UserStream:=backingstream;
  136. // A small number of fields but should be enough
  137. dsfile.FieldDefs.Add('ID',ftInteger);
  138. dsmem.FieldDefs.Add('ID',ftInteger);
  139. dsfile.FieldDefs.Add('NAME',ftString,50);
  140. dsmem.FieldDefs.Add('NAME',ftString,50);
  141. dsfile.CreateTable;
  142. dsmem.CreateTable;
  143. dsfile.Open;
  144. dsmem.Open;
  145. // Some sample data
  146. for i := 1 to 101 do
  147. begin
  148. dsfile.Append;
  149. dsmem.Append;
  150. dsfile.FieldByName('ID').AsInteger := i;
  151. dsmem.FieldByName('ID').AsInteger := i;
  152. dsfile.FieldByName('NAME').AsString := 'TestName' + inttostr(i);
  153. dsmem.FieldByName('NAME').AsString := 'TestName' + inttostr(i);
  154. dsfile.Post;
  155. dsmem.Post;
  156. end;
  157. // By closing, we update the number of records in the header
  158. dsfile.close;
  159. dsmem.close;
  160. dsfile.free;
  161. // Keep dsmem; load file into stream:
  162. thefile.LoadfromFile(FileName);
  163. deletefile(FileName);
  164. CheckEquals(backingstream.size,thefile.size,'Memory backed dbf should have same size as file-backed dbf');
  165. // Now compare stream contents - thereby comparing the file with backingstream
  166. CheckEquals(true,comparemem(thefile.Memory,backingstream.Memory,thefile.size),'Memory backed dbf data should be the same as file-backed dbf');
  167. backingstream.free;
  168. thefile.free;
  169. end;
  170. procedure TTestSpecificTDBF.TestCreateDatasetFromFielddefs;
  171. var
  172. ds : TDBF;
  173. begin
  174. ds := TDBFAutoClean.Create(nil);
  175. DS.FieldDefs.Add('ID',ftInteger);
  176. DS.FieldDefs.Add('NAME',ftString,50);
  177. DS.CreateTable;
  178. DS.Open;
  179. WriteReadbackTest(ds);
  180. DS.Close;
  181. ds.free;
  182. end;
  183. procedure TTestSpecificTDBF.TestCreateDatasetFromFields;
  184. var
  185. ds : TDBF;
  186. f: TField;
  187. begin
  188. ds := TDBFAutoClean.Create(nil);
  189. DS.CreateTable;
  190. F := TIntegerField.Create(ds);
  191. F.FieldName:='ID';
  192. F.DataSet:=ds;
  193. F := TStringField.Create(ds);
  194. F.FieldName:='NAME';
  195. F.DataSet:=ds;
  196. F.Size:=50;
  197. DS.Open;
  198. ds.free;
  199. end;
  200. procedure TTestSpecificTDBF.TestOpenNonExistingDataset_Fails;
  201. var
  202. ds : TDBF;
  203. f: TField;
  204. begin
  205. ds := TDBFAutoClean.Create(nil);
  206. F := TIntegerField.Create(ds);
  207. F.FieldName:='ID';
  208. F.DataSet:=ds;
  209. CheckException(ds.Open,EDbfError);
  210. ds.Free;
  211. ds := TDBFAutoClean.Create(nil);
  212. DS.FieldDefs.Add('ID',ftInteger);
  213. CheckException(ds.Open,EDbfError);
  214. ds.Free;
  215. end;
  216. procedure TTestSpecificTDBF.TestCreationDatasetWithCalcFields;
  217. var
  218. ds : TDBF;
  219. f: TField;
  220. i: integer;
  221. begin
  222. //todo: find out which tablelevels support calculated/lookup fields
  223. ds := TDBFAutoClean.Create(nil);
  224. try
  225. ds.FieldDefs.Add('ID',ftInteger);
  226. ds.FieldDefs.Add('NAME',ftString,50);
  227. ds.CreateTable;
  228. for i:=0 to ds.FieldDefs.Count-1 do
  229. begin
  230. ds.FieldDefs[i].CreateField(ds); // make fields persistent
  231. end;
  232. F := TStringField.Create(ds);
  233. F.FieldKind:=fkCalculated;
  234. F.FieldName:='NAME_CALC';
  235. F.DataSet:=ds;
  236. F.Size:=50;
  237. F.ProviderFlags:=[];
  238. F := TStringField.Create(ds);
  239. F.FieldKind:=fkLookup;
  240. F.FieldName:='NAME_LKP';
  241. F.LookupDataSet:=DBConnector.GetNDataset(5);
  242. F.KeyFields:='ID';
  243. F.LookupKeyFields:='ID';
  244. F.LookupResultField:='NAME';
  245. F.DataSet:=ds;
  246. F.Size:=50;
  247. DS.Open;
  248. WriteReadbackTest(ds);
  249. for i := 0 to ds.FieldDefs.Count-1 do
  250. begin
  251. CheckNotEquals(ds.FieldDefs[i].Name,'NAME_CALC');
  252. CheckNotEquals(ds.FieldDefs[i].Name,'NAME_LKP');
  253. end;
  254. DS.Close;
  255. finally
  256. ds.Free;
  257. end;
  258. end;
  259. procedure TTestSpecificTDBF.TestAutoIncField;
  260. var
  261. ds : TDbf;
  262. f: TField;
  263. begin
  264. ds := TDbfAutoClean.Create(nil);
  265. if ds.TableLevel<7 then
  266. begin
  267. Ignore('Autoinc fields are only supported in tablelevel 7 and higher');
  268. end;
  269. F := TAutoIncField.Create(ds);
  270. F.FieldName:='ID';
  271. F.DataSet:=ds;
  272. F := TStringField.Create(ds);
  273. F.FieldName:='NAME';
  274. F.DataSet:=ds;
  275. F.Size:=50;
  276. DS.CreateTable;
  277. DS.Open;
  278. WriteReadbackTest(ds,True);
  279. DS.Close;
  280. ds.Free;
  281. end;
  282. procedure TTestSpecificTDBF.TestFindFirst;
  283. const
  284. NumRecs=8;
  285. var
  286. DS: TDataSet;
  287. begin
  288. DS:=DBConnector.GetNDataset(NumRecs);
  289. DS.Open;
  290. DS.Last;
  291. CheckEquals(true,DS.FindFirst,'Findfirst should return true');
  292. CheckEquals(1,DS.fieldbyname('ID').asinteger);
  293. end;
  294. procedure TTestSpecificTDBF.TestFindLast;
  295. const
  296. NumRecs=8;
  297. var
  298. DS: TDataSet;
  299. begin
  300. DS:=DBConnector.GetNDataset(NumRecs);
  301. DS.Open;
  302. DS.First;
  303. CheckEquals(true,DS.FindLast,'Findlast should return true');
  304. CheckEquals(NumRecs,DS.fieldbyname('ID').asinteger);
  305. end;
  306. procedure TTestSpecificTDBF.TestFindNext;
  307. const
  308. NumRecs=8;
  309. var
  310. DS: TDataSet;
  311. begin
  312. DS:=DBConnector.GetNDataset(NumRecs);
  313. DS.Open;
  314. DS.First;
  315. CheckEquals(true,DS.FindNext,'FindNext should return true');
  316. CheckEquals(2,DS.fieldbyname('ID').asinteger);
  317. end;
  318. procedure TTestSpecificTDBF.TestFindPrior;
  319. const
  320. NumRecs=8;
  321. var
  322. DS: TDataSet;
  323. begin
  324. DS:=DBConnector.GetNDataset(NumRecs);
  325. DS.Open;
  326. DS.Last;
  327. CheckEquals(true,DS.FindPrior,'FindPrior should return true');
  328. CheckEquals(NumRecs-1,DS.fieldbyname('ID').asinteger);
  329. end;
  330. procedure TTestSpecificTDBF.TestMemo;
  331. var
  332. ds : TDBF;
  333. begin
  334. ds := TDBFAutoClean.Create(nil);
  335. DS.FieldDefs.Add('ID',ftInteger);
  336. DS.FieldDefs.Add('NAME',ftMemo);
  337. DS.OpenMode:=omAutoCreate; //let dbf code create memo etc files when needed
  338. DS.CreateTable;
  339. DS.Open;
  340. WriteReadbackTest(ds);
  341. DS.Close;
  342. ds.free;
  343. end;
  344. procedure TTestSpecificTDBF.TestMemoClose;
  345. const
  346. MaxRecs = 10;
  347. var
  348. ds : TDBF;
  349. i: integer;
  350. DBFStream: TMemoryStream;
  351. MemoStream: TMemoryStream;
  352. begin
  353. ds := TDBF.Create(nil);
  354. DBFStream:=TMemoryStream.Create;
  355. MemoStream:=TMemoryStream.Create;
  356. DS.Storage:=stoMemory;
  357. DS.UserStream:=DBFStream;
  358. DS.UserMemoStream:=MemoStream;
  359. DS.FieldDefs.Add('ID',ftInteger);
  360. DS.FieldDefs.Add('NAME',ftMemo);
  361. DS.OpenMode:=omAutoCreate; //let dbf code create memo etc files when needed
  362. DS.CreateTable;
  363. DS.Open;
  364. for i := 1 to MaxRecs do
  365. begin
  366. DS.Append;
  367. DS.FieldByName('ID').AsInteger := i;
  368. DS.FieldByName('NAME').AsString := 'TestName' + inttostr(i);
  369. DS.Post;
  370. end;
  371. DS.Close; //in old implementations, this erased memo memory
  372. DS.Open;
  373. DS.First;
  374. for i := 1 to MaxRecs do
  375. begin
  376. CheckEquals(i,DS.fieldbyname('ID').asinteger);
  377. CheckEquals('TestName' + inttostr(i),DS.fieldbyname('NAME').AsString);
  378. DS.next;
  379. end;
  380. CheckTrue(DS.EOF,'After reading all records the dataset should show EOF');
  381. DS.Close;
  382. ds.free;
  383. DBFStream.Free;
  384. MemoStream.Free;
  385. end;
  386. procedure TTestSpecificTDBF.TestIndexClose;
  387. const
  388. MaxRecs = 10;
  389. var
  390. ds : TDBF;
  391. i: integer;
  392. DBFStream: TMemoryStream;
  393. IndexStream: TMemoryStream;
  394. MemoStream: TMemoryStream;
  395. begin
  396. ds := TDBF.Create(nil);
  397. DBFStream:=TMemoryStream.Create;
  398. IndexStream:=TMemoryStream.Create;
  399. MemoStream:=TMemoryStream.Create;
  400. DS.Storage:=stoMemory;
  401. DS.UserStream:=DBFStream;
  402. DS.UserIndexStream:=IndexStream;
  403. DS.UserMemoStream:=MemoStream;
  404. DS.FieldDefs.Add('ID',ftInteger);
  405. DS.FieldDefs.Add('NAME',ftMemo);
  406. DS.OpenMode:=omAutoCreate; //let dbf code create memo etc files when needed
  407. DS.CreateTable;
  408. DS.Exclusive:=true;//needed for index
  409. DS.Open;
  410. DS.AddIndex('idxID','ID', [ixPrimary, ixUnique]);
  411. DS.Close;
  412. DS.Exclusive:=false;
  413. DS.Open;
  414. for i := 1 to MaxRecs do
  415. begin
  416. DS.Append;
  417. DS.FieldByName('ID').AsInteger := i;
  418. DS.FieldByName('NAME').AsString := 'TestName' + inttostr(i);
  419. DS.Post;
  420. end;
  421. DS.Close; //in old implementations, this erased memo memory
  422. // Check streams have content
  423. CheckNotEquals(0,DBFStream.Size,'DBF stream should have content');
  424. CheckNotEquals(0,IndexStream.Size,'Index stream should have content');
  425. CheckNotEquals(0,MemoStream.Size,'Memo stream should have content');
  426. DS.Open;
  427. DS.First;
  428. for i := 1 to MaxRecs do
  429. begin
  430. CheckEquals(i,DS.fieldbyname('ID').asinteger);
  431. CheckEquals('TestName' + inttostr(i),DS.fieldbyname('NAME').AsString);
  432. DS.next;
  433. end;
  434. CheckTrue(DS.EOF,'After reading all records the dataset should show EOF');
  435. DS.Close;
  436. ds.free;
  437. DBFStream.Free;
  438. IndexStream.Free;
  439. MemoStream.Free;
  440. end;
  441. procedure TTestSpecificTDBF.TestLargeString;
  442. var
  443. ds : TDBF;
  444. MaxStringSize: integer;
  445. TestValue: string;
  446. begin
  447. ds := TDBFAutoClean.Create(nil);
  448. if (ds.TableLevel>=25) then
  449. // (Visual) FoxPro supports 32K
  450. MaxStringSize:=32767
  451. else
  452. // Dbase III..V,7
  453. MaxStringSize:=254;
  454. TestValue:=StringOfChar('a',MaxStringSize);
  455. DS.FieldDefs.Add('ID',ftInteger);
  456. DS.FieldDefs.Add('NAME',ftString,MaxStringSize);
  457. DS.CreateTable;
  458. DS.Open;
  459. // Write & readback test
  460. DS.Append;
  461. DS.FieldByName('ID').AsInteger := 1;
  462. DS.FieldByName('NAME').AsString := TestValue;
  463. DS.Post;
  464. DS.first;
  465. CheckEquals(1,DS.fieldbyname('ID').asinteger,'ID field must match record number');
  466. // If test fails, let's count the number of "a"s instead so we can report that instead of printing out the entire string
  467. CheckEquals(length(TestValue),length(DS.fieldbyname('NAME').AsString),'NAME field length must match test value length');
  468. CheckEquals(TestValue,DS.fieldbyname('NAME').AsString,'NAME field must match test value');
  469. DS.next;
  470. CheckTrue(DS.EOF,'Dataset EOF must be true');
  471. DS.Close;
  472. ds.free;
  473. end;
  474. procedure TTestSpecificTDBF.TestCodePage;
  475. const
  476. // Chose non-default (i.e. 437,850,1252) cps
  477. DOSCodePage=865; //Nordic ms dos
  478. DOSLanguageID=$66; //... corresponding language ID (according to VFP docs; other sources say $65)
  479. WindowsCodePage=1251; //Russian windows
  480. WindowsLanguageID=$C9; //.... corresponding language ID
  481. var
  482. RequestLanguageID: integer; //dbf language ID marker (byte 29)
  483. CorrespondingCodePage: integer;
  484. ds : TDBF;
  485. begin
  486. ds := TDBFAutoClean.Create(nil);
  487. if ((DS as TDBFAutoClean).UserRequestedTableLevel=25) then
  488. ignore('Foxpro (tablelevel 25) may write data out in dBase IV (tablelevel 4) format.');
  489. DS.FieldDefs.Add('ID',ftInteger);
  490. if ((DS as TDBFAutoClean).UserRequestedTableLevel in [7,30]) then
  491. begin
  492. RequestLanguageID:=WindowsLanguageID;
  493. CorrespondingCodePage:=WindowsCodePage //Visual FoxPro, DBase7
  494. end
  495. else
  496. begin
  497. RequestLanguageID:=DOSLanguageID;
  498. CorrespondingCodePage:=DOSCodePage;
  499. end;
  500. (DS as TDBFAutoClean).LanguageID:=RequestLanguageID;
  501. DS.CreateTable;
  502. DS.Open;
  503. CheckEquals(CorrespondingCodePage,DS.CodePage,'DBF codepage should match requested codeapage.');
  504. DS.Close;
  505. ds.free;
  506. end;
  507. initialization
  508. {$ifdef fpc}
  509. if uppercase(dbconnectorname)='DBF' then
  510. begin
  511. RegisterTestDecorator(TDBBasicsTestSetup, TTestSpecificTDBF);
  512. end;
  513. {$endif fpc}
  514. end.