unit TestDatasources; {$IFDEF FPC} {$mode Delphi}{$H+} {$ENDIF} interface uses Classes, SysUtils, fpcunit, db; type { TTestDatasources } TTestDatasources = class(TTestCase) private procedure FieldNotifyEvent(Sender: TField); procedure DatasetNotifyEvent(Dataset: TDataset); protected procedure SetUp; override; procedure TearDown; override; published // This test is also in TestDBBasics // procedure TestDataEventsResync; procedure TestDataEvent1; procedure TestDataEvent2; procedure TestDataEvent3; procedure TestDataEvent4; procedure TestDataEvent5; procedure TestDataEvent6; procedure TestDataEvent7; procedure TestCalcFirstRecord1; procedure TestRefreshLookupList; procedure TestCalculateFields; procedure TestCalcLookupValue; procedure TestEnableControls; end; implementation uses ToolsUnit, dbf, testregistry, variants{$IFDEF UNIX},cwstring {$ENDIF}; type THackDataset=class(TDataset); THackDataLink=class(TDatalink); { TTestDataSources } procedure TTestDatasources.FieldNotifyEvent(Sender: TField); begin DataEvents := DataEvents + 'FieldNotifyEvent' + ';'; end; procedure TTestDatasources.DatasetNotifyEvent(Dataset: TDataset); begin DataEvents := DataEvents + 'DatasetNotifyEvent' + ';'; end; procedure TTestDatasources.SetUp; begin DBConnector.StartTest(TestName); end; procedure TTestDatasources.TearDown; begin DBConnector.StopTest(TestName); end; {procedure TTestDatasources.TestDataEventsResync; var i,count : integer; aDatasource : TDataSource; aDatalink : TDataLink; ds : tdataset; begin aDatasource := TDataSource.Create(nil); aDatalink := TTestDataLink.Create; aDatalink.DataSource := aDatasource; ds := DBConnector.GetNDataset(6); ds.BeforeScroll := DBConnector.DataEvent; with ds do begin aDatasource.DataSet := ds; open; DataEvents := ''; Resync([rmExact]); AssertEquals('deDataSetChange:0;',DataEvents); DataEvents := ''; next; AssertEquals('deCheckBrowseMode:0;DataEvent;deDataSetScroll:0;',DataEvents); close; end; aDatasource.Free; aDatalink.Free; end;} procedure TTestDatasources.TestDataEvent1; var i,count : integer; aDatasource : TDataSource; aDatalink1, aDatalink2 : TDataLink; ds : tdataset; begin aDatasource := TDataSource.Create(nil); aDatalink1 := TTestDataLink.Create; aDatalink1.DataSource := aDatasource; ds := DBConnector.GetNDataset(6); with ds do begin aDatasource.DataSet := ds; open; DataEvents := ''; THackDataset(ds).DataEvent(deCheckBrowseMode,0); AssertEquals('deCheckBrowseMode:0;',DataEvents); aDatalink2 := TTestDataLink.Create; aDatalink2.DataSource := aDatasource; DataEvents := ''; THackDataset(ds).DataEvent(deCheckBrowseMode,0); AssertEquals('deCheckBrowseMode:0;deCheckBrowseMode:0;',DataEvents); aDatalink2.free; DataEvents := ''; THackDataset(ds).DataEvent(deCheckBrowseMode,0); AssertEquals('deCheckBrowseMode:0;',DataEvents); close; end; end; procedure TTestDatasources.TestDataEvent2; var aDatasource : TDataSource; aDatalink : TDataLink; ds : tdataset; begin aDatasource := TDataSource.Create(nil); aDatalink := TTestDataLink.Create; aDatalink.DataSource := aDatasource; ds := DBConnector.GetTraceDataset(false); with ds do begin aDatasource.DataSet := ds; open; // The deDataSetChange and deDataSetScroll events should trigger a call to // TDataset.UpdateCursorPos... DataEvents := ''; THackDataset(ds).DataEvent(deDataSetChange,0); AssertEquals('SetCurrentRecord;deDataSetChange:0;',DataEvents); DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,0); AssertEquals('SetCurrentRecord;deDataSetScroll:0;DataSetScrolled:0;',DataEvents); // unless TDataset.State is dsInsert ds.insert; DataEvents := ''; AssertTrue(ds.State=dsInsert); THackDataset(ds).DataEvent(deDataSetChange,0); AssertEquals('deDataSetChange:0;',DataEvents); AssertTrue(ds.State=dsInsert); DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,0); AssertEquals('deDataSetScroll:0;DataSetScrolled:0;',DataEvents); end; end; procedure TTestDatasources.TestDataEvent3; var aDatasource : TDataSource; aDatalink : TDataLink; ds : tdataset; AFld : TField; begin aDatasource := TDataSource.Create(nil); aDatalink := TTestDataLink.Create; aDatalink.DataSource := aDatasource; ds := DBConnector.GetTraceDataset(false); with ds do begin aDatasource.DataSet := ds; open; AFld := FieldByName('id'); // On a deFieldChange event from a field with a fieldkind of fkData or // fkInternalCalc, TDataset.Modified must be set to true DataEvents := ''; AssertFalse(Modified); THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertTrue(Modified); AssertEquals('deFieldChange:ID;',DataEvents); Close; AFld := TIntegerField.Create(ds); AFld.FieldName := 'CALCFLD'; AFld.DataSet := ds; Afld.FieldKind := fkCalculated; Open; DataEvents := ''; AssertFalse(Modified); THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertFalse(Modified); AssertEquals('deFieldChange:CALCFLD;',DataEvents); end; end; procedure TTestDatasources.TestDataEvent4; var aDatasource : TDataSource; aDatalink : TDataLink; ds : tdataset; AFld : TField; begin aDatasource := TDataSource.Create(nil); aDatalink := TTestDataLink.Create; aDatalink.DataSource := aDatasource; ds := DBConnector.GetTraceDataset(false); with ds do begin aDatasource.DataSet := ds; // Ugly hack to imitate InternalCalcField, see // TDbfTraceDataset.InternalInitFieldDefs FieldDefs.Add('Name',ftString); FieldDefs.Find('Name').InternalCalcField:=True; open; AssertTrue(THackDataset(ds).InternalCalcFields); // If there are InternalCalcFields (InternalCalcFields=True) and the fieldkind // of the field from the deFieldChange event is fkData, then // RefreshIntenralCalcFields is called AFld := FieldByName('id'); DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('RefreshInternalCalcFields;deFieldChange:ID;',DataEvents); AFld := FieldByName('name'); AFld.FieldKind:=fkInternalCalc; DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('deFieldChange:NAME;',DataEvents); // If the TDataset.State is dsSetKey then IntenralCalcFields shoudn't get called THackDataset(ds).SetState(dsSetKey); AFld := FieldByName('id'); DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('deFieldChange:ID;',DataEvents); end; end; procedure TTestDatasources.TestDataEvent5; var aDatasource : TDataSource; aDatalink : TDataLink; ds : tdataset; AFld : TField; begin aDatasource := TDataSource.Create(nil); aDatalink := TTestDataLink.Create; aDatalink.DataSource := aDatasource; ds := DBConnector.GetTraceDataset(false); with ds do begin aDatasource.DataSet := ds; open; AFld := FieldByName('id'); AFld.OnChange:=FieldNotifyEvent; // When TDataset.State is not dsSetKey then TField.Change is called on a // deFieldChange event DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('FieldNotifyEvent;deFieldChange:ID;',DataEvents); THackDataset(ds).SetState(dsSetKey); DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('deFieldChange:ID;',DataEvents); end; end; procedure TTestDatasources.TestDataEvent6; var aDatasource : TDataSource; aDatalink : TDataLink; ds : tdataset; AFld : TField; begin aDatasource := TDataSource.Create(nil); aDatalink := TTestDataLink.Create; aDatalink.DataSource := aDatasource; ds := DBConnector.GetTraceDataset(false); with ds do begin aDatasource.DataSet := ds; AFld := TIntegerField.Create(ds); AFld.FieldName := 'ID'; AFld.DataSet := ds; AFld := TStringField.Create(ds); AFld.FieldName := 'NAME'; AFld.DataSet := ds; AFld := TIntegerField.Create(ds); AFld.FieldName := 'CALCFLD'; AFld.DataSet := ds; Afld.FieldKind := fkCalculated; open; // If there are Calculated fields and AutoCalcFields is true, then call // CalculateFields in case of a deFieldChange event, if the fields fieldkind // is fkData AFld := FieldByName('id'); DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('deFieldChange:ID;',DataEvents); DataEvents := ''; AutoCalcFields:=True; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('CalculateFields;ClearCalcFields;deFieldChange:ID;',DataEvents); AFld := FieldByName('calcfld'); DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('deFieldChange:CALCFLD;',DataEvents); // If the TDataset.State is dsSetKey then CalculateFields shoudn't get called THackDataset(ds).SetState(dsSetKey); AFld := FieldByName('id'); DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('deFieldChange:ID;',DataEvents); end; end; procedure TTestDatasources.TestDataEvent7; var aDatasource : TDataSource; aDatalink : TDataLink; ds : tdataset; AFld : TField; begin aDatasource := TDataSource.Create(nil); aDatalink := TTestDataLink.Create; aDatalink.DataSource := aDatasource; ds := DBConnector.GetTraceDataset(false); with ds do begin aDatasource.DataSet := ds; AFld := TIntegerField.Create(ds); AFld.FieldName := 'ID'; AFld.DataSet := ds; AFld := TStringField.Create(ds); AFld.FieldName := 'NAME'; AFld.DataSet := ds; AFld := TIntegerField.Create(ds); AFld.FieldName := 'CALCFLD'; AFld.DataSet := ds; Afld.FieldKind := fkCalculated; // Ugly hack to imitate InternalCalcField, see // TDbfTraceDataset.InternalInitFieldDefs FieldDefs.Add('Name',ftString); FieldDefs.Find('Name').InternalCalcField:=True; open; AssertTrue(THackDataset(ds).InternalCalcFields); // If there are InternalCalcFields and 'normal' Calculated fields, only // RefreshInternalCalcFields is called AFld := FieldByName('id'); DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('RefreshInternalCalcFields;deFieldChange:ID;',DataEvents); AFld := FieldByName('name'); AFld.FieldKind:=fkInternalCalc; DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('deFieldChange:NAME;',DataEvents); // If the TDataset.State is dsSetKey then InternalCalcFields shoudn't get called THackDataset(ds).SetState(dsSetKey); AFld := FieldByName('id'); DataEvents := ''; THackDataset(ds).DataEvent(deFieldChange,PtrInt(AFld)); AssertEquals('deFieldChange:ID;',DataEvents); end; end; procedure TTestDatasources.TestCalcFirstRecord1; var aDatasource : TDataSource; aDatalink : TDataLink; ds : tdataset; FirstRec : Integer; begin aDatasource := TDataSource.Create(nil); aDatalink := TTestDataLink.Create; aDatalink.DataSource := aDatasource; ds := DBConnector.GetNDataset(15); aDatasource.DataSet := ds; with ds do begin open; FirstRec := THackDataLink(aDatalink).FirstRecord; // Scroll '0' records, FirstRecord should stay the same, // and the there's no need to scroll the buffer. DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,0); AssertEquals('deDataSetScroll:0;DataSetScrolled:0;',DataEvents); AssertEquals(FirstRec,THackDataLink(aDatalink).FirstRecord); // Scroll 1 record forward, FirstRecord should stay the same, // but the buffer is scrolled one place back. DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,1); AssertEquals('deDataSetScroll:1;DataSetScrolled:-1;',DataEvents); AssertEquals(FirstRec,THackDataLink(aDatalink).FirstRecord); // Scroll 1 record backward, FirstRecord should stay the same, // but the buffer is scrolled one place back. DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,-1); AssertEquals('deDataSetScroll:-1;DataSetScrolled:1;',DataEvents); AssertEquals(FirstRec,THackDataLink(aDatalink).FirstRecord); // Remove the datasource. aDatasource.DataSet := nil; DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,1); AssertEquals('',DataEvents); // Set the buffer-size to 5 and add it to the dataset again aDatalink.BufferCount:=5; aDatasource.DataSet := ds; // Scroll '0' records, firstrecord should stay the same again, // and there's no need to scroll the buffer. DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,0); AssertEquals('deDataSetScroll:0;DataSetScrolled:0;',DataEvents); AssertEquals(FirstRec,THackDataLink(aDatalink).FirstRecord); // Scroll 1 record backwards with a buffer size of 5. // Now the buffer won't scroll, but FirstRecord is decremented DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,-1); AssertEquals('deDataSetScroll:-1;DataSetScrolled:0;',DataEvents); dec(FirstRec); AssertEquals(FirstRec,THackDataLink(aDatalink).FirstRecord); // Scroll one record forward again, no buffer scroll, FirstRecord // is inremented DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,1); AssertEquals('deDataSetScroll:1;DataSetScrolled:0;',DataEvents); inc(FirstRec); AssertEquals(FirstRec,THackDataLink(aDatalink).FirstRecord); // Scroll one more record forward, buffer will scroll, FirstRecord // stays constant DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,1); AssertEquals('deDataSetScroll:1;DataSetScrolled:-1;',DataEvents); AssertEquals(FirstRec,THackDataLink(aDatalink).FirstRecord); // Scroll two records backward, no buffer scroll, FirstRecord // is inremented twice DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,-2); AssertEquals('deDataSetScroll:-2;DataSetScrolled:0;',DataEvents); dec(FirstRec,2); AssertEquals(FirstRec,THackDataLink(aDatalink).FirstRecord); // Scroll 6 records forward, so the buffer is scrolled 4 positions backward // and FirstRecord is Incremented by 2 DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,6); AssertEquals('deDataSetScroll:6;DataSetScrolled:-4;',DataEvents); inc(FirstRec,2); AssertEquals(FirstRec,THackDataLink(aDatalink).FirstRecord); // The other way around, scroll 6 records back, so the buffer is scrolled 2 // positions forward and FirstRecord is decremented by 4 DataEvents := ''; THackDataset(ds).DataEvent(deDataSetScroll,-6); AssertEquals('deDataSetScroll:-6;DataSetScrolled:2;',DataEvents); dec(FirstRec,4); AssertEquals(FirstRec,THackDataLink(aDatalink).FirstRecord); end; end; procedure TTestDatasources.TestRefreshLookupList; var ds, lkpDs : TDataset; AFld1, AFld2, AFld3 : Tfield; Var1,Var2 : Variant; procedure TestLookupList; begin lkpDs.Open; lkpDs.first; while not LkpDs.eof do with AFld3 do begin Var1 := LkpDs.FieldValues[LookupResultField]; Var2 := LookupList.ValueOfKey(LkpDs.fieldvalues[LookupKeyFields]); AssertEquals(VarToStr(Var1),VarToStr(Var2)); lkpDs.Next; end; end; begin ds := DBConnector.GetNDataset(15); lkpDs := DBConnector.GetNDataset(5); with ds do begin AFld1 := TIntegerField.Create(ds); AFld1.FieldName := 'ID'; AFld1.DataSet := ds; AFld2 := TStringField.Create(ds); AFld2.FieldName := 'NAME'; AFld2.DataSet := ds; AFld3 := TIntegerField.Create(ds); with AFld3 do begin // Test if nothing happens when not all properties are filled FieldName := 'LookupFld'; FieldKind := fkLookup; DataSet := ds; RefreshLookupList; LookupDataSet := lkpDs; RefreshLookupList; LookupKeyFields:='name'; RefreshLookupList; LookupResultField:='ID'; RefreshLookupList; KeyFields:='name'; // Everything is filled in, this should run wihout any problems: RefreshLookupList; // The lookupdataset was closed, and should be closed again: AssertFalse(lkpDs.Active); // If some fields don't exist, check if an exception is raised: LookupKeyFields:='faulty'; AssertException(EDatabaseError,RefreshLookupList); LookupKeyFields:='name'; LookupResultField :='faulty'; AssertException(EDatabaseError,RefreshLookupList); LookupResultField :='ID'; // Check if the lookuplist is correctly filled RefreshLookupList; TestLookupList; // Check if the lookuplist is correctly filled when there are multiple // fields in the key LookupResultField:='name'; LookupKeyFields:='id;name'; RefreshLookupList; TestLookupList; end; AFld1.Free; AFld2.Free; AFld3.Free; end; end; procedure TTestDatasources.TestCalculateFields; var ds, lkpDs : TDataset; AFld1, AFld2, AFld3 : Tfield; StoreValue : Variant; Buffer: pchar; begin ds := DBConnector.GetTraceDataset(True); lkpDs := DBConnector.GetNDataset(5); with ds do begin AFld1 := TIntegerField.Create(ds); AFld1.FieldName := 'ID'; AFld1.DataSet := ds; AFld2 := TStringField.Create(ds); AFld2.FieldName := 'NAME'; AFld2.DataSet := ds; AFld3 := TIntegerField.Create(ds); with AFld3 do begin FieldName := 'LookupFld'; FieldKind := fkLookup; DataSet := ds; LookupDataSet := lkpDs; LookupKeyFields:='name'; LookupResultField:='ID'; KeyFields := 'name'; end; ds.OnCalcFields:=DatasetNotifyEvent; lkpds.Open; open; Buffer:=ds.ActiveBuffer; // If the state is dsInternalCalc, only the OnCalcField event should be called THackDataset(ds).SetState(dsInternalCalc); DataEvents:=''; StoreValue:=AFld3.Value; THackDataset(ds).CalculateFields(Buffer); AssertEquals('CalculateFields;DatasetNotifyEvent;',DataEvents); AssertEquals(VarToStr(StoreValue),VarToSTr(AFld3.Value)); THackDataset(ds).SetState(dsBrowse); // Also if the dataset is Unidirectional, only the OnCalcField event should be called THackDataset(ds).SetUniDirectional(True); DataEvents:=''; StoreValue:=AFld3.Value; THackDataset(ds).CalculateFields(Buffer); AssertEquals('CalculateFields;DatasetNotifyEvent;',DataEvents); AssertEquals(VarToStr(StoreValue),VarToSTr(AFld3.Value)); THackDataset(ds).SetUniDirectional(False); // Else, the value of all the lookup fields should get calculated edit; FieldByName('name').asstring := 'TestName3'; post; DataEvents:=''; THackDataset(ds).CalculateFields(Buffer); AssertEquals('CalculateFields;ClearCalcFields;DatasetNotifyEvent;',DataEvents); AssertEquals('3',VarToStr(AFld3.Value)); end; end; procedure TTestDatasources.TestCalcLookupValue; var ds, lkpDs : TDataset; AFld1, AFld2, AFld3 : Tfield; Buffer: pchar; begin ds := DBConnector.GetNDataset(True,15); lkpDs := DBConnector.GetNDataset(5); with ds do begin AFld1 := TIntegerField.Create(ds); AFld1.FieldName := 'ID'; AFld1.DataSet := ds; AFld2 := TStringField.Create(ds); AFld2.FieldName := 'NAME'; AFld2.DataSet := ds; AFld3 := TIntegerField.Create(ds); with AFld3 do begin FieldName := 'LookupFld'; FieldKind := fkLookup; DataSet := ds; LookupDataSet := lkpDs; LookupKeyFields:='name'; LookupResultField:='ID'; KeyFields := 'name'; end; ds.OnCalcFields:=DatasetNotifyEvent; lkpds.Open; open; Next; Buffer:=ds.ActiveBuffer; // When LookupCache is true, use the lookupCache (Here with the 'wrong' value 412) AFld3.LookupList.Clear; AFld3.LookupList.Add('TestName2',412); AFld3.LookupCache:=True; // CalculateFields is the only way to call CalcLookupValue THackDataset(ds).CalculateFields(Buffer); AssertEquals(412,AFld3.AsInteger); // Without lookupcache, return the right value AFld3.LookupCache:=False; THackDataset(ds).CalculateFields(Buffer); AssertEquals(2,AFld3.AsInteger); // If there's no LookupDataset, the result should be Null AFld3.LookupDataSet:= nil; THackDataset(ds).CalculateFields(Buffer); AssertTrue(AFld3.IsNull); // If there's no LookupDataset, the result should be Null AFld3.LookupDataSet:= nil; THackDataset(ds).CalculateFields(Buffer); AssertTrue(AFld3.IsNull); // Same holds for closed lookupdatasets AFld3.LookupDataSet:= lkpDs; lkpDs.Close; THackDataset(ds).CalculateFields(Buffer); AssertTrue(AFld3.IsNull); lkpds.Open; // Thing are getting interesting with multiple fields in the key: AFld3.LookupKeyFields:='name;id'; AFld3.KeyFields := 'name;id'; AFld3.LookupCache:=True; AFld3.LookupList.Clear; AFld3.LookupList.Add(VarArrayOf(['TestName2',2]),112); AFld3.LookupCache:=True; THackDataset(ds).CalculateFields(Buffer); AssertEquals(112,AFld3.AsInteger); AFld3.LookupCache:=False; // Now without a LookupCache // Disabled this part, since tDbf has problems with multiple-field keys { AFld3.LookupKeyFields:='name;id'; AFld3.KeyFields := 'name;id'; THackDataset(ds).CalculateFields(Buffer); AssertEquals(2,AFld3.AsInteger);} end; end; procedure TTestDatasources.TestEnableControls; var ds: TDataset; ADataLink : TTestDataLink; ADataSource : TDataSource; begin ds := DBConnector.GetTraceDataset(False); ADatasource := TDataSource.Create(nil); ADatalink := TTestDataLink.Create; ADatalink.DataSource := aDatasource; ADataSource.DataSet := ds; with ds do begin Open; // If DisableControls isn't called, nothing should happen. DataEvents:=''; EnableControls; AssertEquals('',DataEvents); DisableControls; DisableControls; // DisableControls is called twice. Ie: first call to enablecontrols should // still do nothing. DataEvents:=''; EnableControls; AssertEquals('',DataEvents); // On this call to Enablecontrols, the controls should get enabled again: DataEvents:=''; EnableControls; AssertEquals('SetCurrentRecord;deDataSetChange:0;',DataEvents); // If the state of the dataset has been changed while the controls were // disabled, then an deUpdateState event should be raised DisableControls; THackDataset(ds).SetState(dsSetKey); DataEvents:=''; EnableControls; AssertEquals('deUpdateState:0;SetCurrentRecord;deDataSetChange:0;',DataEvents); THackDataset(ds).SetState(dsBrowse); // If the dataset is closed while the controls were disabled, then only // an deUpdateState event should occur. DisableControls; Close; DataEvents:=''; EnableControls; AssertEquals('deUpdateState:0;',DataEvents); // And the same happens if the dataset was opened DisableControls; Open; DataEvents:=''; EnableControls; AssertEquals('deUpdateState:0;',DataEvents); close; end; ADataLink.Free; ADataSource.Free; end; initialization if uppercase(dbconnectorname)='DBF' then RegisterTest(TTestDatasources); end.