consoletestrunner.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. { This unit contains the TTestRunner class, a base class for the console test
  2. runner for fpcunit.
  3. Copyright (C) 2006 Vincent Snijders
  4. This library is free software; you can redistribute it and/or modify it
  5. under the terms of the GNU Library General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or (at your
  7. option) any later version.
  8. This program is distributed in the hope that it will be useful, but WITHOUT
  9. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  11. for more details.
  12. You should have received a copy of the GNU Library General Public License
  13. along with this library; if not, write to the Free Software Foundation,
  14. Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  15. }
  16. unit consoletestrunner;
  17. {$mode objfpc}{$H+}
  18. interface
  19. uses
  20. custapp, Classes, SysUtils, fpcunit, testregistry, testutils,
  21. fpcunitreport, latextestreport, xmltestreport, plaintestreport,
  22. dom;
  23. const
  24. Version = '0.3';
  25. type
  26. TFormat = (fPlain, fLatex, fXML, fPlainNoTiming);
  27. TRunMode = (rmUnknown,rmList,rmSuite,rmAll);
  28. var
  29. DefaultFormat : TFormat = fXML;
  30. DefaultRunAllTests : Boolean = False;
  31. type
  32. { TTestRunner }
  33. TTestRunner = class(TCustomApplication)
  34. private
  35. FShowProgress: boolean;
  36. FFileName: string;
  37. FStyleSheet: string;
  38. FLongOpts: TStrings;
  39. FFormatParam: TFormat;
  40. FSkipTiming : Boolean;
  41. FSParse: Boolean;
  42. FSkipAddressInfo : Boolean;
  43. FSuite: String;
  44. FRunMode : TRunMode;
  45. protected
  46. Class function StrToFormat(S: String): TFormat;
  47. function DefaultsFileName: String;
  48. procedure RunSuite; virtual;
  49. procedure ShowTestList; virtual;
  50. procedure ReadDefaults; virtual;
  51. procedure Usage; virtual;
  52. property FileName: string read FFileName write FFileName;
  53. property LongOpts: TStrings read FLongOpts write FLongOpts;
  54. property ShowProgress: boolean read FShowProgress write FShowProgress;
  55. property StyleSheet: string read FStyleSheet write FStyleSheet;
  56. property FormatParam: TFormat read FFormatParam write FFormatParam;
  57. procedure DoRun; override;
  58. procedure DoTestRun(ATest: TTest); virtual;
  59. function GetShortOpts: string; virtual;
  60. procedure AppendLongOpts; virtual;
  61. procedure WriteCustomHelp; virtual;
  62. function ParseOptions: Boolean; virtual;
  63. procedure ExtendXmlDocument(Doc: TXMLDocument); virtual;
  64. function GetResultsWriter: TCustomResultsWriter; virtual;
  65. public
  66. constructor Create(AOwner: TComponent); override;
  67. destructor Destroy; override;
  68. end;
  69. implementation
  70. uses inifiles, testdecorator;
  71. const
  72. ShortOpts = 'alhp';
  73. DefaultLongOpts: array[1..11] of string =
  74. ('all', 'list', 'progress', 'help', 'skiptiming',
  75. 'suite:', 'format:', 'file:', 'stylesheet:','sparse','no-addresses');
  76. Type
  77. TTestDecoratorClass = Class of TTestDecorator;
  78. { TDecoratorTestSuite }
  79. TDecoratorTestSuite = Class(TTestSuite)
  80. public
  81. Destructor Destroy; override;
  82. end;
  83. { TProgressWriter }
  84. TProgressWriter= class(TNoRefCountObject, ITestListener)
  85. private
  86. FTotal : Integer;
  87. FFailed: Integer;
  88. FIgnored : Integer;
  89. FErrors : Integer;
  90. FQuiet : Boolean;
  91. FSuccess : Boolean;
  92. procedure WriteChar(c: char);
  93. public
  94. Constructor Create(AQuiet : Boolean);
  95. destructor Destroy; override;
  96. Function GetExitCode : Integer;
  97. { ITestListener interface requirements }
  98. procedure AddFailure(ATest: TTest; AFailure: TTestFailure);
  99. procedure AddError(ATest: TTest; AError: TTestFailure);
  100. procedure StartTest(ATest: TTest);
  101. procedure EndTest(ATest: TTest);
  102. procedure StartTestSuite(ATestSuite: TTestSuite);
  103. procedure EndTestSuite(ATestSuite: TTestSuite);
  104. Property Total : Integer Read FTotal;
  105. Property Failed : Integer Read FFailed;
  106. Property Errors : Integer Read FErrors;
  107. Property Ignored : Integer Read FIgnored;
  108. Property Quiet : Boolean Read FQuiet;
  109. end;
  110. { ---------------------------------------------------------------------
  111. TProgressWriter
  112. ---------------------------------------------------------------------}
  113. procedure TProgressWriter.WriteChar(c: char);
  114. begin
  115. write(c);
  116. // flush output, so that we see the char immediately, even it is written to file
  117. Flush(output);
  118. end;
  119. constructor TProgressWriter.Create(AQuiet: Boolean);
  120. begin
  121. FQuiet:=AQuiet;
  122. end;
  123. destructor TProgressWriter.Destroy;
  124. begin
  125. // on descruction, just write the missing line ending
  126. writeln;
  127. inherited Destroy;
  128. end;
  129. function TProgressWriter.GetExitCode: Integer;
  130. begin
  131. Result:=Ord(Failed<>0); // Bit 0 indicates fails
  132. if Errors<>0 then
  133. Result:=Result or 2; // Bit 1 indicates errors.
  134. end;
  135. procedure TProgressWriter.AddFailure(ATest: TTest; AFailure: TTestFailure);
  136. begin
  137. FSuccess:=False;
  138. If AFailure.IsIgnoredTest then
  139. Inc(FIgnored)
  140. else
  141. Inc(FFailed);
  142. If Not Quiet then
  143. writechar('F');
  144. end;
  145. procedure TProgressWriter.AddError(ATest: TTest; AError: TTestFailure);
  146. begin
  147. FSuccess:=False;
  148. Inc(FErrors);
  149. if not Quiet then
  150. writechar('E');
  151. end;
  152. procedure TProgressWriter.StartTest(ATest: TTest);
  153. begin
  154. FSuccess := true; // assume success, until proven otherwise
  155. end;
  156. procedure TProgressWriter.EndTest(ATest: TTest);
  157. begin
  158. if FSuccess and not Quiet then
  159. writechar('.');
  160. end;
  161. procedure TProgressWriter.StartTestSuite(ATestSuite: TTestSuite);
  162. begin
  163. // do nothing
  164. end;
  165. procedure TProgressWriter.EndTestSuite(ATestSuite: TTestSuite);
  166. begin
  167. // do nothing
  168. end;
  169. { ---------------------------------------------------------------------
  170. TDecoratorTestSuite
  171. ---------------------------------------------------------------------}
  172. destructor TDecoratorTestSuite.Destroy;
  173. begin
  174. OwnsTests:=False;
  175. inherited Destroy;
  176. end;
  177. { ---------------------------------------------------------------------
  178. TTestRunner
  179. ---------------------------------------------------------------------}
  180. constructor TTestRunner.Create(AOwner: TComponent);
  181. begin
  182. inherited Create(AOwner);
  183. FLongOpts := TStringList.Create;
  184. AppendLongOpts;
  185. StopOnException:=True;
  186. end;
  187. destructor TTestRunner.Destroy;
  188. begin
  189. FLongOpts.Free;
  190. inherited Destroy;
  191. end;
  192. class function TTestRunner.StrToFormat(S: String): TFormat;
  193. begin
  194. Case lowercase(S) of
  195. 'latex': Result:=fLatex;
  196. 'plain': Result:=fPlain;
  197. 'plainnotiming': Result:=fPlainNoTiming;
  198. 'xml': Result:=fXML;
  199. else
  200. Raise EConvertError.CreateFmt('Not a valid output format : "%s"',[S]);
  201. end;
  202. end;
  203. function TTestRunner.GetResultsWriter: TCustomResultsWriter;
  204. begin
  205. case FormatParam of
  206. fLatex: Result := TLatexResultsWriter.Create(nil);
  207. fPlain: Result := TPlainResultsWriter.Create(nil);
  208. fPlainNotiming: Result := TPlainResultsWriter.Create(nil);
  209. else
  210. begin
  211. Result := TXmlResultsWriter.Create(nil);
  212. ExtendXmlDocument(TXMLResultsWriter(Result).Document);
  213. end;
  214. end;
  215. Result.SkipTiming:=FSkipTiming or (formatParam=fPlainNoTiming);
  216. Result.Sparse:=FSparse;
  217. Result.SkipAddressInfo:=FSkipAddressInfo;
  218. end;
  219. procedure TTestRunner.DoTestRun(ATest: TTest);
  220. var
  221. ResultsWriter: TCustomResultsWriter;
  222. ProgressWriter: TProgressWriter;
  223. TestResult: TTestResult;
  224. begin
  225. ProgressWriter:=Nil;
  226. ResultsWriter:=Nil;
  227. TestResult := TTestResult.Create;
  228. try
  229. ProgressWriter:=TProgressWriter.Create(Not ShowProgress);
  230. TestResult.AddListener(ProgressWriter);
  231. ResultsWriter:=GetResultsWriter;
  232. ResultsWriter.Filename := FileName;
  233. TestResult.AddListener(ResultsWriter);
  234. ATest.Run(TestResult);
  235. ResultsWriter.WriteResult(TestResult);
  236. finally
  237. if Assigned(ProgressWriter) then
  238. ExitCode:=ProgressWriter.GetExitCode;
  239. TestResult.Free;
  240. ResultsWriter.Free;
  241. ProgressWriter.Free;
  242. end;
  243. end;
  244. function TTestRunner.GetShortOpts: string;
  245. begin
  246. Result := ShortOpts;
  247. end;
  248. procedure TTestRunner.AppendLongOpts;
  249. var
  250. i: Integer;
  251. begin
  252. for i := low(DefaultLongOpts) to high(DefaultLongOpts) do
  253. LongOpts.Add(DefaultLongOpts[i]);
  254. end;
  255. procedure TTestRunner.WriteCustomHelp;
  256. begin
  257. // no custom help options in base class;
  258. end;
  259. procedure TTestRunner.Usage;
  260. begin
  261. writeln(Title);
  262. writeln(Version);
  263. writeln;
  264. writeln('Usage: ');
  265. writeln(' --format=FMT Select output format. FMT is one of:');
  266. writeln(' latex output as latex');
  267. writeln(' plain output as plain ASCII source');
  268. writeln(' plainnotiming output as plain ASCII source, skip timings');
  269. writeln(' xml output as XML source (default)');
  270. writeln(' --skiptiming Do not output timings (useful for diffs of testruns)');
  271. writeln(' --sparse Produce Less output (errors/failures only)');
  272. writeln(' --no-addresses Do not display address info');
  273. writeln(' --stylesheet=<reference> add stylesheet reference');
  274. writeln(' --file=<filename> output results to file');
  275. writeln;
  276. writeln(' -l or --list show a list of registered tests');
  277. writeln(' -a or --all run all tests');
  278. writeln(' -p or --progress show progress');
  279. writeln(' --suite=MyTestSuiteName run single test suite class');
  280. WriteCustomHelp;
  281. writeln;
  282. Writeln('Defaults for long options will be read from ini file ',DefaultsFileName);
  283. writeln('The results can be redirected to a file,');
  284. writeln('for example: ', ParamStr(0),' --all > results.xml');
  285. end;
  286. Function TTestRunner.DefaultsFileName : String;
  287. begin
  288. Result:=GetEnvironmentVariable('FPCUNITCONFIG');
  289. if (Result='') then
  290. Result:=Location+'testdefaults.ini';
  291. end;
  292. procedure TTestRunner.ReadDefaults;
  293. Const
  294. S = 'defaults';
  295. Var
  296. Ini : TMemIniFile;
  297. FN,F : String;
  298. begin
  299. FN:=DefaultsFileName;
  300. if FileExists(FN) then
  301. begin
  302. Ini:=TMemIniFile.Create(FN);
  303. try
  304. F:=Ini.ReadString(S,'format','');
  305. if (F<>'') then
  306. FormatParam:=StrToFormat(F);
  307. FileName:=Ini.ReadString(S,'file',FileName);
  308. StyleSheet:=Ini.ReadString(S,'stylesheet',StyleSheet);
  309. ShowProgress:=Ini.ReadBool(S,'progress',ShowProgress);
  310. FSkipTiming:=Ini.ReadBool(S,'skiptiming',FSKipTiming);
  311. FSparse:=Ini.ReadBool(S,'sparse',FSparse);
  312. FSkipAddressInfo:=Ini.ReadBool(S,'no-addresses',FSkipAddressInfo);
  313. // Determine runmode
  314. FSuite:=Ini.ReadString(S,'suite','');
  315. if (FSuite<>'') then
  316. FRunMode:=rmSuite
  317. else if Ini.ReadBool(S,'all', false) then
  318. FRunMode:=rmAll
  319. else if Ini.ReadBool(S,'list',False) then
  320. FRunMode:=rmList;
  321. finally
  322. Ini.Free;
  323. end;
  324. end;
  325. end;
  326. Function TTestRunner.ParseOptions : Boolean;
  327. begin
  328. Result:=True;
  329. if HasOption('h', 'help') or ((ParamCount = 0) and (FRunMode<>rmAll)) then
  330. begin
  331. Usage;
  332. if not HasOption('h','help') then
  333. ExitCode:=1;
  334. Exit(False);
  335. end;
  336. //get the format parameter
  337. if HasOption('format') then
  338. FormatParam:=StrToFormat(GetOptionValue('format'));
  339. if HasOption('file') then
  340. FileName:=GetOptionValue('file');
  341. if HasOption('stylesheet') then
  342. StyleSheet:=GetOptionValue('stylesheet');
  343. if HasOption('p', 'progress') then
  344. ShowProgress:=True;
  345. if HasOption('skiptiming') then
  346. FSkipTiming:=True;
  347. if HasOption('sparse') then
  348. FSparse:=True;
  349. If HasOption('no-addresses') then
  350. FSkipAddressInfo:=True;
  351. // Determine runmode
  352. if HasOption('suite') then
  353. begin
  354. FSuite:=GetOptionValue('suite');
  355. FRunMode:=rmSuite;
  356. end
  357. else If HasOption('a','all') then
  358. FRunMode:=rmAll
  359. else if HasOption('l','list') then
  360. FRunMode:=rmList;
  361. end;
  362. procedure TTestRunner.ExtendXmlDocument(Doc: TXMLDocument);
  363. var
  364. n: TDOMElement;
  365. begin
  366. if StyleSheet<>'' then begin
  367. Doc.StylesheetType := 'text/xsl';
  368. Doc.StylesheetHRef := StyleSheet;
  369. end;
  370. n := Doc.CreateElement('Title');
  371. n.AppendChild(Doc.CreateTextNode(Title));
  372. Doc.FirstChild.AppendChild(n);
  373. end;
  374. procedure TTestRunner.RunSuite;
  375. var
  376. I,P : integer;
  377. S,TN : string;
  378. TS : TDecoratorTestSuite;
  379. T : TTest;
  380. begin
  381. S := FSuite;
  382. if S = '' then
  383. for I := 0 to GetTestRegistry.ChildTestCount - 1 do
  384. writeln(GetTestRegistry[i].TestName)
  385. else
  386. begin
  387. TS:=TDecoratorTestSuite.Create('SuiteList');
  388. try
  389. while Not(S = '') Do
  390. begin
  391. P:=Pos(',',S);
  392. If P=0 then
  393. P:=Length(S)+1;
  394. TN:=Copy(S,1,P-1);
  395. Delete(S,1,P);
  396. if (TN<>'') then
  397. begin
  398. T:=GetTestRegistry.FindTest(TN);
  399. if Assigned(T) then
  400. TS.AddTest(T);
  401. end;
  402. end;
  403. if (TS.CountTestCases>1) then
  404. DoTestRun(TS)
  405. else if TS.CountTestCases=1 then
  406. DoTestRun(TS[0])
  407. else
  408. Writeln('No tests selected.');
  409. finally
  410. TS.Free;
  411. end;
  412. end;
  413. end;
  414. procedure TTestRunner.ShowTestList;
  415. begin
  416. case FormatParam of
  417. fLatex: Write(GetSuiteAsLatex(GetTestRegistry));
  418. fPlain: Write(GetSuiteAsPlain(GetTestRegistry));
  419. fPlainNoTiming: Write(GetSuiteAsPlain(GetTestRegistry));
  420. else
  421. Write(GetSuiteAsXml(GetTestRegistry));
  422. end
  423. end;
  424. procedure TTestRunner.DoRun;
  425. var
  426. S : string;
  427. begin
  428. Terminate;
  429. FormatParam := DefaultFormat;
  430. If DefaultRunAllTests then
  431. FRunMode:=rmAll;
  432. S := CheckOptions(GetShortOpts, LongOpts);
  433. if (S <> '') then
  434. begin
  435. Writeln(S);
  436. Exit;
  437. end;
  438. ReadDefaults;
  439. if Not ParseOptions then
  440. exit;
  441. //get a list of all registed tests
  442. Case FRunMode of
  443. rmList: ShowTestList;
  444. rmSuite: RunSuite;
  445. rmAll: DoTestRun(GetTestRegistry);
  446. else
  447. Usage
  448. end;
  449. end;
  450. end.