consoletestrunner.pas 14 KB

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