consoletestrunner.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  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. junittestreport, dom;
  23. const
  24. Version = '0.3';
  25. type
  26. TFormat = (fPlain, fLatex, fXML, fPlainNoTiming, fJUnit);
  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(const 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(const 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. 'junit': Result:=fJUnit;
  200. else
  201. Raise EConvertError.CreateFmt('Not a valid output format : "%s"',[S]);
  202. end;
  203. end;
  204. function TTestRunner.GetResultsWriter: TCustomResultsWriter;
  205. begin
  206. case FormatParam of
  207. fLatex: Result := TLatexResultsWriter.Create(nil);
  208. fPlain: Result := TPlainResultsWriter.Create(nil);
  209. fPlainNotiming: Result := TPlainResultsWriter.Create(nil);
  210. fJUnit: Result := TJUnitResultsWriter.Create(nil)
  211. else
  212. begin
  213. Result := TXmlResultsWriter.Create(nil);
  214. ExtendXmlDocument(TXMLResultsWriter(Result).Document);
  215. end;
  216. end;
  217. Result.SkipTiming:=FSkipTiming or (formatParam=fPlainNoTiming);
  218. Result.Sparse:=FSparse;
  219. Result.SkipAddressInfo:=FSkipAddressInfo;
  220. end;
  221. procedure TTestRunner.DoTestRun(ATest: TTest);
  222. var
  223. ResultsWriter: TCustomResultsWriter;
  224. ProgressWriter: TProgressWriter;
  225. TestResult: TTestResult;
  226. begin
  227. ProgressWriter:=Nil;
  228. ResultsWriter:=Nil;
  229. TestResult := TTestResult.Create;
  230. try
  231. ProgressWriter:=TProgressWriter.Create(Not ShowProgress);
  232. TestResult.AddListener(ProgressWriter);
  233. ResultsWriter:=GetResultsWriter;
  234. ResultsWriter.Filename := FileName;
  235. TestResult.AddListener(ResultsWriter);
  236. ATest.Run(TestResult);
  237. ResultsWriter.WriteResult(TestResult);
  238. finally
  239. if Assigned(ProgressWriter) then
  240. ExitCode:=ProgressWriter.GetExitCode;
  241. TestResult.Free;
  242. ResultsWriter.Free;
  243. ProgressWriter.Free;
  244. end;
  245. end;
  246. function TTestRunner.GetShortOpts: string;
  247. begin
  248. Result := ShortOpts;
  249. end;
  250. procedure TTestRunner.AppendLongOpts;
  251. var
  252. i: Integer;
  253. begin
  254. for i := low(DefaultLongOpts) to high(DefaultLongOpts) do
  255. LongOpts.Add(DefaultLongOpts[i]);
  256. end;
  257. procedure TTestRunner.WriteCustomHelp;
  258. begin
  259. // no custom help options in base class;
  260. end;
  261. procedure TTestRunner.Usage;
  262. begin
  263. writeln(Title);
  264. writeln(Version);
  265. writeln;
  266. writeln('Usage: ');
  267. writeln(' --format=FMT Select output format. FMT is one of:');
  268. writeln(' latex output as latex');
  269. writeln(' plain output as plain ASCII source');
  270. writeln(' plainnotiming output as plain ASCII source, skip timings');
  271. writeln(' xml output as XML source (default)');
  272. writeln(' junit output as JUnit compatible XML source');
  273. writeln(' --skiptiming Do not output timings (useful for diffs of testruns)');
  274. writeln(' --sparse Produce Less output (errors/failures only)');
  275. writeln(' --no-addresses Do not display address info');
  276. writeln(' --stylesheet=<reference> add stylesheet reference');
  277. writeln(' --file=<filename> output results to file');
  278. writeln;
  279. writeln(' -l or --list show a list of registered tests');
  280. writeln(' -a or --all run all tests');
  281. writeln(' -p or --progress show progress');
  282. writeln(' --suite=MyTestSuiteName run single test suite class');
  283. WriteCustomHelp;
  284. writeln;
  285. Writeln('Defaults for long options will be read from ini file ',DefaultsFileName);
  286. writeln('The results can be redirected to a file,');
  287. writeln('for example: ', ParamStr(0),' --all > results.xml');
  288. end;
  289. Function TTestRunner.DefaultsFileName : String;
  290. begin
  291. Result:=GetEnvironmentVariable('FPCUNITCONFIG');
  292. if (Result='') then
  293. Result:=Location+'testdefaults.ini';
  294. end;
  295. procedure TTestRunner.ReadDefaults;
  296. Const
  297. S = 'defaults';
  298. Var
  299. Ini : TMemIniFile;
  300. FN,F : String;
  301. begin
  302. FN:=DefaultsFileName;
  303. if FileExists(FN) then
  304. begin
  305. Ini:=TMemIniFile.Create(FN);
  306. try
  307. F:=Ini.ReadString(S,'format','');
  308. if (F<>'') then
  309. FormatParam:=StrToFormat(F);
  310. FileName:=Ini.ReadString(S,'file',FileName);
  311. StyleSheet:=Ini.ReadString(S,'stylesheet',StyleSheet);
  312. ShowProgress:=Ini.ReadBool(S,'progress',ShowProgress);
  313. FSkipTiming:=Ini.ReadBool(S,'skiptiming',FSKipTiming);
  314. FSparse:=Ini.ReadBool(S,'sparse',FSparse);
  315. FSkipAddressInfo:=Ini.ReadBool(S,'no-addresses',FSkipAddressInfo);
  316. // Determine runmode
  317. FSuite:=Ini.ReadString(S,'suite','');
  318. if (FSuite<>'') then
  319. FRunMode:=rmSuite
  320. else if Ini.ReadBool(S,'all', false) then
  321. FRunMode:=rmAll
  322. else if Ini.ReadBool(S,'list',False) then
  323. FRunMode:=rmList;
  324. finally
  325. Ini.Free;
  326. end;
  327. end;
  328. end;
  329. Function TTestRunner.ParseOptions : Boolean;
  330. begin
  331. Result:=True;
  332. if HasOption('h', 'help') or ((ParamCount = 0) and (FRunMode<>rmAll)) then
  333. begin
  334. Usage;
  335. if not HasOption('h','help') then
  336. ExitCode:=1;
  337. Exit(False);
  338. end;
  339. //get the format parameter
  340. if HasOption('format') then
  341. FormatParam:=StrToFormat(GetOptionValue('format'));
  342. if HasOption('file') then
  343. FileName:=GetOptionValue('file');
  344. if HasOption('stylesheet') then
  345. StyleSheet:=GetOptionValue('stylesheet');
  346. if HasOption('p', 'progress') then
  347. ShowProgress:=True;
  348. if HasOption('skiptiming') then
  349. FSkipTiming:=True;
  350. if HasOption('sparse') then
  351. FSparse:=True;
  352. If HasOption('no-addresses') then
  353. FSkipAddressInfo:=True;
  354. // Determine runmode
  355. if HasOption('suite') then
  356. begin
  357. FSuite:=GetOptionValue('suite');
  358. FRunMode:=rmSuite;
  359. end
  360. else If HasOption('a','all') then
  361. FRunMode:=rmAll
  362. else if HasOption('l','list') then
  363. FRunMode:=rmList;
  364. end;
  365. procedure TTestRunner.ExtendXmlDocument(Doc: TXMLDocument);
  366. var
  367. n: TDOMElement;
  368. begin
  369. if StyleSheet<>'' then begin
  370. Doc.StylesheetType := 'text/xsl';
  371. Doc.StylesheetHRef := StyleSheet;
  372. end;
  373. n := Doc.CreateElement('Title');
  374. n.AppendChild(Doc.CreateTextNode(Title));
  375. Doc.FirstChild.AppendChild(n);
  376. end;
  377. procedure TTestRunner.RunSuite;
  378. var
  379. I,P : integer;
  380. S,TN : string;
  381. TS : TDecoratorTestSuite;
  382. T : TTest;
  383. begin
  384. S := FSuite;
  385. if S = '' then
  386. for I := 0 to GetTestRegistry.ChildTestCount - 1 do
  387. writeln(GetTestRegistry[i].TestName)
  388. else
  389. begin
  390. TS:=TDecoratorTestSuite.Create('SuiteList');
  391. try
  392. while Not(S = '') Do
  393. begin
  394. P:=Pos(',',S);
  395. If P=0 then
  396. P:=Length(S)+1;
  397. TN:=Copy(S,1,P-1);
  398. Delete(S,1,P);
  399. if (TN<>'') then
  400. begin
  401. T:=GetTestRegistry.FindTest(TN);
  402. if Assigned(T) then
  403. TS.AddTest(T);
  404. end;
  405. end;
  406. if (TS.CountTestCases>1) then
  407. DoTestRun(TS)
  408. else if TS.CountTestCases=1 then
  409. DoTestRun(TS[0])
  410. else
  411. Writeln('No tests selected.');
  412. finally
  413. TS.Free;
  414. end;
  415. end;
  416. end;
  417. procedure TTestRunner.ShowTestList;
  418. begin
  419. case FormatParam of
  420. fLatex: Write(GetSuiteAsLatex(GetTestRegistry));
  421. fPlain: Write(GetSuiteAsPlain(GetTestRegistry));
  422. fPlainNoTiming: Write(GetSuiteAsPlain(GetTestRegistry));
  423. else
  424. Write(GetSuiteAsXml(GetTestRegistry));
  425. end
  426. end;
  427. procedure TTestRunner.DoRun;
  428. var
  429. S : string;
  430. begin
  431. Terminate;
  432. FormatParam := DefaultFormat;
  433. If DefaultRunAllTests then
  434. FRunMode:=rmAll;
  435. S := CheckOptions(GetShortOpts, LongOpts);
  436. if (S <> '') then
  437. begin
  438. Writeln(S);
  439. Exit;
  440. end;
  441. ReadDefaults;
  442. if Not ParseOptions then
  443. exit;
  444. //get a list of all registed tests
  445. Case FRunMode of
  446. rmList: ShowTestList;
  447. rmSuite: RunSuite;
  448. rmAll: DoTestRun(GetTestRegistry);
  449. else
  450. Usage
  451. end;
  452. end;
  453. end.