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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, id:cactusman, Tom Huybrechts, Yahoo!, Inc.
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.AbortException;
27 import hudson.Util;
28 import hudson.model.AbstractBuild;
29 import hudson.model.Run;
30 import hudson.tasks.test.AbstractTestResultAction;
31 import hudson.tasks.test.MetaTabulatedResult;
32 import hudson.tasks.test.TestObject;
33 import hudson.util.IOException2;
34
35 import java.io.File;
36 import java.io.IOException;
37 import java.io.PrintWriter;
38 import java.io.StringWriter;
39 import java.util.ArrayList;
40 import java.util.Collection;
41 import java.util.Collections;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.TreeMap;
46
47 import org.apache.tools.ant.DirectoryScanner;
48 import org.dom4j.DocumentException;
49 import org.kohsuke.stapler.StaplerRequest;
50 import org.kohsuke.stapler.StaplerResponse;
51 import org.kohsuke.stapler.export.Exported;
52
53 /**
54 * Root of all the test results for one build.
55 *
56 * @author Kohsuke Kawaguchi
57 */
58 public final class TestResult extends MetaTabulatedResult {
59
60 /**
61 * List of all {@link SuiteResult}s in this test.
62 * This is the core data structure to be persisted in the disk.
63 */
64 private final List<SuiteResult> suites = new ArrayList<SuiteResult>();
65
66 /**
67 * {@link #suites} keyed by their names for faster lookup.
68 */
69 private transient Map<String,SuiteResult> suitesByName;
70
71 /**
72 * Results tabulated by package.
73 */
74 private transient Map<String,PackageResult> byPackages;
75
76 // set during the freeze phase
77 private transient AbstractTestResultAction parentAction;
78
79 private transient TestObject parent;
80
81 /**
82 * Number of all tests.
83 */
84 private transient int totalTests;
85
86 private transient int skippedTests;
87
88 private float duration;
89
90 /**
91 * Number of failed/error tests.
92 */
93 private transient List<CaseResult> failedTests;
94
95 private final boolean keepLongStdio;
96
97 /**
98 * Creates an empty result.
99 */
100 public TestResult() {
101 keepLongStdio = false;
102 }
103
104 @Deprecated
105 public TestResult(long buildTime, DirectoryScanner results) throws IOException {
106 this(buildTime, results, false);
107 }
108
109 /**
110 * Collect reports from the given {@link DirectoryScanner}, while
111 * filtering out all files that were created before the given time.
112 * @param keepLongStdio if true, retain a suite's complete stdout/stderr even if this is huge and the suite passed
113 * @since 1.358
114 */
115 public TestResult(long buildTime, DirectoryScanner results, boolean keepLongStdio) throws IOException {
116 this.keepLongStdio = keepLongStdio;
117 parse(buildTime, results);
118 }
119
120 public TestObject getParent() {
121 return parent;
122 }
123
124 @Override
125 public void setParent(TestObject parent) {
126 this.parent = parent;
127 }
128
129 @Override
130 public TestResult getTestResult() {
131 return this;
132 }
133
134 /**
135 * Collect reports from the given {@link DirectoryScanner}, while
136 * filtering out all files that were created before the given time.
137 */
138 public void parse(long buildTime, DirectoryScanner results) throws IOException {
139 String[] includedFiles = results.getIncludedFiles();
140 File baseDir = results.getBasedir();
141 parse(buildTime,baseDir,includedFiles);
142 }
143
144 /**
145 * Collect reports from the given report files, while
146 * filtering out all files that were created before the given time.
147 *
148 * @since 1.426
149 */
150 public void parse(long buildTime, File baseDir, String[] reportFiles) throws IOException {
151
152 boolean parsed=false;
153
154 for (String value : reportFiles) {
155 File reportFile = new File(baseDir, value);
156 // only count files that were actually updated during this build
157 if ( (buildTime-3000/*error margin*/ <= reportFile.lastModified())) {
158 parsePossiblyEmpty(reportFile);
159 parsed = true;
160 }
161 }
162
163 if(!parsed) {
164 long localTime = System.currentTimeMillis();
165 if(localTime < buildTime-1000) /*margin*/
166 // build time is in the the future. clock on this slave must be running behind
167 throw new AbortException(
168 "Clock on this slave is out of sync with the master, and therefore \n" +
169 "I can't figure out what test results are new and what are old.\n" +
170 "Please keep the slave clock in sync with the master.");
171
172 File f = new File(baseDir,reportFiles[0]);
173 throw new AbortException(
174 String.format(
175 "Test reports were found but none of them are new. Did tests run? %n"+
176 "For example, %s is %s old%n", f,
177 Util.getTimeSpanString(buildTime-f.lastModified())));
178 }
179 }
180
181 /**
182 * Collect reports from the given report files
183 *
184 * @since 1.500
185 */
186 public void parse(long buildTime, Iterable<File> reportFiles) throws IOException {
187 boolean parsed=false;
188
189 for (File reportFile : reportFiles) {
190 // only count files that were actually updated during this build
191 if ( (buildTime-3000/*error margin*/ <= reportFile.lastModified())) {
192 parsePossiblyEmpty(reportFile);
193 parsed = true;
194 }
195 }
196
197 if(!parsed) {
198 long localTime = System.currentTimeMillis();
199 if(localTime < buildTime-1000) /*margin*/
200 // build time is in the the future. clock on this slave must be running behind
201 throw new AbortException(
202 "Clock on this slave is out of sync with the master, and therefore \n" +
203 "I can't figure out what test results are new and what are old.\n" +
204 "Please keep the slave clock in sync with the master.");
205
206 File f = reportFiles.iterator().next();
207 throw new AbortException(
208 String.format(
209 "Test reports were found but none of them are new. Did tests run? %n"+
210 "For example, %s is %s old%n", f,
211 Util.getTimeSpanString(buildTime-f.lastModified())));
212 }
213
214 }
215
216 private void parsePossiblyEmpty(File reportFile) throws IOException {
217 if(reportFile.length()==0) {
218 // this is a typical problem when JVM quits abnormally, like OutOfMemoryError during a test.
219 SuiteResult sr = new SuiteResult(reportFile.getName(), "", "");
220 sr.addCase(new CaseResult(sr,"<init>","Test report file "+reportFile.getAbsolutePath()+" was length 0"));
221 add(sr);
222 } else {
223 parse(reportFile);
224 }
225 }
226
227 private void add(SuiteResult sr) {
228 for (SuiteResult s : suites) {
229 // JENKINS-12457: If a testsuite is distributed over multiple files, merge it into a single SuiteResult:
230 if(s.getName().equals(sr.getName()) && nullSafeEq(s.getId(),sr.getId())) {
231
232 // However, a common problem is that people parse TEST-*.xml as well as TESTS-TestSuite.xml.
233 // In that case consider the result file as a duplicate and discard it.
234 // see http://jenkins.361315.n4.nabble.com/Problem-with-duplicate-build-execution-td371616.html for discussion.
235 if(strictEq(s.getTimestamp(),sr.getTimestamp())) {
236 return;
237 }
238
239 for (CaseResult cr: sr.getCases()) {
240 s.addCase(cr);
241 cr.replaceParent(s);
242 }
243 duration += sr.getDuration();
244 return;
245 }
246 }
247 suites.add(sr);
248 duration += sr.getDuration();
249 }
250
251 private boolean strictEq(Object lhs, Object rhs) {
252 return lhs != null && rhs != null && lhs.equals(rhs);
253 }
254
255 private boolean nullSafeEq(Object lhs, Object rhs) {
256 if (lhs == null) {
257 return rhs == null;
258 }
259 return lhs.equals(rhs);
260 }
261
262 /**
263 * Parses an additional report file.
264 */
265 public void parse(File reportFile) throws IOException {
266 try {
267 for (SuiteResult suiteResult : SuiteResult.parse(reportFile, keepLongStdio))
268 add(suiteResult);
269 } catch (InterruptedException e) {
270 throw new IOException2("Failed to read "+reportFile,e);
271 } catch (RuntimeException e) {
272 throw new IOException2("Failed to read "+reportFile,e);
273 } catch (DocumentException e) {
274 if (!reportFile.getPath().endsWith(".xml")) {
275 throw new IOException2("Failed to read "+reportFile+"\n"+
276 "Is this really a JUnit report file? Your configuration must be matching too many files",e);
277 } else {
278 SuiteResult sr = new SuiteResult(reportFile.getName(), "", "");
279 StringWriter writer = new StringWriter();
280 e.printStackTrace(new PrintWriter(writer));
281 String error = "Failed to read test report file "+reportFile.getAbsolutePath()+"\n"+writer.toString();
282 sr.addCase(new CaseResult(sr,"<init>",error));
283 add(sr);
284 throw new IOException2("Failed to read "+reportFile,e);
285 }
286 }
287 }
288
289 public String getDisplayName() {
290 return Messages.TestResult_getDisplayName();
291 }
292
293 @Override
294 public AbstractBuild<?,?> getOwner() {
295 return (parentAction == null? null: parentAction.owner);
296 }
297
298 @Override
299 public hudson.tasks.test.TestResult findCorrespondingResult(String id) {
300 if (getId().equals(id) || (id == null)) {
301 return this;
302 }
303
304 String firstElement = null;
305 String subId = null;
306 int sepIndex = id.indexOf('/');
307 if (sepIndex < 0) {
308 firstElement = id;
309 subId = null;
310 } else {
311 firstElement = id.substring(0, sepIndex);
312 subId = id.substring(sepIndex + 1);
313 if (subId.length() == 0) {
314 subId = null;
315 }
316 }
317
318 String packageName = null;
319 if (firstElement.equals(getId())) {
320 sepIndex = subId.indexOf('/');
321 if (sepIndex < 0) {
322 packageName = subId;
323 subId = null;
324 } else {
325 packageName = subId.substring(0, sepIndex);
326 subId = subId.substring(sepIndex + 1);
327 }
328 } else {
329 packageName = firstElement;
330 subId = null;
331 }
332 PackageResult child = byPackage(packageName);
333 if (child != null) {
334 if (subId != null) {
335 return child.findCorrespondingResult(subId);
336 } else {
337 return child;
338 }
339 } else {
340 return null;
341 }
342 }
343
344 @Override
345 public String getTitle() {
346 return Messages.TestResult_getTitle();
347 }
348
349 @Override
350 public String getChildTitle() {
351 return Messages.TestResult_getChildTitle();
352 }
353
354 @Exported(visibility=999)
355 @Override
356 public float getDuration() {
357 return duration;
358 }
359
360 @Exported(visibility=999)
361 @Override
362 public int getPassCount() {
363 return totalTests-getFailCount()-getSkipCount();
364 }
365
366 @Exported(visibility=999)
367 @Override
368 public int getFailCount() {
369 if(failedTests==null)
370 return 0;
371 else
372 return failedTests.size();
373 }
374
375 @Exported(visibility=999)
376 @Override
377 public int getSkipCount() {
378 return skippedTests;
379 }
380
381 @Override
382 public List<CaseResult> getFailedTests() {
383 return failedTests;
384 }
385
386 /**
387 * Gets the "children" of this test result that passed
388 *
389 * @return the children of this test result, if any, or an empty collection
390 */
391 @Override
392 public Collection<? extends hudson.tasks.test.TestResult> getPassedTests() {
393 throw new UnsupportedOperationException(); // TODO: implement!(FIXME: generated)
394 }
395
396 /**
397 * Gets the "children" of this test result that were skipped
398 *
399 * @return the children of this test result, if any, or an empty list
400 */
401 @Override
402 public Collection<? extends hudson.tasks.test.TestResult> getSkippedTests() {
403 throw new UnsupportedOperationException(); // TODO: implement!(FIXME: generated)
404 }
405
406 /**
407 * If this test failed, then return the build number
408 * when this test started failing.
409 */
410 @Override
411 public int getFailedSince() {
412 throw new UnsupportedOperationException(); // TODO: implement!(FIXME: generated)
413 }
414
415 /**
416 * If this test failed, then return the run
417 * when this test started failing.
418 */
419 @Override
420 public Run<?, ?> getFailedSinceRun() {
421 throw new UnsupportedOperationException(); // TODO: implement!(FIXME: generated)
422 }
423
424 /**
425 * The stdout of this test.
426 * <p/>
427 * <p/>
428 * Depending on the tool that produced the XML report, this method works somewhat inconsistently.
429 * With some tools (such as Maven surefire plugin), you get the accurate information, that is
430 * the stdout from this test case. With some other tools (such as the JUnit task in Ant), this
431 * method returns the stdout produced by the entire test suite.
432 * <p/>
433 * <p/>
434 * If you need to know which is the case, compare this output from {@link SuiteResult#getStdout()}.
435 *
436 * @since 1.294
437 */
438 @Override
439 public String getStdout() {
440 StringBuilder sb = new StringBuilder();
441 for (SuiteResult suite: suites) {
442 sb.append("Standard Out (stdout) for Suite: " + suite.getName());
443 sb.append(suite.getStdout());
444 }
445 return sb.toString();
446 }
447
448 /**
449 * The stderr of this test.
450 *
451 * @see #getStdout()
452 * @since 1.294
453 */
454 @Override
455 public String getStderr() {
456 StringBuilder sb = new StringBuilder();
457 for (SuiteResult suite: suites) {
458 sb.append("Standard Error (stderr) for Suite: " + suite.getName());
459 sb.append(suite.getStderr());
460 }
461 return sb.toString();
462 }
463
464 /**
465 * If there was an error or a failure, this is the stack trace, or otherwise null.
466 */
467 @Override
468 public String getErrorStackTrace() {
469 return "No error stack traces available at this level. Drill down to individual tests to find stack traces.";
470 }
471
472 /**
473 * If there was an error or a failure, this is the text from the message.
474 */
475 @Override
476 public String getErrorDetails() {
477 return "No error details available at this level. Drill down to individual tests to find details.";
478 }
479
480 /**
481 * @return true if the test was not skipped and did not fail, false otherwise.
482 */
483 @Override
484 public boolean isPassed() {
485 return (getFailCount() == 0);
486 }
487
488 @Override
489 public Collection<PackageResult> getChildren() {
490 return byPackages.values();
491 }
492
493 /**
494 * Whether this test result has children.
495 */
496 @Override
497 public boolean hasChildren() {
498 return !suites.isEmpty();
499 }
500
501 @Exported(inline=true,visibility=9)
502 public Collection<SuiteResult> getSuites() {
503 return suites;
504 }
505
506
507 @Override
508 public String getName() {
509 return "junit";
510 }
511
512 @Override
513 public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
514 if (token.equals(getId())) {
515 return this;
516 }
517
518 PackageResult result = byPackage(token);
519 if (result != null) {
520 return result;
521 } else {
522 return super.getDynamic(token, req, rsp);
523 }
524 }
525
526 public PackageResult byPackage(String packageName) {
527 return byPackages.get(packageName);
528 }
529
530 public SuiteResult getSuite(String name) {
531 return suitesByName.get(name);
532 }
533
534 @Override
535 public void setParentAction(AbstractTestResultAction action) {
536 this.parentAction = action;
537 tally(); // I want to be sure to inform our children when we get an action.
538 }
539
540 @Override
541 public AbstractTestResultAction getParentAction() {
542 return this.parentAction;
543 }
544
545 /**
546 * Recount my children.
547 */
548 @Override
549 public void tally() {
550 /// Empty out data structures
551 // TODO: free children? memmory leak?
552 suitesByName = new HashMap<String,SuiteResult>();
553 failedTests = new ArrayList<CaseResult>();
554 byPackages = new TreeMap<String,PackageResult>();
555
556 totalTests = 0;
557 skippedTests = 0;
558
559 // Ask all of our children to tally themselves
560 for (SuiteResult s : suites) {
561 s.setParent(this); // kluge to prevent double-counting the results
562 suitesByName.put(s.getName(),s);
563 List<CaseResult> cases = s.getCases();
564
565 for (CaseResult cr: cases) {
566 cr.setParentAction(this.parentAction);
567 cr.setParentSuiteResult(s);
568 cr.tally();
569 String pkg = cr.getPackageName(), spkg = safe(pkg);
570 PackageResult pr = byPackage(spkg);
571 if(pr==null)
572 byPackages.put(spkg,pr=new PackageResult(this,pkg));
573 pr.add(cr);
574 }
575 }
576
577 for (PackageResult pr : byPackages.values()) {
578 pr.tally();
579 skippedTests += pr.getSkipCount();
580 failedTests.addAll(pr.getFailedTests());
581 totalTests += pr.getTotalCount();
582 }
583 }
584
585 /**
586 * Builds up the transient part of the data structure
587 * from results {@link #parse(File) parsed} so far.
588 *
589 * <p>
590 * After the data is frozen, more files can be parsed
591 * and then freeze can be called again.
592 */
593 public void freeze(TestResultAction parent) {
594 this.parentAction = parent;
595 if(suitesByName==null) {
596 // freeze for the first time
597 suitesByName = new HashMap<String,SuiteResult>();
598 totalTests = 0;
599 failedTests = new ArrayList<CaseResult>();
600 byPackages = new TreeMap<String,PackageResult>();
601 }
602
603 for (SuiteResult s : suites) {
604 if(!s.freeze(this)) // this is disturbing: has-a-parent is conflated with has-been-counted
605 continue;
606
607 suitesByName.put(s.getName(),s);
608
609 totalTests += s.getCases().size();
610 for(CaseResult cr : s.getCases()) {
611 if(cr.isSkipped())
612 skippedTests++;
613 else if(!cr.isPassed())
614 failedTests.add(cr);
615
616 String pkg = cr.getPackageName(), spkg = safe(pkg);
617 PackageResult pr = byPackage(spkg);
618 if(pr==null)
619 byPackages.put(spkg,pr=new PackageResult(this,pkg));
620 pr.add(cr);
621 }
622 }
623
624 Collections.sort(failedTests,CaseResult.BY_AGE);
625
626 for (PackageResult pr : byPackages.values())
627 pr.freeze();
628 }
629
630 private static final long serialVersionUID = 1L;
631
632 }
633