dbdigest.pp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. {
  2. This file is part of the Free Pascal test suite.
  3. Copyright (c) 2002 by the Free Pascal development team.
  4. This program inserts the last tests run
  5. into TESTSUITE database.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. {$mode objfpc}
  13. {$h+}
  14. {$ifndef win32}
  15. {$linklib pthread}
  16. {$endif}
  17. program dbdigest;
  18. uses
  19. {$ifdef unix}
  20. cthreads,
  21. {$endif}
  22. types, classes, custapp, sysutils, inifiles, tsstring, tsutils, tstypes, tsdb, digestanalyst;
  23. Type
  24. { TThreadTask }
  25. TThreadTask = Class(TObject)
  26. CfgFileName : string;
  27. Config: TDigestConfig;
  28. Data: TTestRunData;
  29. Constructor Create(const aFileName : String; const aConfig : TDigestConfig; aData : TTestRunData);
  30. end;
  31. { TDBDigestApplication }
  32. TDBDigestApplication = class(TCustomApplication)
  33. Const
  34. ShortOpts =
  35. 'd'+ { DatabaseName }
  36. 'h'+ { Host }
  37. 'u'+ { UserName }
  38. 'p'+ { Password }
  39. 'P'+ { Port }
  40. 'l'+ { LogFile }
  41. 'L'+ { LongLogFile }
  42. 'o'+ { OS }
  43. 'c'+ { CPU }
  44. 'a'+ { Category }
  45. 'v'+ { Version }
  46. 't'+ { Date }
  47. 's'+ { Submitter }
  48. 'm'+ { Machine }
  49. 'C'+ { Comment }
  50. 'S'+ { TestSrcDir }
  51. 'r'+ { RelSrcDir }
  52. 'T'+ { TaskList }
  53. 'j'+ { ThreadCount }
  54. 'V'+ { Verbose }
  55. 'Q' { SQL }
  56. ;
  57. LongOpts : Array of string = (
  58. 'databasename',
  59. 'host',
  60. 'username',
  61. 'password',
  62. 'port',
  63. 'logfile',
  64. 'longlogfile',
  65. 'os',
  66. 'cpu',
  67. 'category',
  68. 'version',
  69. 'date',
  70. 'submitter',
  71. 'machine',
  72. 'comment',
  73. 'testsrcdir',
  74. 'relsrcdir',
  75. 'tasklist',
  76. 'threadcount',
  77. 'verbose',
  78. 'sql',
  79. 'compilerdate',
  80. 'compilerfullversion',
  81. 'svncompilerrevision',
  82. 'svntestsrevision',
  83. 'svnrtlrevision',
  84. 'svnpackagesrevision'
  85. );
  86. private
  87. FTasks : TThreadList;
  88. FMaxThreads : Integer;
  89. FThreadCount : Integer;
  90. // Process the command line. Return true if we can continue
  91. function ProcessCommandLine(var aConfig: TDigestConfig; var aData: TTestRunData): Boolean;
  92. // Check the names of the log files, expanding them if needed.
  93. function CheckConfigFiles(lCfg: String; var lData: TTestRunData): Boolean;
  94. // Extract a date.
  95. class function ExtractDate(aValue: string): TDateTime;
  96. // Analyse a log file (i.e. one dbdigest.cfg file)
  97. procedure Analyze(const aConfig: TDigestConfig; const aData: TTestRunData);
  98. // process a config file (dbdigest.cfg file);
  99. procedure ProcessConfigfile(const aFileName: String; var aConfig: TDigestConfig; var aData: TTestRunData);
  100. // process a single option. Adapt aConf,aData as needed. Return false if the option was not recognized.
  101. function ProcessOption(const aOption: String; aValue: String; var aConfig: TDigestConfig; var aData: TTestRunData): Boolean;
  102. // Read /etc/dbdigest.ini for database configuration.
  103. procedure ReadSystemDBConfig(var aConfig: TDigestConfig);
  104. // In thread mode, create a list of tasks.
  105. function CreateTaskList(const aBaseConfig: TDigestConfig; const aBaseData: TTestRunData): boolean;
  106. // Callback when a task is done. Checks to see if additional threads must be started.
  107. procedure TaskDone(Sender: TObject);
  108. // Wait for all tasks & threads to terminate.
  109. procedure WaitForThreads;
  110. // Start as many threads as allowed, up to task count.
  111. procedure StartThreads;
  112. protected
  113. // Run
  114. procedure DoRun; override;
  115. // Print usage message.
  116. procedure Usage(const aMsg: String);
  117. Public
  118. Constructor Create(aOwner : TComponent); override;
  119. end;
  120. { TProcessFileThread }
  121. TProcessFileThread = class(TThread)
  122. Private
  123. FTask : TThreadTask;
  124. FApp : TDBDigestApplication;
  125. Public
  126. Constructor Create(aApp : TDBDigestApplication; const aTask : TThreadTask; aOnTerminate : TNotifyEvent);
  127. Destructor Destroy; override;
  128. Procedure Execute; override;
  129. end;
  130. { TThreadTask }
  131. constructor TThreadTask.Create(const aFileName: String; const aConfig: TDigestConfig; aData: TTestRunData);
  132. begin
  133. CfgFileName:=aFileName;
  134. Config:=aConfig;
  135. Data:=aData;
  136. end;
  137. { TProcessFileThread }
  138. constructor TProcessFileThread.Create(aApp: TDBDigestApplication; const aTask: TThreadTask; aOnTerminate: TNotifyEvent);
  139. begin
  140. FTask:=aTask;
  141. FApp:=aApp;
  142. Self.OnTerminate:=aOnTerminate;
  143. Inherited create(False);
  144. end;
  145. destructor TProcessFileThread.Destroy;
  146. var
  147. lPrefix : String;
  148. lCfg : String;
  149. begin
  150. try
  151. lCfg:=FTask.CfgFileName;
  152. lPrefix:='['+IntToStr(PtrInt(GetCurrentThreadId))+' - '+lCfg+'] ';
  153. FreeAndNil(FTask);
  154. Verbose(V_DEBUG,lPrefix+' task destroyed ');
  155. except
  156. On e : Exception do
  157. Verbose(V_WARNING,lPrefix+Format('Error %s during processing of "%s": %s',[E.ClassName,lCfg,E.Message]));
  158. end;
  159. inherited Destroy;
  160. end;
  161. procedure TProcessFileThread.Execute;
  162. var
  163. lPrefix:String;
  164. begin
  165. try
  166. lPrefix:='['+IntToStr(PtrInt(GetCurrentThreadId))+' - '+FTask.CfgFileName+'] ';
  167. FApp.Analyze(FTask.Config,FTask.Data);
  168. Writeln(IntToStr(PtrInt(GetCurrentThreadId))+'Thread done');
  169. except
  170. On e : Exception do
  171. Verbose(V_WARNING,lPrefix+Format('Error %s during processing of "%s": %s',[E.ClassName,FTask.CfgFileName,E.Message]));
  172. end;
  173. end;
  174. class function TDBDigestApplication.ExtractDate(aValue: string): TDateTime;
  175. var
  176. year,month,day,min,hour : word;
  177. begin
  178. if (Length(avalue)=12) or (Length(avalue)=8) then
  179. begin
  180. year:=StrToInt(Copy(avalue,1,4));
  181. month:=StrToInt(Copy(avalue,5,2));
  182. day:=StrToInt(Copy(aValue,7,2));
  183. if Length(avalue)=12 then
  184. begin
  185. hour:=StrToInt(Copy(aValue,9,2));
  186. min:=StrToInt(Copy(aValue,11,2));
  187. end
  188. else
  189. begin
  190. hour:=0;
  191. min:=0;
  192. end;
  193. Result:=EncodeDate(year,month,day)+EncodeTime(hour,min,0,0);
  194. end
  195. else
  196. Verbose(V_Error,'Error in date format, use YYYYMMDDhhmm');
  197. end;
  198. function TDBDigestApplication.ProcessOption(const aOption: String; aValue: String; var aConfig: TDigestConfig;
  199. var aData: TTestRunData): Boolean;
  200. begin
  201. Result:=True;
  202. Verbose(V_DEBUG,'Processing option: '+aOption);
  203. Case aOption of
  204. 'd','databasename' : aConfig.databasename:=aValue;
  205. 'h','host' : aConfig.host:=aValue;
  206. 'u','username': aConfig.username:=aValue;
  207. 'p','password': aConfig.password:=aValue;
  208. 'P','port': aConfig.port:=StrToIntDef(aValue,0);
  209. 'l','logfile': aData.logfile:=aValue;
  210. 'L','longlogfile': aData.longlogfile:=aValue;
  211. 'o','os': aData.os:=aValue;
  212. 'c','cpu': aData.cpu:=aValue;
  213. 'a','category': aData.category:=aValue;
  214. 'v','version': aData.version:=aValue;
  215. 't','date': aData.date:=ExtractDate(aValue);
  216. 's','submitter': aData.submitter:=aValue;
  217. 'm','machine': aData.machine:=aValue;
  218. 'C','comment': aData.config:=aValue;
  219. 'D','description': aData.description:=aValue;
  220. 'S','testsrcdir': aConfig.testsrcdir:=aValue;
  221. 'r','relsrcdir': aConfig.relsrcdir:=aValue;
  222. 'V','verbose': DoVerbose:=True;
  223. 'sql': dosql:=true;
  224. 'T','tasklist' : ; // treated elsewhere
  225. 'j','threadcount' : ; // treated elsewhere
  226. 'compilerdate': aData.CompilerDate:=aValue;
  227. 'compilerfullversion': aData.CompilerFullVersion:=aValue;
  228. 'svncompilerrevision': aData.CompilerRevision:=aValue;
  229. 'svntestsrevision': aData.TestsRevision:=aValue;
  230. 'svnrtlrevision': aData.RTLRevision:=aValue;
  231. 'svnpackagesrevision' : aData.PackagesRevision:=aValue;
  232. else
  233. Verbose(V_ERROR,'Unknown processing option: '+aOption);
  234. end;
  235. end;
  236. procedure TDBDigestApplication.ProcessConfigfile(const aFileName: String; var aConfig: TDigestConfig; var aData: TTestRunData);
  237. Var
  238. Cfg : TStrings;
  239. aLine,S,N,V : String;
  240. I : Integer;
  241. begin
  242. // Set the default value for old digests without RelSrcDir to the rtl/compiler
  243. // testsuite
  244. If Not FileExists(aFileName) Then
  245. Exit;
  246. Verbose(V_DEBUG,'Parsing config file: '+aFileName);
  247. Cfg:=TStringList.Create;
  248. try
  249. Cfg.LoadFromFile(aFileName);
  250. For aLine in Cfg do
  251. begin
  252. S:=Trim(aLine);
  253. I:=Pos('#',S);
  254. If I<>0 then
  255. S:=Copy(S,1,I-1);
  256. If (S<>'') then
  257. begin
  258. I:=Pos('=',S);
  259. if (I=0) then
  260. Verbose(V_ERROR,'Unknown processing option: '+S)
  261. else
  262. begin
  263. N:=LowerCase(Copy(S,1,I-1));
  264. V:=Copy(S,I+1,Length(S)-I);
  265. ProcessOption(N,V,aConfig,aData);
  266. end;
  267. end;
  268. end;
  269. finally
  270. Cfg.Free;
  271. end;
  272. end;
  273. { TDBDigestApplication }
  274. procedure TDBDigestApplication.Usage(const aMsg: String);
  275. begin
  276. if (aMsg<>'') then
  277. Writeln('Error : ',aMsg);
  278. Writeln('Usage: ',ExeName,' [options] [test run data options]');
  279. Writeln('Configuration options:');
  280. Writeln('-H --help show this help');
  281. Writeln('-d --databasename=NAME database name');
  282. Writeln('-f --config=FILENAME config file. If not set, dbdigest.cfg is used.');
  283. Writeln('-h --host=HOST database hostname');
  284. Writeln('-p --password=PWD database user password');
  285. Writeln('-P --port=NNN database connection port');
  286. Writeln('-Q --sql be verbose about sql queries');
  287. Writeln('-r --relsrcdir relative source dir');
  288. Writeln('-S --testsrcdir test source dir');
  289. Writeln('-u --username=USER database user name');
  290. Writeln('-T --tasklist=FILE file with configuration file names to imports.');
  291. Writeln('-j --threadcount=N Number of threads to use');
  292. Writeln('-V --verbose be more verbose');
  293. Writeln('Test run data:');
  294. Writeln('-l --logfile=FILE set log file to analyse');
  295. Writeln('-L --longlogfile=FILE set long log filename (logs of run tests)');
  296. Writeln('-o --os=OS set OS for testrun');
  297. Writeln('-c --cpu=CPU set CPU');
  298. Writeln('-a --category=CAT set category');
  299. Writeln('-v --version=VER set compiler version');
  300. Writeln('-t --date=DATE date in YYYMMDD(hhmmnn) format');
  301. Writeln('-s --submitter=NAME submitter name');
  302. Writeln('-m --machine=NAME set machine name on which testsuite was run');
  303. Writeln('-C --compile-flags=FLAGS set used compilation flags');
  304. Writeln(' --comment=FLAGS backwards compatible way to set compilation flags (deprecated)');
  305. Writeln('-D --description=DESC set config description (helpful comment)');
  306. Writeln(' --compilerdate=DATE set compiler date');
  307. Writeln(' --compilerfullversion=VERSION set full compiler version');
  308. Writeln(' --svncompilerrevision=REV set revision of used compiler');
  309. Writeln(' --svntestsrevision=REV set revision of testsuite files');
  310. Writeln(' --svnrtlrevision=REV set revision of RTL');
  311. Writeln(' --svnpackagesrevision=REV set revison of packages');
  312. Writeln('');
  313. Writeln('If -T is specified, no test run options may be specified');
  314. Writeln('');
  315. Writeln('The config file can contain the same options as the command-line in the form.');
  316. Writeln('option=value');
  317. Writeln('where option is the long or short version of the option');
  318. Writeln('comments may be included using the # character.');
  319. ExitCode:=Ord(aMsg<>'');
  320. end;
  321. constructor TDBDigestApplication.Create(aOwner: TComponent);
  322. begin
  323. inherited Create(aOwner);
  324. FTasks:=TThreadList.Create;
  325. end;
  326. function TDBDigestApplication.ProcessCommandLine(var aConfig: TDigestConfig; var aData : TTestRunData): Boolean;
  327. Function MakeOpts(s : string) : string;
  328. var
  329. C : char;
  330. begin
  331. Result:='';
  332. For C in s do
  333. begin
  334. Result:=Result+C;
  335. if not (C in ['V','Q']) then
  336. Result:=Result+':';
  337. end;
  338. end;
  339. Function MakeLongOpts(s : array of string) : TStringDynArray;
  340. var
  341. I : Integer;
  342. begin
  343. Result:=['help'];
  344. SetLength(Result,1+Length(S));
  345. For I:=0 to Length(S)-1 do
  346. begin
  347. Result[1+I]:=S[I];
  348. if (S[I]<>'verbose') and (S[I]<>'sql') then
  349. Result[1+I]:=Result[1+I]+':';
  350. end;
  351. end;
  352. var
  353. Long,ErrMsg,lValue : String;
  354. Short : Char;
  355. I : integer;
  356. lHas : boolean;
  357. begin
  358. ErrMsg:=CheckOptions(MakeOpts(ShortOpts)+'H',MakeLongOpts(LongOpts));
  359. Result:=(ErrMsg='');
  360. if (not Result) or HasOption('H','help') then
  361. begin
  362. Usage(ErrMsg);
  363. Exit(false);
  364. end;
  365. I:=0;
  366. For Long in LongOpts do
  367. begin
  368. Inc(I);
  369. if I<=Length(ShortOpts) then
  370. begin
  371. Short:=ShortOpts[I];
  372. lHas:=HasOption(Short,Long);
  373. lValue:=GetOptionValue(Short,Long);
  374. end
  375. else
  376. begin
  377. Short:=#0;
  378. lHas:=HasOption(Long);
  379. lValue:=GetOptionValue(Long);
  380. end;
  381. if lHas then
  382. ProcessOption(long,lValue,aConfig,aData);
  383. end;
  384. Result:=True;
  385. end;
  386. procedure TDBDigestApplication.Analyze(const aConfig : TDigestConfig; const aData : TTestRunData);
  387. var
  388. lSQL : TTestSQL;
  389. lDigest : TDBDigestAnalyzer;
  390. lPrefix : string;
  391. begin
  392. lDigest:=Nil;
  393. With aConfig do
  394. lSQL:=TTestSQL.create(databasename,host,username,password,port);
  395. try
  396. lSQL.ConnectToDatabase;
  397. if GetCurrentThreadId<>MainThreadID then
  398. lPrefix:='['+IntToStr(PtrInt(GetCurrentThreadId))+' - '+aData.logfile+']: '
  399. else
  400. lPrefix:='';
  401. lSQL.LogPrefix:=lPrefix;
  402. lDigest:=TDBDigestAnalyzer.Create(lSQL,lPrefix);
  403. lDigest.Analyse(aConfig,aData);
  404. finally
  405. lDigest.Free;
  406. lSQL.Free;
  407. end;
  408. end;
  409. procedure TDBDigestApplication.ReadSystemDBConfig(var aConfig : TDigestConfig);
  410. // Keep filename in sync with algorithm in dbadd
  411. var
  412. lFileName : String;
  413. Ini : TCustomIniFile;
  414. begin
  415. lFileName:='/etc/dbdigest.ini';
  416. if not FileExists(lFileName) then exit;
  417. Ini:=TMemIniFile.Create(lFileName);
  418. With Ini do
  419. try
  420. aConfig.DatabaseName:=ReadString(SSection,KeyName,'testsuite');
  421. aConfig.Host:=ReadString(SSection,KeyHost,'localhost');
  422. aConfig.UserName:=ReadString(SSection,KeyUser,'');
  423. aConfig.Password:=ReadString(SSection,KeyPassword,'');
  424. aConfig.Port:=ReadInteger(SSection,KeyPort,0);
  425. finally
  426. Ini.Free;
  427. end;
  428. end;
  429. function TDBDigestApplication.CheckConfigFiles(lCfg : String; var lData : TTestRunData) : Boolean;
  430. function CheckFile(const aDir : String; var aFile : String) : boolean;
  431. var
  432. lExpanded : string;
  433. begin
  434. if (aFile<>'') and (aFile[1]<>'/') then
  435. begin
  436. lExpanded:=aDir+aFile;
  437. Verbose(V_Debug,Format('Expanding file from %s to %s',[aFile,lExpanded]));
  438. aFile:=lExpanded;
  439. end;
  440. Result:=FileExists(aFile);
  441. if not Result then
  442. Verbose(V_Warning,Format('file does not exist: %s',[lExpanded]));
  443. end;
  444. var
  445. lDir : String;
  446. begin
  447. lDir:=ExtractFilePath(ExpandFileName(lCfg));
  448. Result:=CheckFile(lDir,lData.logfile);
  449. if Result then
  450. Result:=CheckFile(lDir,lData.longlogfile);
  451. end;
  452. function TDBDigestApplication.CreateTaskList(const aBaseConfig: TDigestConfig; const aBaseData: TTestRunData) : boolean;
  453. var
  454. lCfg,lFileName : String;
  455. L : TStrings;
  456. lConfig : TDigestConfig;
  457. lData : TTestRunData;
  458. lList : TList;
  459. begin
  460. Result:=False;
  461. lFileName:=GetOptionValue('T','tasklist');
  462. if not FileExists(lFileName) then
  463. begin
  464. Verbose(V_Normal,'No such file :'+lFileName);
  465. Exit;
  466. end;
  467. L:=TStringList.Create;
  468. try
  469. l.LoadFromFile(lFileName);
  470. Result:=True;
  471. For lcfg in L do
  472. begin
  473. if not FileExists(lCfg) then
  474. begin
  475. Verbose(V_Warning,'No such file: '+lcfg);
  476. Result:=False;
  477. end
  478. else
  479. begin
  480. lConfig:=aBaseConfig;
  481. lData:=aBaseData;
  482. lList:=FTasks.LockList;
  483. ProcessConfigfile(lCfg,lConfig,lData);
  484. if CheckConfigFiles(lCfg,lData) then
  485. lList.Add(TThreadTask.Create(lCfg,lConfig,lData))
  486. else
  487. Result:=False;
  488. end;
  489. end;
  490. finally
  491. l.Free;
  492. end;
  493. end;
  494. procedure TDBDigestApplication.TaskDone(Sender: TObject);
  495. begin
  496. InterlockedDecrement(FThreadCount);
  497. StartThreads;
  498. end;
  499. Procedure TDBDigestApplication.StartThreads;
  500. var
  501. L : TList;
  502. lTask : TThreadTask;
  503. begin
  504. L:=FTasks.LockList;
  505. try
  506. Verbose(V_DEBUG,Format('Starting tasks. Current thread count: %d remaining tasks: %d.',[FThreadCount,l.Count]));
  507. While (L.Count>0) and (FThreadCount<FMaxThreads) do
  508. begin
  509. lTask:=TThreadTask(L[0]);
  510. L.Delete(0);
  511. Verbose(V_DEBUG,'Starting task for '+lTask.CfgFileName);
  512. TProcessFileThread.Create(Self,lTask,@TaskDone);
  513. InterlockedIncrement(FThreadCount);
  514. end;
  515. finally
  516. FTasks.UnlockList;
  517. end;
  518. end;
  519. procedure TDBDigestApplication.WaitForThreads;
  520. var
  521. lDone : Boolean;
  522. lList : TList;
  523. begin
  524. Repeat
  525. CheckSynchronize;
  526. Sleep(100);
  527. lList:=FTasks.LockList;
  528. try
  529. Verbose(V_DEBUG,Format('Waiting...(Todo: %d threads: %d)',[lList.Count,FThreadCount]));
  530. lDone:=(lList.Count=0) and (FThreadCount=0);
  531. finally
  532. FTasks.UnlockList;
  533. end
  534. until ldone;
  535. end;
  536. procedure TDBDigestApplication.DoRun;
  537. var
  538. lConfigFile : String;
  539. lConfig : TDigestConfig;
  540. lData : TTestRunData;
  541. begin
  542. Terminate;
  543. lConfigFile:=GetOptionValue('f','config');
  544. if lConfigFile='' then
  545. lConfigFile:='dbdigest.cfg';
  546. lConfig:=Default(TDigestConfig);
  547. lConfig.RelSrcDir:='tests/';
  548. ReadSystemDBConfig(lConfig);
  549. if not HasOption('T','tasklist') then
  550. begin
  551. lData:=Default(TTestRunData);
  552. ProcessConfigFile(lConfigFile,lConfig,lData);
  553. if ProcessCommandLine(lConfig,lData) then
  554. Analyze(lConfig,lData);
  555. end
  556. else
  557. begin
  558. FMaxThreads:=StrToIntDef(GetOptionValue('j','threadcount'),4);
  559. if ProcessCommandLine(lConfig,lData) then
  560. if CreateTaskList(lConfig,lData) then
  561. begin
  562. StartThreads;
  563. WaitForThreads;
  564. end;
  565. end;
  566. end;
  567. var
  568. Application : TDBDigestApplication;
  569. begin
  570. Application:=TDBDigestApplication.Create(Nil);
  571. Application.Initialize;
  572. Application.Run;
  573. Application.Free;
  574. end.