File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\tasks\junit\SuiteResult.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Xavier Le Vourch, Tom Huybrechts, Yahoo!, Inc., Victor Garcia
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.tasks.junit;
25
26 import hudson.tasks.test.TestObject;
27 import hudson.util.IOException2;
28 import hudson.util.io.ParserConfigurator;
29 import org.dom4j.Document;
30 import org.dom4j.DocumentException;
31 import org.dom4j.Element;
32 import org.dom4j.io.SAXReader;
33 import org.kohsuke.stapler.export.Exported;
34 import org.kohsuke.stapler.export.ExportedBean;
35
36 import java.io.File;
37 import java.io.IOException;
38 import java.io.RandomAccessFile;
39 import java.io.Serializable;
40 import java.nio.ByteBuffer;
41 import java.nio.CharBuffer;
42 import java.nio.channels.FileChannel;
43 import java.nio.charset.Charset;
44 import java.util.ArrayList;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.regex.Matcher;
51 import java.util.regex.Pattern;
52
53 /**
54 * Result of one test suite.
55 *
56 * <p>
57 * The notion of "test suite" is rather arbitrary in JUnit ant task.
58 * It's basically one invocation of junit.
59 *
60 * <p>
61 * This object is really only used as a part of the persisted
62 * object tree.
63 *
64 * @author Kohsuke Kawaguchi
65 */
66 @ExportedBean
67 public final class SuiteResult implements Serializable {
68 private final String file;
69 private final String name;
70 private final String stdout;
71 private final String stderr;
72 private float duration;
73 /**
74 * The 'timestamp' attribute of the test suite.
75 * AFAICT, this is not a required attribute in XML, so the value may be null.
76 */
77 private String timestamp;
78 /** Optional ID attribute of a test suite. E.g., Eclipse plug-ins tests always have the name 'tests' but a different id. **/
79 private String id;
80
81 /**
82 * All test cases.
83 */
84 private final List<CaseResult> cases = new ArrayList<CaseResult>();
85 private transient Map<String,CaseResult> casesByName;
86 private transient hudson.tasks.junit.TestResult parent;
87
88 SuiteResult(String name, String stdout, String stderr) {
89 this.name = name;
90 this.stderr = stderr;
91 this.stdout = stdout;
92 this.file = null;
93 }
94
95 private synchronized Map<String,CaseResult> casesByName() {
96 if (casesByName == null) {
97 casesByName = new HashMap<String,CaseResult>();
98 for (CaseResult c : cases) {
99 casesByName.put(c.getName(), c);
100 }
101 }
102 return casesByName;
103 }
104
105 /**
106 * Passed to {@link ParserConfigurator}.
107 * @since 1.416
108 */
109 public static class SuiteResultParserConfigurationContext {
110 public final File xmlReport;
111
112 SuiteResultParserConfigurationContext(File xmlReport) {
113 this.xmlReport = xmlReport;
114 }
115 }
116
117 /**
118 * Parses the JUnit XML file into {@link SuiteResult}s.
119 * This method returns a collection, as a single XML may have multiple &lt;testsuite>
120 * elements wrapped into the top-level &lt;testsuites>.
121 */
122 static List<SuiteResult> parse(File xmlReport, boolean keepLongStdio) throws DocumentException, IOException, InterruptedException {
123 List<SuiteResult> r = new ArrayList<SuiteResult>();
124
125 // parse into DOM
126 SAXReader saxReader = new SAXReader();
127 ParserConfigurator.applyConfiguration(saxReader,new SuiteResultParserConfigurationContext(xmlReport));
128
129 Document result = saxReader.read(xmlReport);
130 Element root = result.getRootElement();
131
132 parseSuite(xmlReport,keepLongStdio,r,root);
133
134 return r;
135 }
136
137 private static void parseSuite(File xmlReport, boolean keepLongStdio, List<SuiteResult> r, Element root) throws DocumentException, IOException {
138 // nested test suites
139 @SuppressWarnings("unchecked")
140 List<Element> testSuites = (List<Element>)root.elements("testsuite");
141 for (Element suite : testSuites)
142 parseSuite(xmlReport, keepLongStdio, r, suite);
143
144 // child test cases
145 // FIXME: do this also if no testcases!
146 if (root.element("testcase")!=null || root.element("error")!=null)
147 r.add(new SuiteResult(xmlReport, root, keepLongStdio));
148 }
149
150 /**
151 * @param xmlReport
152 * A JUnit XML report file whose top level element is 'testsuite'.
153 * @param suite
154 * The parsed result of {@code xmlReport}
155 */
156 private SuiteResult(File xmlReport, Element suite, boolean keepLongStdio) throws DocumentException, IOException {
157 this.file = xmlReport.getAbsolutePath();
158 String name = suite.attributeValue("name");
159 if(name==null)
160 // some user reported that name is null in their environment.
161 // see http://www.nabble.com/Unexpected-Null-Pointer-Exception-in-Hudson-1.131-tf4314802.html
162 name = '('+xmlReport.getName()+')';
163 else {
164 String pkg = suite.attributeValue("package");
165 if(pkg!=null&& pkg.length()>0) name=pkg+'.'+name;
166 }
167 this.name = TestObject.safe(name);
168 this.timestamp = suite.attributeValue("timestamp");
169 this.id = suite.attributeValue("id");
170
171 Element ex = suite.element("error");
172 if(ex!=null) {
173 // according to junit-noframes.xsl l.229, this happens when the test class failed to load
174 addCase(new CaseResult(this, suite, "<init>", keepLongStdio));
175 }
176
177 @SuppressWarnings("unchecked")
178 List<Element> testCases = (List<Element>)suite.elements("testcase");
179 for (Element e : testCases) {
180 // https://issues.jenkins-ci.org/browse/JENKINS-1233 indicates that
181 // when <testsuites> is present, we are better off using @classname on the
182 // individual testcase class.
183
184 // https://issues.jenkins-ci.org/browse/JENKINS-1463 indicates that
185 // @classname may not exist in individual testcase elements. We now
186 // also test if the testsuite element has a package name that can be used
187 // as the class name instead of the file name which is default.
188 String classname = e.attributeValue("classname");
189 if (classname == null) {
190 classname = suite.attributeValue("name");
191 }
192
193 // https://issues.jenkins-ci.org/browse/JENKINS-1233 and
194 // http://www.nabble.com/difference-in-junit-publisher-and-ant-junitreport-tf4308604.html#a12265700
195 // are at odds with each other --- when both are present,
196 // one wants to use @name from <testsuite>,
197 // the other wants to use @classname from <testcase>.
198
199 addCase(new CaseResult(this, e, classname, keepLongStdio));
200 }
201
202 String stdout = CaseResult.possiblyTrimStdio(cases, keepLongStdio, suite.elementText("system-out"));
203 String stderr = CaseResult.possiblyTrimStdio(cases, keepLongStdio, suite.elementText("system-err"));
204 if (stdout==null && stderr==null) {
205 // Surefire never puts stdout/stderr in the XML. Instead, it goes to a separate file (when ${maven.test.redirectTestOutputToFile}).
206 Matcher m = SUREFIRE_FILENAME.matcher(xmlReport.getName());
207 if (m.matches()) {
208 // look for ***-output.txt from TEST-***.xml
209 File mavenOutputFile = new File(xmlReport.getParentFile(),m.group(1)+"-output.txt");
210 if (mavenOutputFile.exists()) {
211 try {
212 RandomAccessFile raf = new RandomAccessFile(mavenOutputFile, "r");
213 try {
214 ByteBuffer bb = raf.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, mavenOutputFile.length());
215 CharBuffer cb = Charset.defaultCharset().decode(bb);
216 stdout = CaseResult.possiblyTrimStdio(cases, keepLongStdio, cb);
217 } finally {
218 raf.close();
219 }
220 } catch (IOException e) {
221 throw new IOException2("Failed to read "+mavenOutputFile,e);
222 }
223 }
224 }
225 }
226
227 this.stdout = stdout;
228 this.stderr = stderr;
229 }
230
231 /*package*/ void addCase(CaseResult cr) {
232 cases.add(cr);
233 casesByName().put(cr.getName(), cr);
234 duration += cr.getDuration();
235 }
236
237 @Exported(visibility=9)
238 public String getName() {
239 return name;
240 }
241
242 @Exported(visibility=9)
243 public float getDuration() {
244 return duration;
245 }
246
247 /**
248 * The stdout of this test.
249 *
250 * @since 1.281
251 * @see CaseResult#getStdout()
252 */
253 @Exported
254 public String getStdout() {
255 return stdout;
256 }
257
258 /**
259 * The stderr of this test.
260 *
261 * @since 1.281
262 * @see CaseResult#getStderr()
263 */
264 @Exported
265 public String getStderr() {
266 return stderr;
267 }
268
269 /**
270 * The absolute path to the original test report. OS-dependent.
271 */
272 public String getFile() {
273 return file;
274 }
275
276 public hudson.tasks.junit.TestResult getParent() {
277 return parent;
278 }
279
280 @Exported(visibility=9)
281 public String getTimestamp() {
282 return timestamp;
283 }
284
285 @Exported(visibility=9)
286 public String getId() {
287 return id;
288 }
289
290 @Exported(inline=true,visibility=9)
291 public List<CaseResult> getCases() {
292 return cases;
293 }
294
295 public SuiteResult getPreviousResult() {
296 hudson.tasks.test.TestResult pr = parent.getPreviousResult();
297 if(pr==null) return null;
298 if(pr instanceof hudson.tasks.junit.TestResult)
299 return ((hudson.tasks.junit.TestResult)pr).getSuite(name);
300 return null;
301 }
302
303 /**
304 * Returns the {@link CaseResult} whose {@link CaseResult#getName()}
305 * is the same as the given string.
306 *
307 * <p>
308 * Note that test name needs not be unique.
309 */
310 public CaseResult getCase(String name) {
311 return casesByName().get(name);
312 }
313
314 public Set<String> getClassNames() {
315 Set<String> result = new HashSet<String>();
316 for (CaseResult c : cases) {
317 result.add(c.getClassName());
318 }
319 return result;
320 }
321
322 /** KLUGE. We have to call this to prevent freeze()
323 * from calling c.freeze() on all its children,
324 * because that in turn calls c.getOwner(),
325 * which requires a non-null parent.
326 * @param parent
327 */
328 void setParent(hudson.tasks.junit.TestResult parent) {
329 this.parent = parent;
330 }
331
332 /*package*/ boolean freeze(hudson.tasks.junit.TestResult owner) {
333 if(this.parent!=null)
334 return false; // already frozen
335
336 this.parent = owner;
337 for (CaseResult c : cases)
338 c.freeze(this);
339 return true;
340 }
341
342 private static final long serialVersionUID = 1L;
343
344 private static final Pattern SUREFIRE_FILENAME = Pattern.compile("TEST-(.+)\\.xml");
345 }
346