xmlreporter.pas 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. {
  2. Copyright (C) 2006 Graeme Geldenhuys <[email protected]>
  3. This source is free software; you can redistribute it and/or modify it under
  4. the terms of the GNU General Public License as published by the Free
  5. Software Foundation; either version 2 of the License, or (at your option)
  6. any later version.
  7. This code is distributed in the hope that it will be useful, but WITHOUT ANY
  8. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  9. FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  10. details.
  11. A copy of the GNU General Public License is available on the World Wide Web
  12. at <http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by writing
  13. to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  14. Boston, MA 02110-1301, USA.
  15. Purpose:
  16. This unit contains an XML TestListener for use with the fpcUnit testing
  17. framework. It uses the XMLWrite unit, which is part of FPC, to generate
  18. the XML document. The benefit of using the XMLWrite unit is that the
  19. data generated is valid XML, with reserved characters correctly escaped.
  20. This allows the XML document to be further processed with XSLT etc without
  21. any issues.
  22. }
  23. {$IFNDEF FPC_DOTTEDUNITS}
  24. unit xmlreporter;
  25. {$ENDIF FPC_DOTTEDUNITS}
  26. {$mode objfpc}{$H+}
  27. interface
  28. {$IFDEF FPC_DOTTEDUNITS}
  29. uses
  30. System.Classes
  31. ,System.SysUtils
  32. ,FpcUnit.Test
  33. ,FpcUnit.Utils
  34. ,Xml.Dom
  35. ,Xml.Writer
  36. ;
  37. {$ELSE FPC_DOTTEDUNITS}
  38. uses
  39. Classes
  40. ,SysUtils
  41. ,fpcUnit
  42. ,TestUtils
  43. ,dom
  44. ,XMLWrite
  45. ;
  46. {$ENDIF FPC_DOTTEDUNITS}
  47. type
  48. { XML Test Listener }
  49. { TXMLResultsWriter }
  50. TXMLResultsWriter = class(TNoRefCountObject, ITestListener)
  51. private
  52. FDoc: TXMLDocument;
  53. { These TDOMNodes are for easy access and a bit of optimization }
  54. FResults: TDOMNode;
  55. FListing: TDOMNode;
  56. FFailures: TDOMNode;
  57. FIgnores: TDOMNode;
  58. FErrors: TDOMNode;
  59. FLastTestSuite: TDOMNode;
  60. FStartCrono: TDateTime;
  61. FskipTiming : Boolean;
  62. { Converts the actual test results into XML nodes. This gets called
  63. by the public method WriteResult. }
  64. procedure TestResultAsXML(pTestResult: TTestResult);
  65. { This gets called in the class constructor and sets up the starting nodes }
  66. procedure WriteHeader;
  67. public
  68. constructor Create;
  69. destructor Destroy; override;
  70. procedure WriteResult(aResult: TTestResult);
  71. { ITestListener interface requirements }
  72. procedure AddFailure(ATest: TTest; AFailure: TTestFailure);
  73. procedure AddError(ATest: TTest; AError: TTestFailure);
  74. procedure StartTest(ATest: TTest);
  75. procedure EndTest(ATest: TTest);
  76. procedure StartTestSuite(ATestSuite: TTestSuite);
  77. procedure EndTestSuite(ATestSuite: TTestSuite);
  78. { A public property to the internal XML document }
  79. property Document: TXMLDocument read FDoc;
  80. Property SkipTiming : Boolean Read FSkipTiming Write FSkipTiming;
  81. end;
  82. implementation
  83. { TXMLResultsWriter }
  84. procedure TXMLResultsWriter.TestResultAsXML(pTestResult: TTestResult);
  85. var
  86. n, lResults: TDOMNode;
  87. begin
  88. lResults := FDoc.FindNode('TestResults');
  89. n := FDoc.CreateElement('NumberOfRunTests');
  90. n.AppendChild(FDoc.CreateTextNode(IntToStr(pTestResult.RunTests)));
  91. lResults.AppendChild(n);
  92. n := FDoc.CreateElement('NumberOfErrors');
  93. n.AppendChild(FDoc.CreateTextNode(IntToStr(pTestResult.NumberOfErrors)));
  94. lResults.AppendChild(n);
  95. n := FDoc.CreateElement('NumberOfFailures');
  96. n.AppendChild(FDoc.CreateTextNode(IntToStr(pTestResult.NumberOfFailures)));
  97. lResults.AppendChild(n);
  98. n := FDoc.CreateElement('NumberOfIgnoredTests');
  99. n.AppendChild(FDoc.CreateTextNode(IntToStr(pTestResult.NumberOfIgnoredTests)));
  100. lResults.AppendChild(n);
  101. { We don't have access to TCustomResultsWriter so we cannot honour SkipTiming}
  102. if not(SkipTiming) then
  103. begin
  104. n := FDoc.CreateElement('TotalElapsedTime');
  105. n.AppendChild(FDoc.CreateTextNode(FormatDateTime('hh:nn:ss.zzz', Now - pTestResult.StartingTime)));
  106. lResults.AppendChild(n);
  107. end;
  108. { Summary of ISO 8601 http://www.cl.cam.ac.uk/~mgk25/iso-time.html }
  109. n := FDoc.CreateElement('DateTimeRan');
  110. n.AppendChild(FDoc.CreateTextNode(FormatDateTime('yyyy-mm-dd hh:mm:ss', Now)));
  111. lResults.AppendChild(n);
  112. end;
  113. procedure TXMLResultsWriter.WriteHeader;
  114. begin
  115. FResults := FDoc.CreateElement('TestResults');
  116. FResults.AppendChild(FDoc.CreateComment(' Generated using FpcUnit on '
  117. + FormatDateTime('yyyy-mm-dd hh:mm:ss', Now) ));
  118. FDoc.AppendChild(FResults);
  119. FListing := FDoc.CreateElement('TestListing');
  120. FResults.AppendChild(FListing);
  121. end;
  122. constructor TXMLResultsWriter.Create;
  123. begin
  124. FDoc := TXMLDocument.Create;
  125. FResults := nil;
  126. FFailures := nil;
  127. FIgnores := nil;
  128. FErrors := nil;
  129. FListing := nil;
  130. FLastTestSuite := nil;
  131. WriteHeader;
  132. end;
  133. destructor TXMLResultsWriter.Destroy;
  134. begin
  135. FResults := nil;
  136. FFailures := nil;
  137. FIgnores := nil;
  138. FErrors := nil;
  139. FListing := nil;
  140. FLastTestSuite := nil;
  141. FDoc.Free;
  142. inherited Destroy;
  143. end;
  144. procedure TXMLResultsWriter.WriteResult(aResult: TTestResult);
  145. begin
  146. TestResultAsXML(aResult);
  147. end;
  148. procedure TXMLResultsWriter.AddFailure(ATest: TTest; AFailure: TTestFailure);
  149. var
  150. n: TDOMElement;
  151. begin
  152. if AFailure.IsIgnoredTest then
  153. begin
  154. { Try and find the node first }
  155. if not Assigned(FIgnores) then
  156. FIgnores := FDoc.FindNode('ListOfIgnoredTests');
  157. { If we couldn't find it, create it }
  158. if not Assigned(FIgnores) then
  159. begin
  160. FIgnores := FDoc.CreateElement('ListOfIgnoredTests');
  161. FResults.AppendChild(FIgnores);
  162. end;
  163. n := FDoc.CreateElement('IgnoredTest');
  164. n.AppendChild(FDoc.CreateElement('Message') ).AppendChild(FDoc.CreateTextNode(AFailure.AsString));
  165. n.AppendChild(FDoc.CreateElement('ExceptionClass') ).AppendChild(FDoc.CreateTextNode(AFailure.ExceptionClassName));
  166. n.AppendChild(FDoc.CreateElement('ExceptionMessage')).AppendChild(FDoc.CreateTextNode(AFailure.ExceptionMessage));
  167. FIgnores.AppendChild(n);
  168. end
  169. else
  170. begin
  171. { Try and find the node first }
  172. if not Assigned(FFailures) then
  173. FFailures := FDoc.FindNode('ListOfFailures');
  174. { If we couldn't find it, create it }
  175. if not Assigned(FFailures) then
  176. begin
  177. FFailures := FDoc.CreateElement('ListOfFailures');
  178. FResults.AppendChild(FFailures);
  179. end;
  180. n := FDoc.CreateElement('Failure');
  181. n.AppendChild(FDoc.CreateElement('Message') ).AppendChild(FDoc.CreateTextNode(AFailure.AsString));
  182. n.AppendChild(FDoc.CreateElement('ExceptionClass') ).AppendChild(FDoc.CreateTextNode(AFailure.ExceptionClassName));
  183. n.AppendChild(FDoc.CreateElement('ExceptionMessage')).AppendChild(FDoc.CreateTextNode(AFailure.ExceptionMessage));
  184. FFailures.AppendChild(n);
  185. end;
  186. end;
  187. procedure TXMLResultsWriter.AddError(ATest: TTest; AError: TTestFailure);
  188. var
  189. n: TDOMElement;
  190. begin
  191. { Try and find the node first }
  192. if not Assigned(FErrors) then
  193. FErrors := FDoc.FindNode('ListOfErrors');
  194. { If we couldn't find it, create it }
  195. if not Assigned(FErrors) then
  196. begin
  197. FErrors := FDoc.CreateElement('ListOfErrors');
  198. FResults.AppendChild(FErrors);
  199. end;
  200. n := FDoc.CreateElement('Error');
  201. n.AppendChild(FDoc.CreateElement('Message') ).AppendChild(FDoc.CreateTextNode(AError.AsString));
  202. n.AppendChild(FDoc.CreateElement('ExceptionClass') ).AppendChild(FDoc.CreateTextNode(AError.ExceptionClassName));
  203. n.AppendChild(FDoc.CreateElement('ExceptionMessage')).AppendChild(FDoc.CreateTextNode(AError.ExceptionMessage));
  204. n.AppendChild(FDoc.CreateElement('SourceUnitName') ).AppendChild(FDoc.CreateTextNode(AError.SourceUnitName));
  205. n.AppendChild(FDoc.CreateElement('LineNumber') ).AppendChild(FDoc.CreateTextNode(IntToStr(AError.LineNumber)));
  206. n.AppendChild(FDoc.CreateElement('FailedMethodName')).AppendChild(FDoc.CreateTextNode(AError.FailedMethodName));
  207. FErrors.AppendChild(n);
  208. end;
  209. procedure TXMLResultsWriter.StartTest(ATest: TTest);
  210. var
  211. n: TDOMElement;
  212. begin
  213. n := FDoc.CreateElement('Test');
  214. n['Name'] := ATest.TestSuiteName + '.' + ATest.TestName;
  215. FLastTestSuite.AppendChild(n);
  216. FStartCrono := Now;
  217. end;
  218. procedure TXMLResultsWriter.EndTest(ATest: TTest);
  219. var
  220. n: TDOMNode;
  221. lNew: TDOMElement;
  222. begin
  223. n := FLastTestSuite.LastChild;
  224. if not(SkipTiming) then
  225. begin
  226. lNew := FDoc.CreateElement('ElapsedTime');
  227. lNew.AppendChild(FDoc.CreateTextNode(FormatDateTime('hh:nn:ss.zzz', Now - FStartCrono)));
  228. n.AppendChild(lNew);
  229. end;
  230. end;
  231. procedure TXMLResultsWriter.StartTestSuite(ATestSuite: TTestSuite);
  232. var
  233. n: TDOMElement;
  234. begin
  235. { Try and find the Listings node first }
  236. if not Assigned(FListing) then
  237. FListing := FDoc.FindNode('TestListing');
  238. { If we couldn't find it, create it }
  239. if not Assigned(FListing) then
  240. begin
  241. FListing := FDoc.CreateElement('TestListing');
  242. FResults.AppendChild(FListing);
  243. end;
  244. { The first TestSuite always seem to be blank/empty }
  245. if ATestSuite.TestName <> '' then
  246. begin
  247. n := FDoc.CreateElement('TestSuite');
  248. n['Name'] := ATestSuite.TestName;
  249. FListing.AppendChild(n);
  250. FLastTestSuite := n;
  251. end;
  252. end;
  253. procedure TXMLResultsWriter.EndTestSuite(ATestSuite: TTestSuite);
  254. begin
  255. // do nothing
  256. end;
  257. end.