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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Seiji Sogabe, 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 org.jvnet.localizer.Localizable;
27 import hudson.model.AbstractBuild;
28 import hudson.model.Run;
29 import hudson.tasks.test.TestResult;
30 import org.dom4j.Element;
31 import org.kohsuke.stapler.export.Exported;
32
33 import java.text.DecimalFormat;
34 import java.text.ParseException;
35 import java.util.*;
36 import java.util.logging.Logger;
37
38 import static java.util.Collections.emptyList;
39
40 /**
41 * One test result.
42 *
43 * @author Kohsuke Kawaguchi
44 */
45 public final class CaseResult extends TestResult implements Comparable<CaseResult> {
46 private static final Logger LOGGER = Logger.getLogger(CaseResult.class.getName());
47 private final float duration;
48 /**
49 * In JUnit, a test is a method of a class. This field holds the fully qualified class name
50 * that the test was in.
51 */
52 private final String className;
53 /**
54 * This field retains the method name.
55 */
56 private final String testName;
57 private transient String safeName;
58 private final boolean skipped;
59 private final String errorStackTrace;
60 private final String errorDetails;
61 private transient SuiteResult parent;
62
63 private transient ClassResult classResult;
64
65 /**
66 * Some tools report stdout and stderr at testcase level (such as Maven surefire plugin), others do so at
67 * the suite level (such as Ant JUnit task.)
68 *
69 * If these information are reported at the test case level, these fields are set,
70 * otherwise null, in which case {@link SuiteResult#stdout}.
71 */
72 private final String stdout,stderr;
73
74 /**
75 * This test has been failing since this build number (not id.)
76 *
77 * If {@link #isPassed() passing}, this field is left unused to 0.
78 */
79 private /*final*/ int failedSince;
80
81 private static float parseTime(Element testCase) {
82 String time = testCase.attributeValue("time");
83 if(time!=null) {
84 time = time.replace(",","");
85 try {
86 return Float.parseFloat(time);
87 } catch (NumberFormatException e) {
88 try {
89 return new DecimalFormat().parse(time).floatValue();
90 } catch (ParseException x) {
91 // hmm, don't know what this format is.
92 }
93 }
94 }
95 return 0.0f;
96 }
97
98 CaseResult(SuiteResult parent, Element testCase, String testClassName, boolean keepLongStdio) {
99 // schema for JUnit report XML format is not available in Ant,
100 // so I don't know for sure what means what.
101 // reports in http://www.nabble.com/difference-in-junit-publisher-and-ant-junitreport-tf4308604.html#a12265700
102 // indicates that maybe I shouldn't use @classname altogether.
103
104 //String cn = testCase.attributeValue("classname");
105 //if(cn==null)
106 // // Maven seems to skip classname, and that shows up in testSuite/@name
107 // cn = parent.getName();
108
109
110 /*
111 According to http://www.nabble.com/NPE-(Fatal%3A-Null)-in-recording-junit-test-results-td23562964.html
112 there's some odd-ball cases where testClassName is null but
113 @name contains fully qualified name.
114 */
115 String nameAttr = testCase.attributeValue("name");
116 if(testClassName==null && nameAttr.contains(".")) {
117 testClassName = nameAttr.substring(0,nameAttr.lastIndexOf('.'));
118 nameAttr = nameAttr.substring(nameAttr.lastIndexOf('.')+1);
119 }
120
121 className = testClassName;
122 testName = nameAttr;
123 errorStackTrace = getError(testCase);
124 errorDetails = getErrorMessage(testCase);
125 this.parent = parent;
126 duration = parseTime(testCase);
127 skipped = isMarkedAsSkipped(testCase);
128 @SuppressWarnings("LeakingThisInConstructor")
129 Collection<CaseResult> _this = Collections.singleton(this);
130 stdout = possiblyTrimStdio(_this, keepLongStdio, testCase.elementText("system-out"));
131 stderr = possiblyTrimStdio(_this, keepLongStdio, testCase.elementText("system-err"));
132 }
133
134 private static final int HALF_MAX_SIZE = 500;
135 static String possiblyTrimStdio(Collection<CaseResult> results, boolean keepLongStdio, CharSequence stdio) { // HUDSON-6516
136 if (stdio == null) {
137 return null;
138 }
139 if (keepLongStdio) {
140 return stdio.toString();
141 }
142 for (CaseResult result : results) {
143 if (result.errorStackTrace != null) {
144 return stdio.toString();
145 }
146 }
147 int len = stdio.length();
148 int middle = len - HALF_MAX_SIZE * 2;
149 if (middle <= 0) {
150 return stdio.toString();
151 }
152 return stdio.subSequence(0, HALF_MAX_SIZE) + "\n...[truncated " + middle + " chars]...\n" + stdio.subSequence(len - HALF_MAX_SIZE, len);
153 }
154
155 /**
156 * Used to create a fake failure, when Hudson fails to load data from XML files.
157 */
158 CaseResult(SuiteResult parent, String testName, String errorStackTrace) {
159 this.className = parent == null ? "unnamed" : parent.getName();
160 this.testName = testName;
161 this.errorStackTrace = errorStackTrace;
162 this.errorDetails = "";
163 this.parent = parent;
164 this.stdout = null;
165 this.stderr = null;
166 this.duration = 0.0f;
167 this.skipped = false;
168 }
169
170 public ClassResult getParent() {
171 return classResult;
172 }
173
174 private static String getError(Element testCase) {
175 String msg = testCase.elementText("error");
176 if(msg!=null)
177 return msg;
178 return testCase.elementText("failure");
179 }
180
181 private static String getErrorMessage(Element testCase) {
182
183 Element msg = testCase.element("error");
184 if (msg == null) {
185 msg = testCase.element("failure");
186 }
187 if (msg == null) {
188 return null; // no error or failure elements! damn!
189 }
190
191 return msg.attributeValue("message");
192 }
193
194 /**
195 * If the testCase element includes the skipped element (as output by TestNG), then
196 * the test has neither passed nor failed, it was never run.
197 */
198 private static boolean isMarkedAsSkipped(Element testCase) {
199 return testCase.element("skipped") != null;
200 }
201
202 public String getDisplayName() {
203 return testName;
204 }
205
206 /**
207 * Gets the name of the test, which is returned from {@code TestCase.getName()}
208 *
209 * <p>
210 * Note that this may contain any URL-unfriendly character.
211 */
212 @Exported(visibility=999)
213 public @Override String getName() {
214 return testName;
215 }
216
217 /**
218 * Gets the human readable title of this result object.
219 */
220 @Override
221 public String getTitle() {
222 return "Case Result: " + getName();
223 }
224
225 /**
226 * Gets the duration of the test, in seconds
227 */
228 @Exported(visibility=9)
229 public float getDuration() {
230 return duration;
231 }
232
233 /**
234 * Gets the version of {@link #getName()} that's URL-safe.
235 */
236 public @Override synchronized String getSafeName() {
237 if (safeName != null) {
238 return safeName;
239 }
240 StringBuilder buf = new StringBuilder(testName);
241 for( int i=0; i<buf.length(); i++ ) {
242 char ch = buf.charAt(i);
243 if(!Character.isJavaIdentifierPart(ch))
244 buf.setCharAt(i,'_');
245 }
246 Collection<CaseResult> siblings = (classResult ==null ? Collections.<CaseResult>emptyList(): classResult.getChildren());
247 return safeName = uniquifyName(siblings, buf.toString());
248 }
249
250 /**
251 * Gets the class name of a test class.
252 */
253 @Exported(visibility=9)
254 public String getClassName() {
255 return className;
256 }
257
258 /**
259 * Gets the simple (not qualified) class name.
260 */
261 public String getSimpleName() {
262 int idx = className.lastIndexOf('.');
263 return className.substring(idx+1);
264 }
265
266 /**
267 * Gets the package name of a test case
268 */
269 public String getPackageName() {
270 int idx = className.lastIndexOf('.');
271 if(idx<0) return "(root)";
272 else return className.substring(0,idx);
273 }
274
275 public String getFullName() {
276 return className+'.'+getName();
277 }
278
279
280 @Override
281 public int getFailCount() {
282 if (!isPassed() && !isSkipped()) return 1; else return 0;
283 }
284
285 @Override
286 public int getSkipCount() {
287 if (isSkipped()) return 1; else return 0;
288 }
289
290 @Override
291 public int getPassCount() {
292 return isPassed() ? 1 : 0;
293 }
294
295 /**
296 * If this test failed, then return the build number
297 * when this test started failing.
298 */
299 @Exported(visibility=9)
300 public int getFailedSince() {
301 // If we haven't calculated failedSince yet, and we should,
302 // do it now.
303 if (failedSince==0 && getFailCount()==1) {
304 CaseResult prev = getPreviousResult();
305 if(prev!=null && !prev.isPassed())
306 this.failedSince = prev.failedSince;
307 else if (getOwner() != null) {
308 this.failedSince = getOwner().getNumber();
309 } else {
310 LOGGER.warning("trouble calculating getFailedSince. We've got prev, but no owner.");
311 // failedSince will be 0, which isn't correct.
312 }
313 }
314 return failedSince;
315 }
316
317 public Run<?,?> getFailedSinceRun() {
318 return getOwner().getParent().getBuildByNumber(getFailedSince());
319 }
320
321 /**
322 * Gets the number of consecutive builds (including this)
323 * that this test case has been failing.
324 */
325 @Exported(visibility=9)
326 public int getAge() {
327 if(isPassed())
328 return 0;
329 else if (getOwner() != null) {
330 return getOwner().getNumber()-getFailedSince()+1;
331 } else {
332 LOGGER.fine("Trying to get age of a CaseResult without an owner");
333 return 0;
334 }
335 }
336
337 /**
338 * The stdout of this test.
339 *
340 * <p>
341 * Depending on the tool that produced the XML report, this method works somewhat inconsistently.
342 * With some tools (such as Maven surefire plugin), you get the accurate information, that is
343 * the stdout from this test case. With some other tools (such as the JUnit task in Ant), this
344 * method returns the stdout produced by the entire test suite.
345 *
346 * <p>
347 * If you need to know which is the case, compare this output from {@link SuiteResult#getStdout()}.
348 * @since 1.294
349 */
350 @Exported
351 public String getStdout() {
352 if(stdout!=null) return stdout;
353 SuiteResult sr = getSuiteResult();
354 if (sr==null) return "";
355 return getSuiteResult().getStdout();
356 }
357
358 /**
359 * The stderr of this test.
360 *
361 * @see #getStdout()
362 * @since 1.294
363 */
364 @Exported
365 public String getStderr() {
366 if(stderr!=null) return stderr;
367 SuiteResult sr = getSuiteResult();
368 if (sr==null) return "";
369 return getSuiteResult().getStderr();
370 }
371
372 @Override
373 public CaseResult getPreviousResult() {
374 if (parent == null) return null;
375 SuiteResult pr = parent.getPreviousResult();
376 if(pr==null) return null;
377 return pr.getCase(getName());
378 }
379
380 /**
381 * Case results have no children
382 * @return null
383 */
384 @Override
385 public TestResult findCorrespondingResult(String id) {
386 if (id.equals(safe(getName()))) {
387 return this;
388 }
389 return null;
390 }
391
392 /**
393 * Gets the "children" of this test result that failed
394 *
395 * @return the children of this test result, if any, or an empty collection
396 */
397 @Override
398 public Collection<? extends TestResult> getFailedTests() {
399 return singletonListOrEmpty(!isPassed());
400 }
401
402 /**
403 * Gets the "children" of this test result that passed
404 *
405 * @return the children of this test result, if any, or an empty collection
406 */
407 @Override
408 public Collection<? extends TestResult> getPassedTests() {
409 return singletonListOrEmpty(isPassed());
410 }
411
412 /**
413 * Gets the "children" of this test result that were skipped
414 *
415 * @return the children of this test result, if any, or an empty list
416 */
417 @Override
418 public Collection<? extends TestResult> getSkippedTests() {
419 return singletonListOrEmpty(isSkipped());
420 }
421
422 private Collection<? extends hudson.tasks.test.TestResult> singletonListOrEmpty(boolean f) {
423 if (f)
424 return Collections.singletonList(this);
425 else
426 return emptyList();
427 }
428
429 /**
430 * If there was an error or a failure, this is the stack trace, or otherwise null.
431 */
432 @Exported
433 public String getErrorStackTrace() {
434 return errorStackTrace;
435 }
436
437 /**
438 * If there was an error or a failure, this is the text from the message.
439 */
440 @Exported
441 public String getErrorDetails() {
442 return errorDetails;
443 }
444
445 /**
446 * @return true if the test was not skipped and did not fail, false otherwise.
447 */
448 public boolean isPassed() {
449 return !skipped && errorStackTrace==null;
450 }
451
452 /**
453 * Tests whether the test was skipped or not. TestNG allows tests to be
454 * skipped if their dependencies fail or they are part of a group that has
455 * been configured to be skipped.
456 * @return true if the test was not executed, false otherwise.
457 */
458 @Exported(visibility=9)
459 public boolean isSkipped() {
460 return skipped;
461 }
462
463 public SuiteResult getSuiteResult() {
464 return parent;
465 }
466
467 @Override
468 public AbstractBuild<?,?> getOwner() {
469 SuiteResult sr = getSuiteResult();
470 if (sr==null) {
471 LOGGER.warning("In getOwner(), getSuiteResult is null"); return null; }
472 hudson.tasks.junit.TestResult tr = sr.getParent();
473 if (tr==null) {
474 LOGGER.warning("In getOwner(), suiteResult.getParent() is null."); return null; }
475 return tr.getOwner();
476 }
477
478 public void setParentSuiteResult(SuiteResult parent) {
479 this.parent = parent;
480 }
481
482 public void freeze(SuiteResult parent) {
483 this.parent = parent;
484 // some old test data doesn't have failedSince value set, so for those compute them.
485 if(!isPassed() && failedSince==0) {
486 CaseResult prev = getPreviousResult();
487 if(prev!=null && !prev.isPassed())
488 this.failedSince = prev.failedSince;
489 else
490 this.failedSince = getOwner().getNumber();
491 }
492 }
493
494 public int compareTo(CaseResult that) {
495 return this.getFullName().compareTo(that.getFullName());
496 }
497
498 @Exported(name="status",visibility=9) // because stapler notices suffix 's' and remove it
499 public Status getStatus() {
500 if (skipped) {
501 return Status.SKIPPED;
502 }
503 CaseResult pr = getPreviousResult();
504 if(pr==null) {
505 return isPassed() ? Status.PASSED : Status.FAILED;
506 }
507
508 if(pr.isPassed()) {
509 return isPassed() ? Status.PASSED : Status.REGRESSION;
510 } else {
511 return isPassed() ? Status.FIXED : Status.FAILED;
512 }
513 }
514
515 /*package*/ void setClass(ClassResult classResult) {
516 this.classResult = classResult;
517 }
518
519 void replaceParent(SuiteResult parent) {
520 this.parent = parent;
521 }
522
523 /**
524 * Constants that represent the status of this test.
525 */
526 public enum Status {
527 /**
528 * This test runs OK, just like its previous run.
529 */
530 PASSED("result-passed",Messages._CaseResult_Status_Passed(),true),
531 /**
532 * This test was skipped due to configuration or the
533 * failure or skipping of a method that it depends on.
534 */
535 SKIPPED("result-skipped",Messages._CaseResult_Status_Skipped(),false),
536 /**
537 * This test failed, just like its previous run.
538 */
539 FAILED("result-failed",Messages._CaseResult_Status_Failed(),false),
540 /**
541 * This test has been failing, but now it runs OK.
542 */
543 FIXED("result-fixed",Messages._CaseResult_Status_Fixed(),true),
544 /**
545 * This test has been running OK, but now it failed.
546 */
547 REGRESSION("result-regression",Messages._CaseResult_Status_Regression(),false);
548
549 private final String cssClass;
550 private final Localizable message;
551 public final boolean isOK;
552
553 Status(String cssClass, Localizable message, boolean OK) {
554 this.cssClass = cssClass;
555 this.message = message;
556 isOK = OK;
557 }
558
559 public String getCssClass() {
560 return cssClass;
561 }
562
563 public String getMessage() {
564 return message.toString();
565 }
566
567 public boolean isRegression() {
568 return this==REGRESSION;
569 }
570 }
571
572 /**
573 * For sorting errors by age.
574 */
575 /*package*/ static final Comparator<CaseResult> BY_AGE = new Comparator<CaseResult>() {
576 public int compare(CaseResult lhs, CaseResult rhs) {
577 return lhs.getAge()-rhs.getAge();
578 }
579 };
580
581 private static final long serialVersionUID = 1L;
582 }
583