testspecifictdbf.pas 14 KB

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