dbdigest.pp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  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'+ { ThreadList }
  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. 'threadlist',
  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. Result:=EncodeDate(year,month,day)+EncodeTime(hour,min,0,0);
  189. end
  190. else
  191. Verbose(V_Error,'Error in date format, use YYYYMMDDhhmm');
  192. end;
  193. function TDBDigestApplication.ProcessOption(const aOption: String; aValue: String; var aConfig: TDigestConfig;
  194. var aData: TTestRunData): Boolean;
  195. begin
  196. Result:=True;
  197. Verbose(V_DEBUG,'Processing option: '+aOption);
  198. Case aOption of
  199. 'd','databasename' : aConfig.databasename:=aValue;
  200. 'h','host' : aConfig.host:=aValue;
  201. 'u','username': aConfig.username:=aValue;
  202. 'p','password': aConfig.password:=aValue;
  203. 'P','port': aConfig.port:=StrToIntDef(aValue,0);
  204. 'l','logfile': aData.logfile:=aValue;
  205. 'L','longlogfile': aData.longlogfile:=aValue;
  206. 'o','os': aData.os:=aValue;
  207. 'c','cpu': aData.cpu:=aValue;
  208. 'a','category': aData.category:=aValue;
  209. 'v','version': aData.version:=aValue;
  210. 't','date': aData.date:=ExtractDate(aValue);
  211. 's','submitter': aData.submitter:=aValue;
  212. 'm','machine': aData.machine:=aValue;
  213. 'C','comment': aData.config:=aValue;
  214. 'D','description': aData.description:=aValue;
  215. 'S','testsrcdir': aConfig.testsrcdir:=aValue;
  216. 'r','relsrcdir': aConfig.relsrcdir:=aValue;
  217. 'V','verbose': DoVerbose:=True;
  218. 'sql': dosql:=true;
  219. 'T','tasklist' : ; // treated elsewhere
  220. 'j','threadcount' : ; // treated elsewhere
  221. 'compilerdate': aData.CompilerDate:=aValue;
  222. 'compilerfullversion': aData.CompilerFullVersion:=aValue;
  223. 'svncompilerrevision': aData.CompilerRevision:=aValue;
  224. 'svntestsrevision': aData.TestsRevision:=aValue;
  225. 'svnrtlrevision': aData.RTLRevision:=aValue;
  226. 'svnpackagesrevision' : aData.PackagesRevision:=aValue;
  227. else
  228. Verbose(V_ERROR,'Unknown processing option: '+aOption);
  229. end;
  230. end;
  231. procedure TDBDigestApplication.ProcessConfigfile(const aFileName: String; var aConfig: TDigestConfig; var aData: TTestRunData);
  232. Var
  233. Cfg : TStrings;
  234. aLine,S,N,V : String;
  235. I : Integer;
  236. begin
  237. // Set the default value for old digests without RelSrcDir to the rtl/compiler
  238. // testsuite
  239. If Not FileExists(aFileName) Then
  240. Exit;
  241. Verbose(V_DEBUG,'Parsing config file: '+aFileName);
  242. Cfg:=TStringList.Create;
  243. try
  244. Cfg.LoadFromFile(aFileName);
  245. For aLine in Cfg do
  246. begin
  247. S:=Trim(aLine);
  248. I:=Pos('#',S);
  249. If I<>0 then
  250. S:=Copy(S,1,I-1);
  251. If (S<>'') then
  252. begin
  253. I:=Pos('=',S);
  254. if (I=0) then
  255. Verbose(V_ERROR,'Unknown processing option: '+S)
  256. else
  257. begin
  258. N:=LowerCase(Copy(S,1,I-1));
  259. V:=Copy(S,I+1,Length(S)-I);
  260. ProcessOption(N,V,aConfig,aData);
  261. end;
  262. end;
  263. end;
  264. finally
  265. Cfg.Free;
  266. end;
  267. end;
  268. { TDBDigestApplication }
  269. procedure TDBDigestApplication.Usage(const aMsg: String);
  270. begin
  271. if (aMsg<>'') then
  272. Writeln('Error : ',aMsg);
  273. Writeln('Usage: ',ExeName,' [options] [test run data options]');
  274. Writeln('Configuration options:');
  275. Writeln('-H --help show this help');
  276. Writeln('-d --databasename=NAME database name');
  277. Writeln('-f --config=FILENAME config file. If not set, dbdigest.cfg is used.');
  278. Writeln('-h --host=HOST database hostname');
  279. Writeln('-p --password=PWD database user password');
  280. Writeln('-P --port=NNN database connection port');
  281. Writeln('-r --relsrcdir relative source dir');
  282. Writeln('-S --testsrcdir test source dir');
  283. Writeln('-u --username=USER database user name');
  284. Writeln('-T --tasklist=FILE file with configuration file names to imports.');
  285. Writeln('-j --threadcount=N Number of threads to use');
  286. Writeln('-V --verbose be more verbose');
  287. Writeln('Test run data:');
  288. Writeln('-l --logfile=FILE set log file to analyse');
  289. Writeln('-L --longlogfile=FILE set long log filename (logs of run tests)');
  290. Writeln('-o --os=OS set OS for testrun');
  291. Writeln('-c --cpu=CPU set CPU');
  292. Writeln('-a --category=CAT set category');
  293. Writeln('-v --version=VER set compiler version');
  294. Writeln('-t --date=DATE date in YYYMMDD(hhmmnn) format');
  295. Writeln('-s --submitter=NAME submitter name');
  296. Writeln('-m --machine=NAME set machine name on which testsuite was run');
  297. Writeln('-C --compile-flags=FLAGS set used compilation flags');
  298. Writeln(' --comment=FLAGS backwards compatible way to set compilation flags (deprecated)');
  299. Writeln('-D --description=DESC set config description (helpful comment)');
  300. Writeln(' --compilerdate=DATE set compiler date');
  301. Writeln(' --compilerfullversion=VERSION set full compiler version');
  302. Writeln(' --svncompilerrevision=REV set revision of used compiler');
  303. Writeln(' --svntestsrevision=REV set revision of testsuite files');
  304. Writeln(' --svnrtlrevision=REV set revision of RTL');
  305. Writeln(' --svnpackagesrevision=REV set revison of packages');
  306. Writeln('');
  307. Writeln('If -T is specified, no test run options may be specified');
  308. Writeln('');
  309. Writeln('The config file can contain the same options as the command-line in the form.');
  310. Writeln('option=value');
  311. Writeln('where option is the long or short version of the option');
  312. Writeln('comments may be included using the # character.');
  313. ExitCode:=Ord(aMsg<>'');
  314. end;
  315. constructor TDBDigestApplication.Create(aOwner: TComponent);
  316. begin
  317. inherited Create(aOwner);
  318. FTasks:=TThreadList.Create;
  319. end;
  320. function TDBDigestApplication.ProcessCommandLine(var aConfig: TDigestConfig; var aData : TTestRunData): Boolean;
  321. Function MakeOpts(s : string) : string;
  322. var
  323. C : char;
  324. begin
  325. Result:='';
  326. For C in s do
  327. begin
  328. Result:=Result+C;
  329. if not (C in ['V','Q']) then
  330. Result:=Result+':';
  331. end;
  332. end;
  333. Function MakeLongOpts(s : array of string) : TStringDynArray;
  334. var
  335. I : Integer;
  336. begin
  337. Result:=['help'];
  338. SetLength(Result,1+Length(S));
  339. For I:=0 to Length(S)-1 do
  340. begin
  341. Result[1+I]:=S[I];
  342. if (S[I]<>'verbose') and (S[I]<>'sql') then
  343. Result[1+I]:=Result[1+I]+':';
  344. end;
  345. end;
  346. var
  347. Long,ErrMsg,lValue : String;
  348. Short : Char;
  349. I : integer;
  350. lHas : boolean;
  351. begin
  352. ErrMsg:=CheckOptions(MakeOpts(ShortOpts)+'H',MakeLongOpts(LongOpts));
  353. Result:=(ErrMsg='');
  354. if (not Result) or HasOption('H','help') then
  355. begin
  356. Usage(ErrMsg);
  357. Exit(false);
  358. end;
  359. I:=0;
  360. For Long in LongOpts do
  361. begin
  362. Inc(I);
  363. if I<=Length(ShortOpts) then
  364. begin
  365. Short:=ShortOpts[I];
  366. if Short='r' then
  367. Writeln('ag');
  368. lHas:=HasOption(Short,Long);
  369. lValue:=GetOptionValue(Short,Long);
  370. end
  371. else
  372. begin
  373. Short:=#0;
  374. lHas:=HasOption(Long);
  375. lValue:=GetOptionValue(Long);
  376. end;
  377. if lHas then
  378. ProcessOption(long,lValue,aConfig,aData);
  379. end;
  380. Result:=True;
  381. end;
  382. procedure TDBDigestApplication.Analyze(const aConfig : TDigestConfig; const aData : TTestRunData);
  383. var
  384. lSQL : TTestSQL;
  385. lDigest : TDBDigestAnalyzer;
  386. lPrefix : string;
  387. begin
  388. lDigest:=Nil;
  389. With aConfig do
  390. lSQL:=TTestSQL.create(databasename,host,username,password,port);
  391. try
  392. lSQL.ConnectToDatabase;
  393. if GetCurrentThreadId<>MainThreadID then
  394. lPrefix:='['+IntToStr(PtrInt(GetCurrentThreadId))+' - '+aData.logfile+']: '
  395. else
  396. lPrefix:='';
  397. lSQL.LogPrefix:=lPrefix;
  398. lDigest:=TDBDigestAnalyzer.Create(lSQL,lPrefix);
  399. lDigest.Analyse(aConfig,aData);
  400. finally
  401. lDigest.Free;
  402. lSQL.Free;
  403. end;
  404. end;
  405. procedure TDBDigestApplication.ReadSystemDBConfig(var aConfig : TDigestConfig);
  406. // Keep filename in sync with algorithm in dbadd
  407. var
  408. lFileName : String;
  409. Ini : TCustomIniFile;
  410. begin
  411. lFileName:='/etc/dbdigest.ini';
  412. if not FileExists(lFileName) then exit;
  413. Ini:=TMemIniFile.Create(lFileName);
  414. With Ini do
  415. try
  416. aConfig.DatabaseName:=ReadString(SSection,KeyName,'testsuite');
  417. aConfig.Host:=ReadString(SSection,KeyHost,'localhost');
  418. aConfig.UserName:=ReadString(SSection,KeyUser,'');
  419. aConfig.Password:=ReadString(SSection,KeyPassword,'');
  420. aConfig.Port:=ReadInteger(SSection,KeyPort,0);
  421. finally
  422. Ini.Free;
  423. end;
  424. end;
  425. function TDBDigestApplication.CheckConfigFiles(lCfg : String; var lData : TTestRunData) : Boolean;
  426. function CheckFile(const aDir : String; var aFile : String) : boolean;
  427. var
  428. lExpanded : string;
  429. begin
  430. if (aFile<>'') and (aFile[1]<>'/') then
  431. begin
  432. lExpanded:=aDir+aFile;
  433. Verbose(V_Debug,Format('Expanding file from %s to %s',[aFile,lExpanded]));
  434. aFile:=lExpanded;
  435. end;
  436. Result:=FileExists(aFile);
  437. if not Result then
  438. Verbose(V_Warning,Format('file does not exist: %s',[lExpanded]));
  439. end;
  440. var
  441. lDir : String;
  442. begin
  443. lDir:=ExtractFilePath(ExpandFileName(lCfg));
  444. Result:=CheckFile(lDir,lData.logfile);
  445. if Result then
  446. Result:=CheckFile(lDir,lData.longlogfile);
  447. end;
  448. function TDBDigestApplication.CreateTaskList(const aBaseConfig: TDigestConfig; const aBaseData: TTestRunData) : boolean;
  449. var
  450. lCfg,lFileName : String;
  451. L : TStrings;
  452. lConfig : TDigestConfig;
  453. lData : TTestRunData;
  454. lList : TList;
  455. begin
  456. Result:=False;
  457. lFileName:=GetOptionValue('T','tasklist');
  458. if not FileExists(lFileName) then
  459. begin
  460. Verbose(V_Normal,'No such file :'+lFileName);
  461. Exit;
  462. end;
  463. L:=TStringList.Create;
  464. try
  465. l.LoadFromFile(lFileName);
  466. Result:=True;
  467. For lcfg in L do
  468. begin
  469. if not FileExists(lCfg) then
  470. begin
  471. Verbose(V_Warning,'No such file: '+lcfg);
  472. Result:=False;
  473. end
  474. else
  475. begin
  476. lConfig:=aBaseConfig;
  477. lData:=aBaseData;
  478. lList:=FTasks.LockList;
  479. ProcessConfigfile(lCfg,lConfig,lData);
  480. if CheckConfigFiles(lCfg,lData) then
  481. lList.Add(TThreadTask.Create(lCfg,lConfig,lData))
  482. else
  483. Result:=False;
  484. end;
  485. end;
  486. finally
  487. l.Free;
  488. end;
  489. end;
  490. procedure TDBDigestApplication.TaskDone(Sender: TObject);
  491. begin
  492. InterlockedDecrement(FThreadCount);
  493. StartThreads;
  494. end;
  495. Procedure TDBDigestApplication.StartThreads;
  496. var
  497. L : TList;
  498. lTask : TThreadTask;
  499. begin
  500. L:=FTasks.LockList;
  501. try
  502. Verbose(V_DEBUG,Format('Starting tasks. Current thread count: %d remaining tasks: %d.',[FThreadCount,l.Count]));
  503. While (L.Count>0) and (FThreadCount<FMaxThreads) do
  504. begin
  505. lTask:=TThreadTask(L[0]);
  506. L.Delete(0);
  507. Verbose(V_DEBUG,'Starting task for '+lTask.CfgFileName);
  508. TProcessFileThread.Create(Self,lTask,@TaskDone);
  509. InterlockedIncrement(FThreadCount);
  510. end;
  511. finally
  512. FTasks.UnlockList;
  513. end;
  514. end;
  515. procedure TDBDigestApplication.WaitForThreads;
  516. var
  517. lDone : Boolean;
  518. lList : TList;
  519. begin
  520. Repeat
  521. CheckSynchronize;
  522. Sleep(100);
  523. lList:=FTasks.LockList;
  524. try
  525. Verbose(V_DEBUG,Format('Waiting...(Todo: %d threads: %d)',[lList.Count,FThreadCount]));
  526. lDone:=(lList.Count=0) and (FThreadCount=0);
  527. finally
  528. FTasks.UnlockList;
  529. end
  530. until ldone;
  531. end;
  532. procedure TDBDigestApplication.DoRun;
  533. var
  534. lConfigFile : String;
  535. lConfig : TDigestConfig;
  536. lData : TTestRunData;
  537. begin
  538. Terminate;
  539. lConfigFile:=GetOptionValue('f','config');
  540. if lConfigFile='' then
  541. lConfigFile:='dbdigest.cfg';
  542. lConfig:=Default(TDigestConfig);
  543. lConfig.RelSrcDir:='tests/';
  544. ReadSystemDBConfig(lConfig);
  545. if not HasOption('T','threadlist') then
  546. begin
  547. lData:=Default(TTestRunData);
  548. ProcessConfigFile(lConfigFile,lConfig,lData);
  549. if ProcessCommandLine(lConfig,lData) then
  550. Analyze(lConfig,lData);
  551. end
  552. else
  553. begin
  554. FMaxThreads:=StrToIntDef(GetOptionValue('j','threadcount'),4);
  555. if ProcessCommandLine(lConfig,lData) then
  556. if CreateTaskList(lConfig,lData) then
  557. begin
  558. StartThreads;
  559. WaitForThreads;
  560. end;
  561. end;
  562. end;
  563. var
  564. Application : TDBDigestApplication;
  565. begin
  566. Application:=TDBDigestApplication.Create(Nil);
  567. Application.Initialize;
  568. Application.Run;
  569. Application.Free;
  570. end.