dbdigest.pp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  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('-r --relsrcdir relative source dir');
  287. Writeln('-S --testsrcdir test source dir');
  288. Writeln('-u --username=USER database user name');
  289. Writeln('-T --tasklist=FILE file with configuration file names to imports.');
  290. Writeln('-j --threadcount=N Number of threads to use');
  291. Writeln('-V --verbose be more verbose');
  292. Writeln('Test run data:');
  293. Writeln('-l --logfile=FILE set log file to analyse');
  294. Writeln('-L --longlogfile=FILE set long log filename (logs of run tests)');
  295. Writeln('-o --os=OS set OS for testrun');
  296. Writeln('-c --cpu=CPU set CPU');
  297. Writeln('-a --category=CAT set category');
  298. Writeln('-v --version=VER set compiler version');
  299. Writeln('-t --date=DATE date in YYYMMDD(hhmmnn) format');
  300. Writeln('-s --submitter=NAME submitter name');
  301. Writeln('-m --machine=NAME set machine name on which testsuite was run');
  302. Writeln('-C --compile-flags=FLAGS set used compilation flags');
  303. Writeln(' --comment=FLAGS backwards compatible way to set compilation flags (deprecated)');
  304. Writeln('-D --description=DESC set config description (helpful comment)');
  305. Writeln(' --compilerdate=DATE set compiler date');
  306. Writeln(' --compilerfullversion=VERSION set full compiler version');
  307. Writeln(' --svncompilerrevision=REV set revision of used compiler');
  308. Writeln(' --svntestsrevision=REV set revision of testsuite files');
  309. Writeln(' --svnrtlrevision=REV set revision of RTL');
  310. Writeln(' --svnpackagesrevision=REV set revison of packages');
  311. Writeln('');
  312. Writeln('If -T is specified, no test run options may be specified');
  313. Writeln('');
  314. Writeln('The config file can contain the same options as the command-line in the form.');
  315. Writeln('option=value');
  316. Writeln('where option is the long or short version of the option');
  317. Writeln('comments may be included using the # character.');
  318. ExitCode:=Ord(aMsg<>'');
  319. end;
  320. constructor TDBDigestApplication.Create(aOwner: TComponent);
  321. begin
  322. inherited Create(aOwner);
  323. FTasks:=TThreadList.Create;
  324. end;
  325. function TDBDigestApplication.ProcessCommandLine(var aConfig: TDigestConfig; var aData : TTestRunData): Boolean;
  326. Function MakeOpts(s : string) : string;
  327. var
  328. C : char;
  329. begin
  330. Result:='';
  331. For C in s do
  332. begin
  333. Result:=Result+C;
  334. if not (C in ['V','Q']) then
  335. Result:=Result+':';
  336. end;
  337. end;
  338. Function MakeLongOpts(s : array of string) : TStringDynArray;
  339. var
  340. I : Integer;
  341. begin
  342. Result:=['help'];
  343. SetLength(Result,1+Length(S));
  344. For I:=0 to Length(S)-1 do
  345. begin
  346. Result[1+I]:=S[I];
  347. if (S[I]<>'verbose') and (S[I]<>'sql') then
  348. Result[1+I]:=Result[1+I]+':';
  349. end;
  350. end;
  351. var
  352. Long,ErrMsg,lValue : String;
  353. Short : Char;
  354. I : integer;
  355. lHas : boolean;
  356. begin
  357. ErrMsg:=CheckOptions(MakeOpts(ShortOpts)+'H',MakeLongOpts(LongOpts));
  358. Result:=(ErrMsg='');
  359. if (not Result) or HasOption('H','help') then
  360. begin
  361. Usage(ErrMsg);
  362. Exit(false);
  363. end;
  364. I:=0;
  365. For Long in LongOpts do
  366. begin
  367. Inc(I);
  368. if I<=Length(ShortOpts) then
  369. begin
  370. Short:=ShortOpts[I];
  371. lHas:=HasOption(Short,Long);
  372. lValue:=GetOptionValue(Short,Long);
  373. end
  374. else
  375. begin
  376. Short:=#0;
  377. lHas:=HasOption(Long);
  378. lValue:=GetOptionValue(Long);
  379. end;
  380. if lHas then
  381. ProcessOption(long,lValue,aConfig,aData);
  382. end;
  383. Result:=True;
  384. end;
  385. procedure TDBDigestApplication.Analyze(const aConfig : TDigestConfig; const aData : TTestRunData);
  386. var
  387. lSQL : TTestSQL;
  388. lDigest : TDBDigestAnalyzer;
  389. lPrefix : string;
  390. begin
  391. lDigest:=Nil;
  392. With aConfig do
  393. lSQL:=TTestSQL.create(databasename,host,username,password,port);
  394. try
  395. lSQL.ConnectToDatabase;
  396. if GetCurrentThreadId<>MainThreadID then
  397. lPrefix:='['+IntToStr(PtrInt(GetCurrentThreadId))+' - '+aData.logfile+']: '
  398. else
  399. lPrefix:='';
  400. lSQL.LogPrefix:=lPrefix;
  401. lDigest:=TDBDigestAnalyzer.Create(lSQL,lPrefix);
  402. lDigest.Analyse(aConfig,aData);
  403. finally
  404. lDigest.Free;
  405. lSQL.Free;
  406. end;
  407. end;
  408. procedure TDBDigestApplication.ReadSystemDBConfig(var aConfig : TDigestConfig);
  409. // Keep filename in sync with algorithm in dbadd
  410. var
  411. lFileName : String;
  412. Ini : TCustomIniFile;
  413. begin
  414. lFileName:='/etc/dbdigest.ini';
  415. if not FileExists(lFileName) then exit;
  416. Ini:=TMemIniFile.Create(lFileName);
  417. With Ini do
  418. try
  419. aConfig.DatabaseName:=ReadString(SSection,KeyName,'testsuite');
  420. aConfig.Host:=ReadString(SSection,KeyHost,'localhost');
  421. aConfig.UserName:=ReadString(SSection,KeyUser,'');
  422. aConfig.Password:=ReadString(SSection,KeyPassword,'');
  423. aConfig.Port:=ReadInteger(SSection,KeyPort,0);
  424. finally
  425. Ini.Free;
  426. end;
  427. end;
  428. function TDBDigestApplication.CheckConfigFiles(lCfg : String; var lData : TTestRunData) : Boolean;
  429. function CheckFile(const aDir : String; var aFile : String) : boolean;
  430. var
  431. lExpanded : string;
  432. begin
  433. if (aFile<>'') and (aFile[1]<>'/') then
  434. begin
  435. lExpanded:=aDir+aFile;
  436. Verbose(V_Debug,Format('Expanding file from %s to %s',[aFile,lExpanded]));
  437. aFile:=lExpanded;
  438. end;
  439. Result:=FileExists(aFile);
  440. if not Result then
  441. Verbose(V_Warning,Format('file does not exist: %s',[lExpanded]));
  442. end;
  443. var
  444. lDir : String;
  445. begin
  446. lDir:=ExtractFilePath(ExpandFileName(lCfg));
  447. Result:=CheckFile(lDir,lData.logfile);
  448. if Result then
  449. Result:=CheckFile(lDir,lData.longlogfile);
  450. end;
  451. function TDBDigestApplication.CreateTaskList(const aBaseConfig: TDigestConfig; const aBaseData: TTestRunData) : boolean;
  452. var
  453. lCfg,lFileName : String;
  454. L : TStrings;
  455. lConfig : TDigestConfig;
  456. lData : TTestRunData;
  457. lList : TList;
  458. begin
  459. Result:=False;
  460. lFileName:=GetOptionValue('T','tasklist');
  461. if not FileExists(lFileName) then
  462. begin
  463. Verbose(V_Normal,'No such file :'+lFileName);
  464. Exit;
  465. end;
  466. L:=TStringList.Create;
  467. try
  468. l.LoadFromFile(lFileName);
  469. Result:=True;
  470. For lcfg in L do
  471. begin
  472. if not FileExists(lCfg) then
  473. begin
  474. Verbose(V_Warning,'No such file: '+lcfg);
  475. Result:=False;
  476. end
  477. else
  478. begin
  479. lConfig:=aBaseConfig;
  480. lData:=aBaseData;
  481. lList:=FTasks.LockList;
  482. ProcessConfigfile(lCfg,lConfig,lData);
  483. if CheckConfigFiles(lCfg,lData) then
  484. lList.Add(TThreadTask.Create(lCfg,lConfig,lData))
  485. else
  486. Result:=False;
  487. end;
  488. end;
  489. finally
  490. l.Free;
  491. end;
  492. end;
  493. procedure TDBDigestApplication.TaskDone(Sender: TObject);
  494. begin
  495. InterlockedDecrement(FThreadCount);
  496. StartThreads;
  497. end;
  498. Procedure TDBDigestApplication.StartThreads;
  499. var
  500. L : TList;
  501. lTask : TThreadTask;
  502. begin
  503. L:=FTasks.LockList;
  504. try
  505. Verbose(V_DEBUG,Format('Starting tasks. Current thread count: %d remaining tasks: %d.',[FThreadCount,l.Count]));
  506. While (L.Count>0) and (FThreadCount<FMaxThreads) do
  507. begin
  508. lTask:=TThreadTask(L[0]);
  509. L.Delete(0);
  510. Verbose(V_DEBUG,'Starting task for '+lTask.CfgFileName);
  511. TProcessFileThread.Create(Self,lTask,@TaskDone);
  512. InterlockedIncrement(FThreadCount);
  513. end;
  514. finally
  515. FTasks.UnlockList;
  516. end;
  517. end;
  518. procedure TDBDigestApplication.WaitForThreads;
  519. var
  520. lDone : Boolean;
  521. lList : TList;
  522. begin
  523. Repeat
  524. CheckSynchronize;
  525. Sleep(100);
  526. lList:=FTasks.LockList;
  527. try
  528. Verbose(V_DEBUG,Format('Waiting...(Todo: %d threads: %d)',[lList.Count,FThreadCount]));
  529. lDone:=(lList.Count=0) and (FThreadCount=0);
  530. finally
  531. FTasks.UnlockList;
  532. end
  533. until ldone;
  534. end;
  535. procedure TDBDigestApplication.DoRun;
  536. var
  537. lConfigFile : String;
  538. lConfig : TDigestConfig;
  539. lData : TTestRunData;
  540. begin
  541. Terminate;
  542. lConfigFile:=GetOptionValue('f','config');
  543. if lConfigFile='' then
  544. lConfigFile:='dbdigest.cfg';
  545. lConfig:=Default(TDigestConfig);
  546. lConfig.RelSrcDir:='tests/';
  547. ReadSystemDBConfig(lConfig);
  548. if not HasOption('T','tasklist') then
  549. begin
  550. lData:=Default(TTestRunData);
  551. ProcessConfigFile(lConfigFile,lConfig,lData);
  552. if ProcessCommandLine(lConfig,lData) then
  553. Analyze(lConfig,lData);
  554. end
  555. else
  556. begin
  557. FMaxThreads:=StrToIntDef(GetOptionValue('j','threadcount'),4);
  558. if ProcessCommandLine(lConfig,lData) then
  559. if CreateTaskList(lConfig,lData) then
  560. begin
  561. StartThreads;
  562. WaitForThreads;
  563. end;
  564. end;
  565. end;
  566. var
  567. Application : TDBDigestApplication;
  568. begin
  569. Application:=TDBDigestApplication.Create(Nil);
  570. Application.Initialize;
  571. Application.Run;
  572. Application.Free;
  573. end.