提交 92ca98c4 编写于 作者: K kohsuke

Merged revisions...

Merged revisions 24854,24862,24946,25411,25418-25419,25478,25699,25701,25770,25832,25834,26401-26404 via svnmerge from 
https://www.dev.java.net/svn/hudson/branches/test-result-refactor

........
  r24854 | bshine | 2009-12-22 12:52:32 -0800 (Tue, 22 Dec 2009) | 82 lines
  
  Generalize the junit-specific TestResult class into AbstractTestResult,
  and allow plugins to contribute test result parsers that generate
  AbstractTestResult subclasses.
  
  In order to support the increasing variety of test result file formats, we
  have added two extension points:
  
   * AbstractTestResult: a class that represents a general concept of a test
     result, without any language or implementation specifics.
  
   * AbstractTestResultParser: an API for parsing test result files and
     producing an instance of a concrete subclass of AbstractTestResult
  
   
   AbstractModelObject
      \
       TestObject 
          \
           AbstractTestResult is-a ExtensionPoint
                 \
                  |-- CaseResult 
                  |
                  |-- SimpleCaseResult
                  |
                  |-- TabulatedResult
                        \
                         |-- ClassResult
                         |
                         |-- MetaTabulatedResult
                               \
                                 |-- TestResult 
                                 |
                                 |-- PackageResult
  
  
   AbstractTestResultParser is-a ExtensionPoint
      \
       JUnitParser
  
  We have refactored the JUnit result publishing system so that it is "just"
  a subclass of AbstractTestResult. We have promoted functionality into
  AbstractTestResult from TestResult; we have also promoted jelly resources
  from TestResult up to AbstractTestResult. Together, these changes make it
  possible -- without much code -- to add a test result parser and a test
  result publisher that can participate in the test result ecosystem. See,
  for instance, TrivialTestResult, with its two supporting classes,
  TrivialTestResultAction and TrivialTestResultRecorder.
  
  We have moved the format-agnostic classes in the test result system into
  the hudson.tasks.test package, from hudson.tasks.junit:
     TestObject
     MetaTabulatedResult
     TabulatedResult
  
  We have attempted at every step to preserve backwards compatibility both
  with the existing APIs and with test results stored by earlier versions of
  Hudson. Numerous tests verify that the old APIs and historical data are
  still readable; all of the tests in hudson.tasks.junit operate on stored
  builds and test results created by Hudson around version 1.320. These
  tests have been created and expanded to exercise the HTML ui and the XML
  api of the test result publishing system, as well as the underlying java
  logic.
  
     hudson.tasks.junit.CaseResultTest
     hudson.tasks.junit.JUnitParserTest
     hudson.tasks.junit.JUnitResultArchiverTest 
     hudson.tasks.junit.TestResultLinksTest 
     hudson.tasks.junit.TestResultPublishingTest   
     
  This checkin also includes several smaller changes that were enacted in the course of this work: 
     Added convenience/utility methods
        - HudsonTestCase.java
     
     Added a read-only textbox.
        - core/src/main/resources/lib/form/readOnlyTextbox.jelly
  
     Added a standalone builder that touches all files in the workspace.
        - TouchBuilder.java
  
     Added a test: testUnwrappedLongString
        - src/test/java/hudson/model/ApiTest.java
........
  r24862 | bshine | 2009-12-22 17:30:04 -0800 (Tue, 22 Dec 2009) | 1 line
  
  Recreate the hudson.tasks.junit.TestObject class, and adapt the TestResultAction.Data class to be friendly to the refactoring. This makes the claim plugin and the junit-attachments plugin compile and pass tests against this branch.
........
  r24946 | bshine | 2009-12-24 21:59:14 -0800 (Thu, 24 Dec 2009) | 1 line
  
  Getting rid of some TODOs that don't apply anymore. The TODOs that remain are meaningful.
........
  r25411 | kohsuke | 2010-01-05 18:14:15 -0800 (Tue, 05 Jan 2010) | 3 lines
  
  Cosmetic change. It's not necessary to define our own constant for empty list when there's already one in JDK.
  
  I also think creating and wasting singleton list as needed is probably better use of memory than having a singleton list tied in memory for every CaseResult. It improves cognitive overhead for people reading the code, and modern GCs are very good at handling short-lived objects, and I don't see this method called in a busy loop.
........
  r25418 | kohsuke | 2010-01-05 18:37:17 -0800 (Tue, 05 Jan 2010) | 1 line
  
  pushed the get/setDescription implemention up to the AbstractTestResultAction. I believe the default implementation is sufficient for most purposes, and typical subtypes don't want to bother with the details that descriptions are actually persisted by the owning Action instead of in TestObjects.
........
  r25419 | kohsuke | 2010-01-05 18:59:29 -0800 (Tue, 05 Jan 2010) | 1 line
  
  we should allow the parse method to be called outside of the build, so this should be TaskListener.
........
  r25478 | kohsuke | 2010-01-07 11:44:34 -0800 (Thu, 07 Jan 2010) | 14 lines
  
  The ExtensionPoint interface primarily serves as a marker to call attention of the plugin developers as to where to hook into Hudson. Since the act of implementing AbstractTestResultParser naturally make them to implement AbstractTestResult, I'm removing the ExtensionPoint marker from this abstract class.
  
  The all() method is removed after the following discussion:
  
  >> - I propose we remove AbstractTestResult.all(). I don't think being  
  >> able
  >> to enumerate AbstractTestResult subtypes buy us anything, since those
  >> can only be instantiated by parsers that intimately know how to
  >> instantiate them properly. Or did I miss the purpose of this?
  > 
  > I put in the all method because it was suggested by the wiki:
  > "You also need to define the static "all" method..."
  > http://wiki.hudson-ci.org/display/HUDSON/Defining+a+new+extension+point
  > so I don't have a problem with removing it.
........
  r25699 | bshine | 2010-01-11 16:34:12 -0800 (Mon, 11 Jan 2010) | 1 line
  
  Removed a test that exercised AbstractTestResult.all(), because that API has been removed.
........
  r25701 | bshine | 2010-01-11 17:05:40 -0800 (Mon, 11 Jan 2010) | 1 line
  
  Don't test getDescription() on a TrivialTestResult because the TrivialTestResult doesn't actually implement a description.
........
  r25770 | bshine | 2010-01-13 11:52:27 -0800 (Wed, 13 Jan 2010) | 1 line
  
  Added a failing test to demonstrate bug HUDSON-5246, inter-build diffs for junit test results are wrong.
........
  r25832 | bshine | 2010-01-14 13:50:15 -0800 (Thu, 14 Jan 2010) | 1 line
  
  The id field was accidentally declared here; it was also declared in hudson.tasks.test.TestObject. This id field here is always hidden by the id field in the more-concrete TestObject. Therefore, I'm removing this field.
........
  r25834 | bshine | 2010-01-14 13:55:10 -0800 (Thu, 14 Jan 2010) | 5 lines
  
  Fixes bugs with getPreviousResult:
  HUDSON-5269 TestResult.getPreviousResult returns null when it shouldn't
  HUDSON-5246 test-result-refactor branch: inter-build diffs for junit test results are wrong 
........
  r26401 | kohsuke | 2010-01-25 17:24:51 -0800 (Mon, 25 Jan 2010) | 7 lines
  
  renamed:
   AbstractTestResultParser -> TestResultParser
   AbstractTestResult -> TestResult
  
  This discussion is in http://n4.nabble.com/Review-requested-Test-Result-Refactoring-td978100.html#a978100
  
  The conclusion of this renaming proposal isn't clear, but I'm assuming it's OK.
........
  r26402 | kohsuke | 2010-01-25 17:27:57 -0800 (Mon, 25 Jan 2010) | 1 line
  
  elsewhere in Hudson it's the "display name" that indicates a human readable name that programs don't try to interpret.
........
  r26403 | kohsuke | 2010-01-25 17:31:38 -0800 (Mon, 25 Jan 2010) | 1 line
  
  typo
........
  r26404 | kohsuke | 2010-01-25 17:35:41 -0800 (Mon, 25 Jan 2010) | 1 line
  
  doc improvement.
........


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@26406 71c3de6d-444a-0410-be80-ed276b4c234a
上级 e30746eb
......@@ -27,7 +27,7 @@ import java.io.IOException;
/**
* Signals a failure where the error was anticipated and diagnosed.
* When this exception is caughted,
* When this exception is caught,
* the stack trace will not be printed, and the build will be marked as a failure.
*
* @author Kohsuke Kawaguchi
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Seiji Sogabe, Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Seiji Sogabe, Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -25,20 +25,24 @@ package hudson.tasks.junit;
import hudson.model.AbstractBuild;
import hudson.model.Run;
import hudson.tasks.test.TestResult;
import org.dom4j.Element;
import org.kohsuke.stapler.export.Exported;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.Comparator;
import java.util.*;
import java.util.logging.Logger;
import org.dom4j.Element;
import org.kohsuke.stapler.export.Exported;
import static java.util.Collections.emptyList;
/**
* One test result.
*
* @author Kohsuke Kawaguchi
*/
public final class CaseResult extends TestObject implements Comparable<CaseResult> {
public final class CaseResult extends TestResult implements Comparable<CaseResult> {
private static final Logger LOGGER = Logger.getLogger(CaseResult.class.getName());
private final float duration;
/**
* In JUnit, a test is a method of a class. This field holds the fully qualified class name
......@@ -127,7 +131,7 @@ public final class CaseResult extends TestObject implements Comparable<CaseResul
* Used to create a fake failure, when Hudson fails to load data from XML files.
*/
CaseResult(SuiteResult parent, String testName, String errorStackTrace) {
this( parent, parent.getName(), testName, errorStackTrace, "", null, null, 0.0f, false );
this( parent, (parent==null? "unnamed" : parent.getName()), testName, errorStackTrace, "", null, null, 0.0f, false );
}
CaseResult(SuiteResult parent, String testClassName, String testName, String errorStackTrace, String errorDetails, String stdout, String stderr, float duration, boolean skipped) {
......@@ -189,6 +193,14 @@ public final class CaseResult extends TestObject implements Comparable<CaseResul
return testName;
}
/**
* Gets the human readable title of this result object.
*/
@Override
public String getTitle() {
return "Case Result: " + getName();
}
/**
* Gets the duration of the test, in seconds
*/
......@@ -201,13 +213,14 @@ public final class CaseResult extends TestObject implements Comparable<CaseResul
* Gets the version of {@link #getName()} that's URL-safe.
*/
public @Override String getSafeName() {
StringBuffer buf = new StringBuffer(testName);
StringBuilder buf = new StringBuilder(testName);
for( int i=0; i<buf.length(); i++ ) {
char ch = buf.charAt(i);
if(!Character.isJavaIdentifierPart(ch))
buf.setCharAt(i,'_');
}
return uniquifyName(classResult.getChildren(), buf.toString());
Collection<CaseResult> siblings = (classResult ==null ? Collections.<CaseResult>emptyList(): classResult.getChildren());
return uniquifyName(siblings, buf.toString());
}
/**
......@@ -261,11 +274,24 @@ public final class CaseResult extends TestObject implements Comparable<CaseResul
*/
@Exported(visibility=9)
public int getFailedSince() {
// If we haven't calculated failedSince yet, and we should,
// do it now.
if (failedSince==0 && getFailCount()==1) {
CaseResult prev = getPreviousResult();
if(prev!=null && !prev.isPassed())
this.failedSince = prev.failedSince;
else if (getOwner() != null) {
this.failedSince = getOwner().getNumber();
} else {
LOGGER.warning("trouble calculating getFailedSince. We've got prev, but no owner.");
// failedSince will be 0, which isn't correct.
}
}
return failedSince;
}
public Run<?,?> getFailedSinceRun() {
return getOwner().getParent().getBuildByNumber(failedSince);
return getOwner().getParent().getBuildByNumber(getFailedSince());
}
/**
......@@ -276,8 +302,12 @@ public final class CaseResult extends TestObject implements Comparable<CaseResul
public int getAge() {
if(isPassed())
return 0;
else
return getOwner().getNumber()-failedSince+1;
else if (getOwner() != null) {
return getOwner().getNumber()-getFailedSince()+1;
} else {
LOGGER.fine("Trying to get age of a CaseResult without an owner");
return 0;
}
}
/**
......@@ -296,6 +326,8 @@ public final class CaseResult extends TestObject implements Comparable<CaseResul
@Exported
public String getStdout() {
if(stdout!=null) return stdout;
SuiteResult sr = getSuiteResult();
if (sr==null) return "";
return getSuiteResult().getStdout();
}
......@@ -308,21 +340,66 @@ public final class CaseResult extends TestObject implements Comparable<CaseResul
@Exported
public String getStderr() {
if(stderr!=null) return stderr;
SuiteResult sr = getSuiteResult();
if (sr==null) return "";
return getSuiteResult().getStderr();
}
@Override
public CaseResult getPreviousResult() {
if (parent == null) return null;
SuiteResult pr = parent.getPreviousResult();
if(pr==null) return null;
return pr.getCase(getName());
}
/**
* Case results have no children
* @return null
*/
@Override
public CaseResult getResultInBuild(AbstractBuild<?, ?> build) {
ClassResult pr = getParent().getResultInBuild(build);
if(pr==null) return null;
return pr.getCaseResult(getName());
public TestResult findCorrespondingResult(String id) {
if (id.equals(safe(getName()))) {
return this;
}
return null;
}
/**
* Gets the "children" of this test result that failed
*
* @return the children of this test result, if any, or an empty collection
*/
@Override
public Collection<? extends TestResult> getFailedTests() {
return singletonListOrEmpty(!isPassed());
}
/**
* Gets the "children" of this test result that passed
*
* @return the children of this test result, if any, or an empty collection
*/
@Override
public Collection<? extends TestResult> getPassedTests() {
return singletonListOrEmpty(isPassed());
}
/**
* Gets the "children" of this test result that were skipped
*
* @return the children of this test result, if any, or an empty list
*/
@Override
public Collection<? extends TestResult> getSkippedTests() {
return singletonListOrEmpty(isSkipped());
}
private Collection<? extends hudson.tasks.test.TestResult> singletonListOrEmpty(boolean f) {
if (f)
return Collections.singletonList(this);
else
return emptyList();
}
/**
......@@ -363,47 +440,20 @@ public final class CaseResult extends TestObject implements Comparable<CaseResul
return parent;
}
public String annotate(String text) {
if (text == null)
return null;
text = text.replace("&", "&amp;").replace("<", "&lt;").replaceAll(
"\\b(https?://[^\\s)>]+)", "<a href=\"$1\">$1</a>");
for (TestAction action: getTestActions()) {
text = action.annotate(text);
}
return text;
}
@Override
public AbstractBuild<?,?> getOwner() {
return getSuiteResult().getParent().getOwner();
SuiteResult sr = getSuiteResult();
if (sr==null) {
LOGGER.warning("In getOwner(), getSuiteResult is null"); return null; }
hudson.tasks.junit.TestResult tr = sr.getParent();
if (tr==null) {
LOGGER.warning("In getOwner(), suiteResult.getParent() is null."); return null; }
return tr.getOwner();
}
/**
* Gets the relative path to this test case from the given object.
*/
public String getRelativePathFrom(TestObject it) {
if(it==this)
return ".";
// package, then class
StringBuilder buf = new StringBuilder();
buf.append(getSafeName());
if(it!=classResult) {
buf.insert(0,'/');
buf.insert(0,classResult.getSafeName());
PackageResult pkg = classResult.getParent();
if(it!=pkg) {
buf.insert(0,'/');
buf.insert(0,pkg.getSafeName());
public void setParentSuiteResult(SuiteResult parent) {
this.parent = parent;
}
}
return buf.toString();
}
public void freeze(SuiteResult parent) {
this.parent = parent;
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, id:cactusman, Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, id:cactusman, Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -24,6 +24,9 @@
package hudson.tasks.junit;
import hudson.model.AbstractBuild;
import hudson.tasks.test.*;
import hudson.tasks.test.TestResult;
import hudson.tasks.test.TestObject;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
......@@ -53,27 +56,55 @@ public final class ClassResult extends TabulatedResult implements Comparable<Cla
this.className = className;
}
@Override
public AbstractBuild<?, ?> getOwner() {
return (parent==null ? null: parent.getOwner());
}
public PackageResult getParent() {
return parent;
}
@Override
public ClassResult getPreviousResult() {
PackageResult pr = parent.getPreviousResult();
if(parent==null) return null;
TestResult pr = parent.getPreviousResult();
if(pr==null) return null;
return pr.getClassResult(getName());
if(pr instanceof PackageResult) {
return ((PackageResult)pr).getClassResult(getName());
}
return null;
}
@Override
public ClassResult getResultInBuild(AbstractBuild<?, ?> build) {
PackageResult pr = getParent().getResultInBuild(build);
if(pr==null) return null;
return pr.getClassResult(getName());
public hudson.tasks.test.TestResult findCorrespondingResult(String id) {
String myID = safe(getName());
int base = id.indexOf(myID);
String caseName;
if (base > 0) {
int caseNameStart = base + myID.length() + 1;
caseName = id.substring(caseNameStart);
} else {
caseName = id;
}
CaseResult child = getCaseResult(caseName);
if (child != null) {
return child;
}
return null;
}
public String getTitle() {
return Messages.ClassResult_getTitle(getName());
}
@Override
public String getChildTitle() {
return "Class Reults";
}
@Exported(visibility=999)
public String getName() {
int idx = className.lastIndexOf('.');
......@@ -109,6 +140,10 @@ public final class ClassResult extends TabulatedResult implements Comparable<Cla
return cases;
}
public boolean hasChildren() {
return ((cases != null) && (cases.size() > 0));
}
// TODO: wait for stapler 1.60 @Exported
public float getDuration() {
return duration;
......@@ -133,6 +168,29 @@ public final class ClassResult extends TabulatedResult implements Comparable<Cla
cases.add(r);
}
/**
* Recount my children.
*/
@Override
public void tally() {
passCount=failCount=skipCount=0;
duration=0;
for (CaseResult r : cases) {
r.setClass(this);
if (r.isSkipped()) {
skipCount++;
}
else if(r.isPassed()) {
passCount++;
}
else {
failCount++;
}
duration += r.getDuration();
}
}
void freeze() {
passCount=failCount=skipCount=0;
duration=0;
......@@ -171,19 +229,12 @@ public final class ClassResult extends TabulatedResult implements Comparable<Cla
/**
* Gets the relative path to this test case from the given object.
*/
@Override
public String getRelativePathFrom(TestObject it) {
if(it==this)
return ".";
if (it instanceof TestResult) {
return getParent().getSafeName() + "/" + getSafeName();
} else if (it instanceof PackageResult) {
return getSafeName();
} else if (it instanceof CaseResult) {
if(it instanceof CaseResult) {
return "..";
} else {
throw new UnsupportedOperationException();
return super.getRelativePathFrom(it);
}
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -24,18 +24,10 @@
package hudson.tasks.junit;
import hudson.model.AbstractBuild;
import hudson.util.ChartUtil;
import hudson.util.ColorPalette;
import hudson.util.DataSetBuilder;
import hudson.util.ShiftedCategoryAxis;
import hudson.util.StackedAreaRenderer2;
import hudson.util.Graph;
import java.awt.Color;
import java.awt.Paint;
import java.util.ArrayList;
import java.util.List;
import hudson.model.Hudson;
import hudson.tasks.test.TestResult;
import hudson.tasks.test.TestObject;
import hudson.util.*;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
......@@ -47,8 +39,12 @@ import org.jfree.chart.renderer.category.StackedAreaRenderer;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleInsets;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* History of {@link TestObject} over time.
* History of {@link hudson.tasks.test.TestObject} over time.
*
* @since 1.320
*/
......@@ -63,11 +59,18 @@ public class History {
return testObject;
}
public List<TestObject> getList() {
List<TestObject> list = new ArrayList<TestObject>();
public boolean historyAvailable() {
if (testObject.getOwner().getParent().getBuilds().size() > 1)
return true;
else
return false;
}
public List<TestResult> getList() {
List<TestResult> list = new ArrayList<TestResult>();
for (AbstractBuild<?,?> b: testObject.getOwner().getParent().getBuilds()) {
if (b.isBuilding()) continue;
TestObject o = testObject.getResultInBuild(b);
TestResult o = testObject.getResultInBuild(b);
if (o != null) {
list.add(o);
}
......@@ -82,7 +85,7 @@ public class History {
return new GraphImpl("seconds") {
protected DataSetBuilder<String, ChartLabel> createDataSet() {
DataSetBuilder<String, ChartLabel> data = new DataSetBuilder<String, ChartLabel>();
for (TestObject o: getList()) {
for (hudson.tasks.test.TestResult o: getList()) {
data.add(((double) o.getDuration()) / (1000), "", new ChartLabel(o) {
@Override
public Color getColor() {
......@@ -108,7 +111,7 @@ public class History {
protected DataSetBuilder<String, ChartLabel> createDataSet() {
DataSetBuilder<String, ChartLabel> data = new DataSetBuilder<String, ChartLabel>();
for (TestObject o: getList()) {
for (TestResult o: getList()) {
data.add(o.getPassCount(), "2Passed", new ChartLabel(o));
data.add(o.getFailCount(), "1Failed", new ChartLabel(o));
data.add(o.getSkipCount(), "0Skipped", new ChartLabel(o));
......@@ -122,7 +125,7 @@ public class History {
private final String yLabel;
protected GraphImpl(String yLabel) {
super(testObject.getOwner().getTimestamp(),500,400);
super(testObject.getOwner().getTimestamp(),600,300);
this.yLabel = yLabel;
}
......@@ -179,7 +182,7 @@ public class History {
public String generateURL(CategoryDataset dataset, int row,
int column) {
ChartLabel label = (ChartLabel) dataset.getColumnKey(column);
return String.valueOf(label.o.getOwner().number);
return label.getUrl();
}
@Override
......@@ -203,9 +206,23 @@ public class History {
}
class ChartLabel implements Comparable<ChartLabel> {
TestObject o;
public ChartLabel(TestObject o) {
TestResult o;
String url;
public ChartLabel(TestResult o) {
this.o = o;
this.url = null;
}
public String getUrl() {
if (this.url == null) generateUrl();
return url;
}
private void generateUrl() {
AbstractBuild<?,?> build = o.getOwner();
String buildLink = build.getUrl();
String actionUrl = o.getTestResultAction().getUrlName();
this.url = Hudson.getInstance().getRootUrl() + buildLink + actionUrl + o.getUrl();
}
public int compareTo(ChartLabel that) {
......
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.junit;
import hudson.model.TaskListener;
import hudson.tasks.test.TestResultParser;
import hudson.model.AbstractBuild;
import hudson.*;
import hudson.remoting.VirtualChannel;
import java.io.IOException;
import java.io.File;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.DirectoryScanner;
/**
* Parse some JUnit xml files and generate a TestResult containing all the
* results parsed.
*/
@Extension
public class JUnitParser extends TestResultParser {
@Override
public String getDisplayName() {
return "JUnit Parser";
}
@Override
public String getTestResultLocationMessage() {
return "JUnit xml files:";
}
@Override
public TestResult parse(String testResultLocations,
AbstractBuild build, Launcher launcher,
TaskListener listener)
throws InterruptedException, IOException
{
final long buildTime = build.getTimestamp().getTimeInMillis();
final long timeOnMaster = System.currentTimeMillis();
// [BUG 3123310] TODO - Test Result Refactor: review and fix TestDataPublisher/TestAction subsystem]
// also get code that deals with testDataPublishers from JUnitResultArchiver.perform
TestResult testResult = build.getWorkspace().act( new ParseResultCallable(testResultLocations, buildTime, timeOnMaster));
return testResult;
}
private static final class ParseResultCallable implements
FilePath.FileCallable<TestResult> {
private final long buildTime;
private final String testResults;
private final long nowMaster;
private ParseResultCallable(String testResults, long buildTime, long nowMaster) {
this.buildTime = buildTime;
this.testResults = testResults;
this.nowMaster = nowMaster;
}
public TestResult invoke(File ws, VirtualChannel channel) throws IOException {
final long nowSlave = System.currentTimeMillis();
FileSet fs = Util.createFileSet(ws, testResults);
DirectoryScanner ds = fs.getDirectoryScanner();
String[] files = ds.getIncludedFiles();
if (files.length == 0) {
// no test result. Most likely a configuration
// error or fatal problem
throw new AbortException(Messages.JUnitResultArchiver_NoTestReportFound());
}
TestResult result = new TestResult(buildTime + (nowSlave - nowMaster), ds);
result.tally();
return result;
}
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Martin Eigenbrodt, Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Martin Eigenbrodt, Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -27,8 +27,6 @@ import hudson.AbortException;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.FilePath.FileCallable;
import hudson.matrix.MatrixAggregatable;
import hudson.matrix.MatrixAggregator;
import hudson.matrix.MatrixBuild;
......@@ -40,7 +38,6 @@ import hudson.model.CheckPoint;
import hudson.model.Descriptor;
import hudson.model.Result;
import hudson.model.Saveable;
import hudson.remoting.VirtualChannel;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
......@@ -50,17 +47,7 @@ import hudson.tasks.test.TestResultAggregator;
import hudson.tasks.test.TestResultProjectAction;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import net.sf.json.JSONObject;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;
import org.kohsuke.stapler.AncestorInPath;
......@@ -68,6 +55,13 @@ import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Generates HTML report from JUnit test result XML files.
*
......@@ -105,6 +99,16 @@ public class JUnitResultArchiver extends Recorder implements Serializable,
this.testDataPublishers = testDataPublishers;
}
/**
* In progress. Working on delegating the actual parsing to the JUnitParser.
*/
protected TestResult parse(String expandedTestResults, AbstractBuild build, Launcher launcher, BuildListener listener)
throws IOException, InterruptedException
{
TestResult result = (new JUnitParser()).parse(expandedTestResults, build, launcher, listener);
return result;
}
public boolean perform(AbstractBuild build, Launcher launcher,
BuildListener listener) throws InterruptedException, IOException {
listener.getLogger().println(Messages.JUnitResultArchiver_Recording());
......@@ -113,20 +117,18 @@ public class JUnitResultArchiver extends Recorder implements Serializable,
final String testResults = build.getEnvironment(listener).expand(this.testResults);
try {
final long buildTime = build.getTimeInMillis();
final long nowMaster = System.currentTimeMillis();
TestResult result = build.getWorkspace().act(
new ParseResultCallable(testResults, buildTime, nowMaster));
TestResult result = parse(testResults, build, launcher, listener);
try {
action = new TestResultAction(build, result, listener);
} catch (NullPointerException npe) {
throw new AbortException(Messages.JUnitResultArchiver_BadXML(testResults));
}
result.freeze(action);
if (result.getPassCount() == 0 && result.getFailCount() == 0)
throw new AbortException(Messages.JUnitResultArchiver_ResultIsEmpty());
// TODO: Move into JUnitParser [BUG 3123310]
List<Data> data = new ArrayList<Data>();
if (testDataPublishers != null) {
for (TestDataPublisher tdp : testDataPublishers) {
......@@ -208,34 +210,6 @@ public class JUnitResultArchiver extends Recorder implements Serializable,
private static final long serialVersionUID = 1L;
private static final class ParseResultCallable implements
FileCallable<TestResult> {
private final long buildTime;
private final String testResults;
private final long nowMaster;
private ParseResultCallable(String testResults, long buildTime, long nowMaster) {
this.buildTime = buildTime;
this.testResults = testResults;
this.nowMaster = nowMaster;
}
public TestResult invoke(File ws, VirtualChannel channel) throws IOException {
final long nowSlave = System.currentTimeMillis();
FileSet fs = Util.createFileSet(ws, testResults);
DirectoryScanner ds = fs.getDirectoryScanner();
String[] files = ds.getIncludedFiles();
if (files.length == 0) {
// no test result. Most likely a configuration
// error or fatal problem
throw new AbortException(Messages.JUnitResultArchiver_NoTestReportFound());
}
return new TestResult(buildTime + (nowSlave - nowMaster), ds);
}
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, id:cactusman, Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, id:cactusman, Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -24,16 +24,13 @@
package hudson.tasks.junit;
import hudson.model.AbstractBuild;
import hudson.tasks.test.*;
import hudson.tasks.test.TestResult;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.*;
/**
* Cumulative test result for a package.
......@@ -41,24 +38,27 @@ import java.util.TreeMap;
* @author Kohsuke Kawaguchi
*/
public final class PackageResult extends MetaTabulatedResult implements Comparable<PackageResult> {
private final String packageName;
private final String packageName;
/**
* All {@link ClassResult}s keyed by their short name.
*/
private final Map<String,ClassResult> classes = new TreeMap<String,ClassResult>();
private int passCount,failCount,skipCount;
private final TestResult parent;
private final hudson.tasks.junit.TestResult parent;
private float duration;
PackageResult(TestResult parent, String packageName) {
PackageResult(hudson.tasks.junit.TestResult parent, String packageName) {
this.packageName = packageName;
this.parent = parent;
}
public TestResult getParent() {
@Override
public AbstractBuild<?, ?> getOwner() {
return (parent == null ? null : parent.getOwner());
}
public hudson.tasks.junit.TestResult getParent() {
return parent;
}
......@@ -67,21 +67,45 @@ public final class PackageResult extends MetaTabulatedResult implements Comparab
return packageName;
}
public @Override String getSafeName() {
return uniquifyName(parent.getChildren(), safe(getName()));
@Override
public String getSafeName() {
Collection<PackageResult> siblings = (parent == null ? Collections.EMPTY_LIST : parent.getChildren());
return uniquifyName(
siblings,
safe(getName()));
}
@Override
public TestResult findCorrespondingResult(String id) {
String myID = safe(getName());
int base = id.indexOf(myID);
String className;
String subId = null;
if (base > 0) {
int classNameStart = base + myID.length() + 1;
className = id.substring(classNameStart);
} else {
className = id;
}
int classNameEnd = className.indexOf('/');
if (classNameEnd > 0) {
subId = className.substring(classNameEnd + 1);
if (subId.length() == 0) {
subId = null;
}
className = className.substring(0, classNameEnd);
}
public PackageResult getPreviousResult() {
TestResult tr = parent.getPreviousResult();
if(tr==null) return null;
return tr.byPackage(getName());
ClassResult child = getClassResult(className);
if (child != null) {
if (subId != null) {
return child.findCorrespondingResult(subId);
} else {
return child;
}
}
@Override
public PackageResult getResultInBuild(AbstractBuild<?, ?> build) {
TestResult tr = parent.getResultInBuild(build);
if(tr==null) return null;
return tr.byPackage(getName());
return null;
}
public String getTitle() {
......@@ -131,29 +155,135 @@ public final class PackageResult extends MetaTabulatedResult implements Comparab
return classes.values();
}
/**
* Whether this test result has children.
*/
@Override
public boolean hasChildren() {
int totalTests = passCount + failCount + skipCount;
return (totalTests != 0);
}
/**
* Returns a list of the failed cases, in no particular
* sort order
* @return
*/
public List<CaseResult> getFailedTests() {
List<CaseResult> r = new ArrayList<CaseResult>();
for (ClassResult clr : classes.values()) {
for (CaseResult cr : clr.getChildren()) {
if(!cr.isPassed() && !cr.isSkipped())
if (!cr.isPassed() && !cr.isSkipped()) {
r.add(cr);
}
}
}
return r;
}
/**
* Returns a list of the failed cases, sorted by age.
* @return
*/
public List<CaseResult> getFailedTestsSortedByAge() {
List<CaseResult> failedTests = getFailedTests();
Collections.sort(failedTests, CaseResult.BY_AGE);
return failedTests;
}
/**
* Gets the "children" of this test result that passed
*
* @return the children of this test result, if any, or an empty collection
*/
@Override
public Collection<? extends hudson.tasks.test.TestResult> getPassedTests() {
List<CaseResult> r = new ArrayList<CaseResult>();
for (ClassResult clr : classes.values()) {
for (CaseResult cr : clr.getChildren()) {
if (cr.isPassed()) {
r.add(cr);
}
}
}
Collections.sort(r,CaseResult.BY_AGE);
return r;
}
/**
* Gets the "children" of this test result that were skipped
*
* @return the children of this test result, if any, or an empty list
*/
@Override
public Collection<? extends TestResult> getSkippedTests() {
List<CaseResult> r = new ArrayList<CaseResult>();
for (ClassResult clr : classes.values()) {
for (CaseResult cr : clr.getChildren()) {
if (cr.isSkipped()) {
r.add(cr);
}
}
}
Collections.sort(r, CaseResult.BY_AGE);
return r;
}
// /**
// * If this test failed, then return the build number
// * when this test started failing.
// */
// @Override
// TODO: implement! public int getFailedSince() {
// return 0; // (FIXME: generated)
// }
// /**
// * If this test failed, then return the run
// * when this test started failing.
// */
// TODO: implement! @Override
// public Run<?, ?> getFailedSinceRun() {
// return null; // (FIXME: generated)
// }
/**
* @return true if every test was not skipped and every test did not fail, false otherwise.
*/
@Override
public boolean isPassed() {
return (failCount == 0 && skipCount == 0);
}
void add(CaseResult r) {
String n = r.getSimpleName(), sn = safe(n);
ClassResult c = getClassResult(sn);
if(c==null)
if (c == null) {
classes.put(sn,c=new ClassResult(this,n));
}
c.add(r);
duration += r.getDuration();
}
/**
* Recount my children
*/
@Override
public void tally() {
passCount = 0;
failCount = 0;
skipCount = 0;
duration = 0;
for (ClassResult cr : classes.values()) {
cr.tally();
passCount += cr.getPassCount();
failCount += cr.getFailCount();
skipCount += cr.getSkipCount();
duration += cr.getDuration();
}
}
void freeze() {
passCount=failCount=0;
passCount = failCount = skipCount = 0;
for (ClassResult cr : classes.values()) {
cr.freeze();
passCount += cr.getPassCount();
......@@ -162,7 +292,6 @@ public final class PackageResult extends MetaTabulatedResult implements Comparab
}
}
public int compareTo(PackageResult that) {
return this.packageName.compareTo(that.packageName);
}
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Xavier Le Vourch, Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Xavier Le Vourch, Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -23,13 +23,7 @@
*/
package hudson.tasks.junit;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import hudson.tasks.test.TestObject;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
......@@ -37,6 +31,13 @@ import org.dom4j.io.SAXReader;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Result of one test suite.
*
......@@ -67,7 +68,7 @@ public final class SuiteResult implements Serializable {
* All test cases.
*/
private final List<CaseResult> cases = new ArrayList<CaseResult>();
private transient TestResult parent;
private transient hudson.tasks.junit.TestResult parent;
SuiteResult(String name, String stdout, String stderr) {
this.name = name;
......@@ -196,7 +197,7 @@ public final class SuiteResult implements Serializable {
return file;
}
public TestResult getParent() {
public hudson.tasks.junit.TestResult getParent() {
return parent;
}
......@@ -211,9 +212,11 @@ public final class SuiteResult implements Serializable {
}
public SuiteResult getPreviousResult() {
TestResult pr = parent.getPreviousResult();
hudson.tasks.test.TestResult pr = parent.getPreviousResult();
if(pr==null) return null;
return pr.getSuite(name);
if(pr instanceof hudson.tasks.junit.TestResult)
return ((hudson.tasks.junit.TestResult)pr).getSuite(name);
return null;
}
/**
......@@ -239,7 +242,17 @@ public final class SuiteResult implements Serializable {
return result;
}
/*package*/ boolean freeze(TestResult owner) {
/** KLUGE. We have to call this to prevent freeze()
* from calling c.freeze() on all its children,
* because that in turn calls c.getOwner(),
* which requires a non-null parent.
* @param parent
*/
void setParent(hudson.tasks.junit.TestResult parent) {
this.parent = parent;
}
/*package*/ boolean freeze(hudson.tasks.junit.TestResult owner) {
if(this.parent!=null)
return false; // already frozen
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -23,17 +23,13 @@
*/
package hudson.tasks.junit;
import java.io.IOException;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.Launcher;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.model.*;
import java.io.IOException;
/**
* Contributes {@link TestAction}s to test results.
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, Yahoo! Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -39,6 +39,7 @@ import java.util.WeakHashMap;
import javax.servlet.ServletException;
import hudson.tasks.test.AbstractTestResultAction;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
......@@ -47,59 +48,35 @@ import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.ExportedBean;
/**
* Base class for all test result objects.
* Stub of base class for all test result objects. The real implementation of
* the TestObject is in hudson.tasks.test.TestObject. This class simply
* defines abstract methods so that legacy code will continue to compile.
* @deprecated
*
* @author Kohsuke Kawaguchi
*/
@ExportedBean
public abstract class TestObject extends AbstractModelObject implements Serializable {
private volatile transient String id;
public abstract AbstractBuild<?,?> getOwner() ;
public AbstractBuild<?,?> getOwner() {
return getParent().getOwner();
}
/**
* Reverse pointer of {@link TabulatedResult#getChildren()}.
*/
public abstract TestObject getParent();
public String getId() {
if (id == null) {
id = getParent().getId() + "/" + getSafeName();
}
return id;
}
public abstract String getId();
/**
* Returns url relative to TestResult
*/
public String getUrl() {
return getId();
}
public TestResult getTestResult() {
return getParent().getTestResult();
}
public TestResultAction getTestResultAction() {
return getOwner().getAction(TestResultAction.class);
}
public List<TestAction> getTestActions() {
return getTestResultAction().getActions(this);
}
public <T> T getTestAction(Class<T> klazz) {
for (TestAction action : getTestActions()) {
if (klazz.isAssignableFrom(action.getClass())) {
return klazz.cast(action);
}
}
return null;
}
public abstract String getUrl();
/**
public abstract TestResult getTestResult();
public abstract AbstractTestResultAction getTestResultAction();
public abstract List<TestAction> getTestActions();
public abstract <T> T getTestAction(Class<T> klazz);
/**
* Gets the counter part of this {@link TestObject} in the previous run.
*
* @return null if no such counter part exists.
......@@ -117,70 +94,29 @@ public abstract class TestObject extends AbstractModelObject implements Serializ
* Returns the string representation of the {@link #getDuration()}, in a
* human readable format.
*/
public String getDurationString() {
return Util.getTimeSpanString((long) (getDuration() * 1000));
}
public String getDescription() {
return getTestResultAction().getDescription(this);
}
public void setDescription(String description) {
getTestResultAction().setDescription(this, description);
}
public abstract String getDurationString();
/**
public abstract String getDescription();
public abstract void setDescription(String description);
/**
* Exposes this object through the remote API.
*/
public Api getApi() {
return new Api(this);
}
public abstract Api getApi();
/**
/**
* Gets the name of this object.
*/
public/* abstract */String getName() {
return "";
}
public abstract String getName();
/**
/**
* Gets the version of {@link #getName()} that's URL-safe.
*/
public String getSafeName() {
return safe(getName());
}
public String getSearchUrl() {
return getSafeName();
}
public abstract String getSafeName();
/**
* #2988: uniquifies a {@link #getSafeName} amongst children of the parent.
*/
protected final synchronized String uniquifyName(
Collection<? extends TestObject> siblings, String base) {
String uniquified = base;
int sequence = 1;
for (TestObject sibling : siblings) {
if (sibling != this
&& uniquified.equals(UNIQUIFIED_NAMES.get(sibling))) {
uniquified = base + '_' + ++sequence;
}
}
UNIQUIFIED_NAMES.put(this, uniquified);
return uniquified;
}
private static final Map<TestObject, String> UNIQUIFIED_NAMES = new WeakHashMap<TestObject, String>();
public abstract String getSearchUrl();
/**
* Replaces URL-unsafe characters.
*/
protected static String safe(String s) {
// 3 replace calls is still 2-3x faster than a regex replaceAll
return s.replace('/', '_').replace('\\', '_').replace(':', '_');
}
/**
* Gets the total number of passed tests.
*/
......@@ -199,38 +135,15 @@ public abstract class TestObject extends AbstractModelObject implements Serializ
/**
* Gets the total number of tests.
*/
public final int getTotalCount() {
return getPassCount()+getFailCount()+getSkipCount();
}
public History getHistory() {
return new History(this);
}
public Object getDynamic(String token, StaplerRequest req,
StaplerResponse rsp) {
for (Action a : getTestActions()) {
if (a == null)
continue; // be defensive
String urlName = a.getUrlName();
if (urlName == null)
continue;
if (urlName.equals(token))
return a;
}
return null;
}
public synchronized HttpResponse doSubmitDescription(
@QueryParameter String description) throws IOException,
ServletException {
getOwner().checkPermission(Item.BUILD);
setDescription(description);
getOwner().save();
return new HttpRedirect(".");
}
private static final long serialVersionUID = 1L;
public abstract int getTotalCount();
public abstract History getHistory();
// public abstract Object getDynamic(String token, StaplerRequest req,
// StaplerResponse rsp);
//
// public abstract HttpResponse doSubmitDescription(
// @QueryParameter String description) throws IOException,
// ServletException;
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, id:cactusman, Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, id:cactusman, Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -26,32 +26,32 @@ package hudson.tasks.junit;
import hudson.AbortException;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.Run;
import hudson.tasks.test.MetaTabulatedResult;
import hudson.tasks.test.TestObject;
import hudson.tasks.test.AbstractTestResultAction;
import hudson.util.IOException2;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.tools.ant.DirectoryScanner;
import org.dom4j.DocumentException;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import java.util.logging.Logger;
/**
* Root of all the test results for one build.
*
* @author Kohsuke Kawaguchi
*/
public final class TestResult extends MetaTabulatedResult {
private static final Logger LOGGER = Logger.getLogger(TestResult.class.getName());
/**
* List of all {@link SuiteResult}s in this test.
* This is the core data structure to be persisted in the disk.
......@@ -69,7 +69,9 @@ public final class TestResult extends MetaTabulatedResult {
private transient Map<String,PackageResult> byPackages;
// set during the freeze phase
private transient TestResultAction parent;
private transient AbstractTestResultAction parentAction;
private transient TestObject parent;
/**
* Number of all tests.
......@@ -88,7 +90,7 @@ public final class TestResult extends MetaTabulatedResult {
/**
* Creates an empty result.
*/
TestResult() {
public TestResult() {
}
/**
......@@ -96,16 +98,17 @@ public final class TestResult extends MetaTabulatedResult {
* filtering out all files that were created before the given time.
*/
public TestResult(long buildTime, DirectoryScanner results) throws IOException {
this();
parse(buildTime, results);
}
public TestObject getParent() {
return null;
return parent;
}
@Override
public String getId() {
return "";
public void setParent(TestObject parent) {
this.parent = parent;
}
@Override
......@@ -126,7 +129,7 @@ public final class TestResult extends MetaTabulatedResult {
for (String value : includedFiles) {
File reportFile = new File(baseDir, value);
// only count files that were actually updated during this build
if(buildTime-3000/*error margin*/ <= reportFile.lastModified()) {
if ( (buildTime-3000/*error margin*/ <= reportFile.lastModified()) || !checkTimestamps) {
if(reportFile.length()==0) {
// this is a typical problem when JVM quits abnormally, like OutOfMemoryError during a test.
SuiteResult sr = new SuiteResult(reportFile.getName(), "", "");
......@@ -203,34 +206,67 @@ public final class TestResult extends MetaTabulatedResult {
@Override
public AbstractBuild<?,?> getOwner() {
return parent.owner;
return (parentAction == null? null: parentAction.owner);
}
@Override
public TestResult getPreviousResult() {
TestResultAction p = parent.getPreviousResult();
if(p!=null)
return p.getResult();
else
public hudson.tasks.test.TestResult findCorrespondingResult(String id) {
if (getId().equals(id) || (id == null)) {
return this;
}
String firstElement = null;
String subId = null;
int sepIndex = id.indexOf('/');
if (sepIndex < 0) {
firstElement = id;
subId = null;
} else {
firstElement = id.substring(0, sepIndex);
subId = id.substring(sepIndex + 1);
if (subId.length() == 0) {
subId = null;
}
}
String packageName = null;
if (firstElement.equals(getId())) {
sepIndex = subId.indexOf('/');
if (sepIndex < 0) {
packageName = subId;
subId = null;
} else {
packageName = subId.substring(0, sepIndex);
subId = subId.substring(sepIndex + 1);
}
} else {
packageName = firstElement;
subId = null;
}
PackageResult child = byPackage(packageName);
if (child != null) {
if (subId != null) {
return child.findCorrespondingResult(subId);
} else {
return child;
}
} else {
return null;
}
}
@Override
public TestResult getResultInBuild(AbstractBuild<?, ?> build) {
TestResultAction tra = build.getAction(TestResultAction.class);
if (tra == null) return null;
return tra.getResult();
}
public String getTitle() {
return Messages.TestResult_getTitle();
}
@Override
public String getChildTitle() {
return Messages.TestResult_getChildTitle();
}
@Exported(visibility=999)
@Override
public float getDuration() {
return duration;
}
......@@ -244,6 +280,9 @@ public final class TestResult extends MetaTabulatedResult {
@Exported(visibility=999)
@Override
public int getFailCount() {
if(failedTests==null)
return 0;
else
return failedTests.size();
}
......@@ -258,23 +297,138 @@ public final class TestResult extends MetaTabulatedResult {
return failedTests;
}
/**
* Gets the "children" of this test result that passed
*
* @return the children of this test result, if any, or an empty collection
*/
@Override
public Collection<? extends hudson.tasks.test.TestResult> getPassedTests() {
throw new UnsupportedOperationException(); // TODO: implement!(FIXME: generated)
}
/**
* Gets the "children" of this test result that were skipped
*
* @return the children of this test result, if any, or an empty list
*/
@Override
public Collection<? extends hudson.tasks.test.TestResult> getSkippedTests() {
throw new UnsupportedOperationException(); // TODO: implement!(FIXME: generated)
}
/**
* If this test failed, then return the build number
* when this test started failing.
*/
@Override
public int getFailedSince() {
throw new UnsupportedOperationException(); // TODO: implement!(FIXME: generated)
}
/**
* If this test failed, then return the run
* when this test started failing.
*/
@Override
public Run<?, ?> getFailedSinceRun() {
throw new UnsupportedOperationException(); // TODO: implement!(FIXME: generated)
}
/**
* The stdout of this test.
* <p/>
* <p/>
* Depending on the tool that produced the XML report, this method works somewhat inconsistently.
* With some tools (such as Maven surefire plugin), you get the accurate information, that is
* the stdout from this test case. With some other tools (such as the JUnit task in Ant), this
* method returns the stdout produced by the entire test suite.
* <p/>
* <p/>
* If you need to know which is the case, compare this output from {@link SuiteResult#getStdout()}.
*
* @since 1.294
*/
@Override
public String getStdout() {
StringBuilder sb = new StringBuilder();
for (SuiteResult suite: suites) {
sb.append("Standard Out (stdout) for Suite: " + suite.getName());
sb.append(suite.getStdout());
}
return sb.toString();
}
/**
* The stderr of this test.
*
* @see #getStdout()
* @since 1.294
*/
@Override
public String getStderr() {
StringBuilder sb = new StringBuilder();
for (SuiteResult suite: suites) {
sb.append("Standard Error (stderr) for Suite: " + suite.getName());
sb.append(suite.getStderr());
}
return sb.toString();
}
/**
* If there was an error or a failure, this is the stack trace, or otherwise null.
*/
@Override
public String getErrorStackTrace() {
return "No error stack traces available at this level. Drill down to individual tests to find stack traces.";
}
/**
* If there was an error or a failure, this is the text from the message.
*/
@Override
public String getErrorDetails() {
return "No error details available at this level. Drill down to individual tests to find details.";
}
/**
* @return true if the test was not skipped and did not fail, false otherwise.
*/
@Override
public boolean isPassed() {
return (getFailCount() == 0);
}
@Override
public Collection<PackageResult> getChildren() {
return byPackages.values();
}
/**
* Whether this test result has children.
*/
@Override
public boolean hasChildren() {
return !suites.isEmpty();
}
@Exported(inline=true,visibility=9)
public Collection<SuiteResult> getSuites() {
return suites;
}
@Override
public String getName() {
return "";
return "junit";
}
@Override
public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
if (token.equals(getId())) {
return this;
}
PackageResult result = byPackage(token);
if (result != null) {
return result;
......@@ -291,6 +445,57 @@ public final class TestResult extends MetaTabulatedResult {
return suitesByName.get(name);
}
@Override
public void setParentAction(AbstractTestResultAction action) {
this.parentAction = action;
tally(); // I want to be sure to inform our children when we get an action.
}
@Override
public AbstractTestResultAction getParentAction() {
return this.parentAction;
}
/**
* Recount my children.
*/
@Override
public void tally() {
/// Empty out data structures
// TODO: free children? memmory leak?
suitesByName = new HashMap<String,SuiteResult>();
failedTests = new ArrayList<CaseResult>();
byPackages = new TreeMap<String,PackageResult>();
totalTests = 0;
skippedTests = 0;
// Ask all of our children to tally themselves
for (SuiteResult s : suites) {
s.setParent(this); // kluge to prevent double-counting the results
suitesByName.put(s.getName(),s);
List<CaseResult> cases = s.getCases();
for (CaseResult cr: cases) {
cr.setParentAction(this.parentAction);
cr.setParentSuiteResult(s);
cr.tally();
String pkg = cr.getPackageName(), spkg = safe(pkg);
PackageResult pr = byPackage(spkg);
if(pr==null)
byPackages.put(spkg,pr=new PackageResult(this,pkg));
pr.add(cr);
}
}
for (PackageResult pr : byPackages.values()) {
pr.tally();
skippedTests += pr.getSkipCount();
failedTests.addAll(pr.getFailedTests());
totalTests += pr.getTotalCount();
}
}
/**
* Builds up the transient part of the data structure
* from results {@link #parse(File) parsed} so far.
......@@ -300,7 +505,7 @@ public final class TestResult extends MetaTabulatedResult {
* and then freeze can be called again.
*/
public void freeze(TestResultAction parent) {
this.parent = parent;
this.parentAction = parent;
if(suitesByName==null) {
// freeze for the first time
suitesByName = new HashMap<String,SuiteResult>();
......@@ -310,7 +515,7 @@ public final class TestResult extends MetaTabulatedResult {
}
for (SuiteResult s : suites) {
if(!s.freeze(this))
if(!s.freeze(this)) // this is disturbing: has-a-parent is conflated with has-been-counted
continue;
suitesByName.put(s.getName(),s);
......@@ -337,4 +542,6 @@ public final class TestResult extends MetaTabulatedResult {
}
private static final long serialVersionUID = 1L;
private static final boolean checkTimestamps = true; // TODO: change to System.getProperty
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Red Hat, Inc., Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Red Hat, Inc., Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -23,13 +23,16 @@
*/
package hudson.tasks.junit;
import com.thoughtworks.xstream.XStream;
import hudson.XmlFile;
import hudson.model.AbstractBuild;
import hudson.model.Action;
import hudson.model.BuildListener;
import hudson.tasks.test.AbstractTestResultAction;
import hudson.tasks.test.TestObject;
import hudson.util.HeapSpaceStringConverter;
import hudson.util.XStream2;
import org.kohsuke.stapler.StaplerProxy;
import java.io.File;
import java.io.IOException;
......@@ -42,10 +45,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.kohsuke.stapler.StaplerProxy;
import com.thoughtworks.xstream.XStream;
/**
* {@link Action} that displays the JUnit test result.
*
......@@ -64,7 +63,6 @@ public class TestResultAction extends AbstractTestResultAction<TestResultAction>
private int skipCount;
private Integer totalCount;
private List<Data> testData = new ArrayList<Data>();
private Map<String,String> descriptions = new ConcurrentHashMap<String, String>();
public TestResultAction(AbstractBuild owner, TestResult result, BuildListener listener) {
super(owner);
......@@ -161,14 +159,6 @@ public class TestResultAction extends AbstractTestResultAction<TestResultAction>
return getResult();
}
public String getDescription(TestObject object) {
return descriptions.get(object.getId());
}
public void setDescription(TestObject object, String description) {
descriptions.put(object.getId(), description);
}
public List<TestAction> getActions(TestObject object) {
List<TestAction> result = new ArrayList<TestAction>();
// Added check for null testData to avoid NPE from issue 4257.
......@@ -199,13 +189,11 @@ public class TestResultAction extends AbstractTestResultAction<TestResultAction>
* @return
* Can be empty but never null. The caller must assume that the returned list is read-only.
*/
public abstract List<TestAction> getTestAction(TestObject testObject);
public abstract List<TestAction> getTestAction(hudson.tasks.junit.TestObject testObject);
}
public Object readResolve() {
if (descriptions == null) {
descriptions = new ConcurrentHashMap<String, String>();
}
super.readResolve(); // let it do the post-deserialization work
if (testData == null) {
testData = new ArrayList<Data>();
}
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Red Hat, Inc., Stephen Connolly, id:cactusman
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Red Hat, Inc., Stephen Connolly, id:cactusman, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -26,13 +26,8 @@ package hudson.tasks.test;
import hudson.Functions;
import hudson.model.*;
import hudson.tasks.junit.CaseResult;
import hudson.util.ChartUtil;
import hudson.util.*;
import hudson.util.ChartUtil.NumberOnlyBuildLabel;
import hudson.util.ColorPalette;
import hudson.util.DataSetBuilder;
import hudson.util.ShiftedCategoryAxis;
import hudson.util.StackedAreaRenderer2;
import hudson.util.Area;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
......@@ -43,16 +38,18 @@ import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.StackedAreaRenderer;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleInsets;
import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.export.Exported;
import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.export.ExportedBean;
import java.awt.Color;
import java.awt.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Common base class for recording test result.
......@@ -67,6 +64,8 @@ import java.util.List;
public abstract class AbstractTestResultAction<T extends AbstractTestResultAction> implements HealthReportingAction {
public final AbstractBuild<?,?> owner;
private Map<String,String> descriptions = new ConcurrentHashMap<String, String>();
protected AbstractTestResultAction(AbstractBuild owner) {
this.owner = owner;
}
......@@ -170,6 +169,20 @@ public abstract class AbstractTestResultAction<T extends AbstractTestResultActio
}
}
public TestResult findPreviousCorresponding(TestResult test) {
T previousResult = getPreviousResult();
if (previousResult != null) {
TestResult testResult = (TestResult)getResult();
return testResult.findCorrespondingResult(test.getId());
}
return null;
}
public TestResult findCorrespondingResult(String id) {
return ((TestResult)getResult()).findCorrespondingResult(id);
}
/**
* A shortcut for summary.jelly
*
......@@ -205,9 +218,9 @@ public abstract class AbstractTestResultAction<T extends AbstractTestResultActio
}
/**
*
* Returns a full path down to a test result
*/
public String getTestResultPath(CaseResult it) {
public String getTestResultPath(TestResult it) {
return getUrlName() + "/" + it.getRelativePathFrom(null);
}
......@@ -322,4 +335,30 @@ public abstract class AbstractTestResultAction<T extends AbstractTestResultActio
if(relPath==null) return "";
return relPath;
}
/**
* {@link TestObject}s do not have their own persistence mechanism, so updatable data of {@link TestObject}s
* need to be persisted by the owning {@link AbstractTestResultAction}, and this method and
* {@link #setDescription(TestObject, String)} provides that logic.
*
* <p>
* The default implementation stores information in the 'this' object.
*
* @see TestObject#getDescription()
*/
protected String getDescription(TestObject object) {
return descriptions.get(object.getId());
}
protected void setDescription(TestObject object, String description) {
descriptions.put(object.getId(), description);
}
public Object readResolve() {
if (descriptions == null) {
descriptions = new ConcurrentHashMap<String, String>();
}
return this;
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Red Hat, Inc.
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Red Hat, Inc., Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -160,4 +160,28 @@ public abstract class AggregatedTestResultAction extends AbstractTestResultActio
if(b==null) return null;
return b.getAction(AbstractTestResultAction.class);
}
/**
* Since there's no TestObject that points this action as the owner
* (aggregated {@link TestObject}s point to their respective real owners, not 'this'),
* so this method should be never invoked.
*
* @deprecated
* so that IDE warns you if you accidentally try to call it.
*/
@Override
protected final String getDescription(TestObject object) {
throw new AssertionError();
}
/**
* See {@link #getDescription(TestObject)}
*
* @deprecated
* so that IDE warns you if you accidentally try to call it.
*/
@Override
protected final void setDescription(TestObject object, String description) {
throw new AssertionError();
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Michael B. Donohue
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Michael B. Donohue, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -23,30 +23,23 @@
*/
package hudson.tasks.test;
import hudson.Extension;
import hudson.Launcher;
import hudson.Util;
import hudson.Extension;
import static hudson.Util.fixNull;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.*;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Fingerprinter.FingerprintAction;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.util.FormValidation;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.AncestorInPath;
import java.io.IOException;
import java.util.ArrayList;
......@@ -159,6 +152,30 @@ public class AggregatedTestResultPublisher extends Recorder {
return this;
}
/**
* Since there's no TestObject that points this action as the owner
* (aggregated {@link TestObject}s point to their respective real owners, not 'this'),
* so this method should be never invoked.
*
* @deprecated
* so that IDE warns you if you accidentally try to call it.
*/
@Override
protected final String getDescription(TestObject object) {
throw new AssertionError();
}
/**
* See {@link #getDescription(TestObject)}
*
* @deprecated
* so that IDE warns you if you accidentally try to call it.
*/
@Override
protected final void setDescription(TestObject object, String description) {
throw new AssertionError();
}
/**
* Returns the individual test results that are aggregated.
*/
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -23,12 +23,11 @@
*/
package hudson.tasks.test;
import hudson.model.AbstractBuild;
import hudson.model.Action;
import hudson.matrix.MatrixBuild;
import hudson.matrix.Combination;
import hudson.matrix.MatrixBuild;
import hudson.matrix.MatrixRun;
import hudson.tasks.junit.CaseResult;
import hudson.model.AbstractBuild;
import hudson.model.Action;
/**
* {@link Action} that aggregates all the test results from {@link MatrixRun}s.
......@@ -58,7 +57,7 @@ public class MatrixTestResult extends AggregatedTestResultAction {
}
@Override
public String getTestResultPath(CaseResult it) {
public String getTestResultPath(TestResult it) {
// Prepend Configuration path
return it.getOwner().getParent().getShortUrl() + super.getTestResultPath(it);
}
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -21,24 +21,24 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.junit;
package hudson.tasks.test;
import java.util.Collection;
import java.util.List;
/**
* The purpose of this class is to provide a good place for the
* jelly to bind to.
* {@link TabulatedResult} whose immediate children
* are other {@link TabulatedResult}s.
*
* @author Kohsuke Kawaguchi
*/
abstract class MetaTabulatedResult extends TabulatedResult {
public abstract String getChildTitle();
public abstract class MetaTabulatedResult extends TabulatedResult {
/**
* All failed tests.
*/
public abstract List<CaseResult> getFailedTests();
public abstract Collection<? extends TestResult> getFailedTests();
public abstract Collection<? extends TabulatedResult> getChildren();
}
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.test;
import hudson.model.AbstractBuild;
import hudson.tasks.junit.TestAction;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.logging.Logger;
import static java.util.Collections.emptyList;
/**
* The simplest possible case result, with no language ties.
* Acts as if it passed, has no children, and has no failed or skipped tests.
*/
public class SimpleCaseResult extends TestResult {
protected AbstractTestResultAction parentAction;
protected final List<SimpleCaseResult> listOnlyContainingThisObject = new ArrayList<SimpleCaseResult>(1);
protected float duration = 1.0f;
private static final Logger LOGGER = Logger.getLogger(SimpleCaseResult.class.getName());
public SimpleCaseResult(float duration) {
listOnlyContainingThisObject.add(this);
}
public SimpleCaseResult() {
this(1.0f);
}
/**
* Sets the parent action, which means the action that binds
* this particular case result to a build. Should not be null.
* @param parentAction
*/
@Override
public void setParentAction(AbstractTestResultAction parentAction) {
this.parentAction = parentAction;
}
@Override
public AbstractTestResultAction getParentAction() {
return this.parentAction;
}
@Override
public TestObject getParent() {
return null;
}
@Override
public TestResult findCorrespondingResult(String id) {
if (id.equals(getId())) {
return this;
}
return null;
}
/**
* Gets the "children" of this test result that failed
*
* @return the children of this test result, if any, or an empty collection
*/
@Override
public Collection<? extends TestResult> getFailedTests() {
return emptyList();
}
/**
* Gets the "children" of this test result that passed
*
* @return the children of this test result, if any, or an empty collection
*/
@Override
public Collection<? extends TestResult> getPassedTests() {
return listOnlyContainingThisObject;
}
/**
* Gets the "children" of this test result that were skipped
*
* @return the children of this test result, if any, or an empty list
*/
@Override
public Collection<? extends TestResult> getSkippedTests() {
return emptyList();
}
/**
* Let's pretend that our trivial test result always passes.
* @return always true
*/
@Override
public boolean isPassed() {
return true;
}
/**
* Tests whether the test was skipped or not.
*
* @return true if the test was not executed, false otherwise.
*/
public boolean isSkipped() {
return false;
}
/**
* Returns true iff this test failed.
*/
public boolean isFailed() {
return false;
}
/**
* Time took to run this test. In seconds.
*/
@Override
public float getDuration() {
return duration;
}
/**
* Gets the name of this object.
*/
@Override
public String getName() {
return "Simple Case Result";
}
/**
* Gets the total number of passed tests.
*/
@Override
public int getPassCount() {
return 1;
}
/**
* Gets the total number of failed tests.
*/
@Override
public int getFailCount() {
return 0;
}
/**
* Gets the total number of skipped tests.
*/
@Override
public int getSkipCount() {
return 0;
}
/**
* Gets the human readable title of this result object.
*/
@Override
public String getTitle() {
return "Simple Case Result"; //
}
public String getDisplayName() {
return "Simple Case Result";
}
@Override
public AbstractBuild<?,?> getOwner() {
if (parentAction == null) {
LOGGER.warning("in Trivial Test Result, parentAction is null, but getOwner() called");
return null;
}
return parentAction.owner;
}
@Override
public List<TestAction> getTestActions() {
return SimpleCaseResult.EMPTY_ACTION_LIST;
}
/**
* An empty list of actions, useful for tests
*/
public static final List<TestAction> EMPTY_ACTION_LIST = Collections.unmodifiableList(new ArrayList<TestAction>());
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -21,32 +21,31 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.junit;
package hudson.tasks.test;
import java.util.Collection;
/**
* Cumulated result of multiple tests.
*
* This class doesn't have a purpose anymore. TestResult
* is a sufficient replacement, but we'll keep this one for
* backward-compatibility.
*
* @author Kohsuke Kawaguchi
*/
public abstract class TabulatedResult extends TestObject {
/**
* Gets the human readable title of this result object.
*/
public abstract String getTitle();
public abstract class TabulatedResult extends TestResult {
/**
* Gets the child test result objects.
*
* @see TestObject#getParent()
*/
public abstract Collection<? extends TestObject> getChildren();
public abstract Collection<? extends TestResult> getChildren();
/**
* Gets the name of this object.
*/
public @Override abstract String getName();
public abstract boolean hasChildren();
public String getChildTitle() {
return "";
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.test;
import hudson.Util;
import hudson.Functions;
import hudson.model.*;
import hudson.tasks.junit.History;
import hudson.tasks.junit.TestAction;
import hudson.tasks.junit.TestResultAction;
import org.kohsuke.stapler.*;
import org.kohsuke.stapler.export.ExportedBean;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.*;
import java.util.logging.Logger;
/**
* Base class for all test result objects.
* For compatibility with code that expects this class to be in hudson.tasks.junit,
* we've created a pure-abstract class, hudson.tasks.junit.TestObject. That
* stub class is deprecated; instead, people should use this class.
*
* @author Kohsuke Kawaguchi
*/
@ExportedBean
public abstract class TestObject extends hudson.tasks.junit.TestObject {
private static final Logger LOGGER = Logger.getLogger(TestObject.class.getName());
private volatile transient String id;
public abstract AbstractBuild<?, ?> getOwner();
/**
* Reverse pointer of {@link TabulatedResult#getChildren()}.
*/
public abstract TestObject getParent();
@Override
public final String getId() {
if (id == null) {
StringBuilder buf = new StringBuilder();
buf.append(getSafeName());
TestObject parent = getParent();
if (parent != null) {
String parentId = parent.getId();
if ((parentId != null) && (parentId.length() > 0)) {
buf.insert(0, '/');
buf.insert(0, parent.getId());
}
}
id = buf.toString();
}
return id;
}
/**
* Returns url relative to TestResult
*/
@Override
public String getUrl() {
return '/' + getId();
}
/**
* Returns the top level test result data.
*
* @deprecated This method returns a JUnit specific class. Use
* {@link #topLevelTestResult()} instead for a more general interface.
* @return
*/
@Override
public hudson.tasks.junit.TestResult getTestResult() {
TestObject parent = getParent();
return (parent == null ? null : getParent().getTestResult());
}
/**
* Returns the top level test result data.
*
* @return
*/
public TestResult getTopLevelTestResult() {
TestObject parent = getParent();
return (parent == null ? null : getParent().getTopLevelTestResult());
}
/**
* Computes the relative path to get to this test object from <code>it</code>. If
* <code>it</code> does not appear in the parent chain for this object, a
* relative path from the server root will be returned.
*
* @return A relative path to this object, potentially from the top of the
* Hudson object model
*/
public String getRelativePathFrom(TestObject it) {
// if (it is one of my ancestors) {
// return a relative path from it
// } else {
// return a complete path starting with "/"
// }
if (it==this) {
return ".";
}
StringBuilder buf = new StringBuilder();
TestObject next = this;
TestObject cur = this;
// Walk up my ancesotors from leaf to root, looking for "it"
// and accumulating a relative url as I go
while (next!=null && it!=next) {
cur = next;
buf.insert(0,'/');
buf.insert(0,cur.getSafeName());
next = cur.getParent();
}
if (it==next) {
return buf.toString();
} else {
// Keep adding on to the string we've built so far
// Start with the test result action
AbstractTestResultAction action = getTestResultAction();
if (action==null) {
LOGGER.warning("trying to get relative path, but we can't determine the action that owns this result.");
return ""; // this won't take us to the right place, but it also won't 404.
}
buf.insert(0,'/');
buf.insert(0,action.getUrlName());
// Now the build
AbstractBuild<?,?> myBuild = cur.getOwner();
if (myBuild ==null) {
LOGGER.warning("trying to get relative path, but we can't determine the build that owns this result.");
return ""; // this won't take us to the right place, but it also won't 404.
}
buf.insert(0,'/');
buf.insert(0,myBuild.getUrl());
// If we're inside a stapler request, just delegate to Hudson.Functions to get the relative path!
StaplerRequest req = Stapler.getCurrentRequest();
if (req!=null && myBuild instanceof Item) {
buf.insert(0, '/');
// Ugly but I don't see how else to convince the compiler that myBuild is an Item
Item myBuildAsItem = (Item) myBuild;
buf.insert(0, Functions.getRelativeLinkTo(myBuildAsItem));
} else {
// We're not in a stapler request. Okay, give up.
LOGGER.info("trying to get relative path, but it is not my ancestor, and we're not in a stapler request. Trying absolute hudson url...");
String hudsonRootUrl = Hudson.getInstance().getRootUrl();
if (hudsonRootUrl==null||hudsonRootUrl.isEmpty()) {
LOGGER.warning("Can't find anything like a decent hudson url. Punting, returning empty string.");
return "";
}
buf.insert(0, '/');
buf.insert(0, hudsonRootUrl);
}
LOGGER.info("Here's our relative path: " + buf.toString());
return buf.toString();
}
}
/**
* Subclasses may override this method if they are
* associated with a particular subclass of
* AbstractTestResultAction.
*
* @return the test result action that connects this test result to a particular build
*/
@Override
public AbstractTestResultAction getTestResultAction() {
AbstractBuild<?, ?> owner = getOwner();
if (owner != null) {
return owner.getAction(AbstractTestResultAction.class);
} else {
LOGGER.warning("owner is null when trying to getTestResultAction.");
return null;
}
}
/**
* Get a list of all TestActions associated with this TestObject.
* @return
*/
@Override
public List<TestAction> getTestActions() {
AbstractTestResultAction atra = getTestResultAction();
if ((atra != null) && (atra instanceof TestResultAction)) {
TestResultAction tra = (TestResultAction) atra;
return tra.getActions(this);
} else {
return new ArrayList<TestAction>();
}
}
/**
* Gets a test action of the class passed in.
* @param klazz
* @param <T> an instance of the class passed in
* @return
*/
@Override
public <T> T getTestAction(Class<T> klazz) {
for (TestAction action : getTestActions()) {
if (klazz.isAssignableFrom(action.getClass())) {
return klazz.cast(action);
}
}
return null;
}
/**
* Gets the counterpart of this {@link TestResult} in the previous run.
*
* @return null if no such counter part exists.
*/
public abstract TestResult getPreviousResult();
/**
* Gets the counterpart of this {@link TestResult} in the specified run.
*
* @return null if no such counter part exists.
*/
public abstract TestResult getResultInBuild(AbstractBuild<?, ?> build);
/**
* Find the test result corresponding to the one identified by <code>id></code>
* withint this test result.
*
* @param id The path to the original test result
* @return A corresponding test result, or null if there is no corresponding
* result.
*/
public abstract TestResult findCorrespondingResult(String id);
/**
* Time took to run this test. In seconds.
*/
public abstract float getDuration();
/**
* Returns the string representation of the {@link #getDuration()}, in a
* human readable format.
*/
@Override
public String getDurationString() {
return Util.getTimeSpanString((long) (getDuration() * 1000));
}
@Override
public String getDescription() {
AbstractTestResultAction action = getTestResultAction();
if (action != null) {
return action.getDescription(this);
}
return "";
}
@Override
public void setDescription(String description) {
AbstractTestResultAction action = getTestResultAction();
if (action != null) {
action.setDescription(this, description);
}
}
/**
* Exposes this object through the remote API.
*/
@Override
public Api getApi() {
return new Api(this);
}
/**
* Gets the name of this object.
*/
@Override
public/* abstract */ String getName() {
return "";
}
/**
* Gets the version of {@link #getName()} that's URL-safe.
*/
@Override
public String getSafeName() {
return safe(getName());
}
@Override
public String getSearchUrl() {
return getSafeName();
}
/**
* #2988: uniquifies a {@link #getSafeName} amongst children of the parent.
*/
protected final synchronized String uniquifyName(
Collection<? extends TestObject> siblings, String base) {
String uniquified = base;
int sequence = 1;
for (TestObject sibling : siblings) {
if (sibling != this && uniquified.equals(UNIQUIFIED_NAMES.get(sibling))) {
uniquified = base + '_' + ++sequence;
}
}
UNIQUIFIED_NAMES.put(this, uniquified);
return uniquified;
}
private static final Map<TestObject, String> UNIQUIFIED_NAMES = new WeakHashMap<TestObject, String>();
/**
* Replaces URL-unsafe characters.
*/
public static String safe(String s) {
// 3 replace calls is still 2-3x faster than a regex replaceAll
return s.replace('/', '_').replace('\\', '_').replace(':', '_');
}
/**
* Gets the total number of passed tests.
*/
public abstract int getPassCount();
/**
* Gets the total number of failed tests.
*/
public abstract int getFailCount();
/**
* Gets the total number of skipped tests.
*/
public abstract int getSkipCount();
/**
* Gets the total number of tests.
*/
@Override
public int getTotalCount() {
return getPassCount() + getFailCount() + getSkipCount();
}
@Override
public History getHistory() {
return new History(this);
}
public Object getDynamic(String token, StaplerRequest req,
StaplerResponse rsp) {
for (Action a : getTestActions()) {
if (a == null) {
continue; // be defensive
}
String urlName = a.getUrlName();
if (urlName == null) {
continue;
}
if (urlName.equals(token)) {
return a;
}
}
return null;
}
public synchronized HttpResponse doSubmitDescription(
@QueryParameter String description) throws IOException,
ServletException {
if (getOwner() == null) {
LOGGER.severe("getOwner() is null, can't save description.");
}
if (getOwner() != null) {
getOwner().checkPermission(Item.BUILD);
}
setDescription(description);
if (getOwner() != null) {
getOwner().save();
}
return new HttpRedirect(".");
}
private static final long serialVersionUID = 1L;
}
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.test;
import hudson.tasks.junit.TestAction;
import hudson.model.AbstractBuild;
import hudson.model.Run;
import hudson.model.Result;
import java.util.Collection;
import static java.util.Collections.emptyList;
/**
* A class that represents a general concept of a test result, without any
* language or implementation specifics.
* Subclasses must add @Exported annotation to the fields they want to export.
*
* @sine 1.343
*/
public abstract class TestResult extends TestObject {
/**
* If the concept of a parent action is important to a subclass, then it should
* provide a non-noop implementation of this method.
* @param action
*/
public void setParentAction(AbstractTestResultAction action) {
}
/**
* Returns the action that points to the top level test result includes
* this test result.
*
* @return
*/
public AbstractTestResultAction getParentAction() {
return getOwner().getTestResultAction();
}
/**
* Request that the result update its counts of its children. Does not
* require a parent action or owner or siblings. Subclasses should
* implement this, unless they are *always* in a tallied state.
*/
public void tally() {
}
/**
* Sets the parent test result
* @param parent
*/
public void setParent(TestObject parent) {
}
/**
* Gets the human readable title of this result object.
*/
public /* abstract */ String getTitle(){
return "";
}
/**
* Mark a build as unstable if there are failures. Otherwise, leave the
* build result unchanged.
*
* @return {@link Result#UNSTABLE} if there are test failures, null otherwise.
*
*/
public Result getBuildResult() {
if (getFailCount() > 0) {
return Result.UNSTABLE;
} else {
return null;
}
}
/**
* Time it took to run this test. In seconds.
*/
public /* abstract */ float getDuration() {
return 0.0f;
}
/**
* Gets the total number of passed tests.
*/
public /* abstract */ int getPassCount() {
return 0;
}
/**
* Gets the total number of failed tests.
*/
public /* abstract */ int getFailCount() {
return 0;
}
/**
* Gets the total number of skipped tests.
*/
public /* abstract */ int getSkipCount() {
return 0;
}
/**
* Gets the counter part of this {@link TestResult} in the previous run.
*
* @return null if no such counter part exists.
*/
public TestResult getPreviousResult() {
AbstractBuild<?,?> b = getOwner();
if (b == null) {
return null;
}
while(true) {
b = b.getPreviousBuild();
if(b==null)
return null;
AbstractTestResultAction r = b.getAction(getParentAction().getClass());
if(r!=null) {
TestResult result = r.findCorrespondingResult(this.getId());
if (result!=null)
return result;
}
}
}
/**
* Gets the counter part of this {@link TestResult} in the specified run.
*
* @return null if no such counter part exists.
*/
public TestResult getResultInBuild(AbstractBuild<?,?> build) {
AbstractTestResultAction tra = build.getAction(getParentAction().getClass());
if (tra == null) {
tra = build.getAction(AbstractTestResultAction.class);
}
return (tra == null) ? null : tra.findCorrespondingResult(this.getId());
}
/**
* Gets the "children" of this test result that failed
* @return the children of this test result, if any, or an empty collection
*/
public Collection<? extends TestResult> getFailedTests() {
return emptyList();
}
/**
* Gets the "children" of this test result that passed
* @return the children of this test result, if any, or an empty collection
*/
public Collection<? extends TestResult> getPassedTests() {
return emptyList();
}
/**
* Gets the "children" of this test result that were skipped
* @return the children of this test result, if any, or an empty list
*/
public Collection<? extends TestResult> getSkippedTests() {
return emptyList();
}
/**
* If this test failed, then return the build number
* when this test started failing.
*/
public int getFailedSince() {
return 0;
}
/**
* If this test failed, then return the run
* when this test started failing.
*/
public Run<?,?> getFailedSinceRun() {
return null;
}
/**
* The stdout of this test.
*/
public String getStdout() {
return "";
}
/**
* The stderr of this test.
*/
public String getStderr() {
return "";
}
/**
* If there was an error or a failure, this is the stack trace, or otherwise null.
*/
public String getErrorStackTrace() {
return "";
}
/**
* If there was an error or a failure, this is the text from the message.
*/
public String getErrorDetails() {
return "";
}
/**
* @return true if the test was not skipped and did not fail, false otherwise.
*/
public boolean isPassed() {
return ((getSkipCount() == 0) && (getFailCount() == 0));
}
public String toPrettyString() {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("Name: ").append(this.getName()).append(", ");
sb.append("Result: ").append(this.getBuildResult()).append(",\n");
sb.append("Total Count: ").append(this.getTotalCount()).append(", ");
sb.append("Fail: ").append(this.getFailCount()).append(", ");
sb.append("Skipt: ").append(this.getSkipCount()).append(", ");
sb.append("Pass: ").append(this.getSkipCount()).append(",\n");
sb.append("Test Result Class: " ).append(this.getClass().getName()).append(" }\n");
return sb.toString();
}
/**
* Annotate some text -- what does this do?
* @param text
* @return
*/
public String annotate(String text) {
if (text == null)
return null;
text = text.replace("&", "&amp;").replace("<", "&lt;").replaceAll(
"\\b(https?://[^\\s)>]+)", "<a href=\"$1\">$1</a>");
for (TestAction action: getTestActions()) {
text = action.annotate(text);
}
return text;
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -23,11 +23,11 @@
*/
package hudson.tasks.test;
import hudson.Launcher;
import hudson.matrix.MatrixAggregator;
import hudson.matrix.MatrixBuild;
import hudson.matrix.MatrixRun;
import hudson.model.BuildListener;
import hudson.Launcher;
import java.io.IOException;
......
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.test;
import hudson.AbortException;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.Hudson;
import hudson.model.TaskListener;
import java.io.IOException;
/**
* An extension point by which various parsers can register to parse
* results files and produce some subclass of TestResult.
*
* @since 1.343
*/
public abstract class TestResultParser implements ExtensionPoint {
/**
* Returns a human readable name of the parser, like "JUnit Parser".
*/
public String getDisplayName() {
return "Unknown Parser";
}
/**
* This text is used in the UI prompt for the GLOB that specifies files to be parsed by this parser.
* For example, "JUnit XML reports:"
*/
public String getTestResultLocationMessage() {
return "Paths to results files to parse:";
}
/**
* All registered {@link TestResultParser}s
*/
public static ExtensionList<TestResultParser> all() {
return Hudson.getInstance().getExtensionList(TestResultParser.class);
}
/**
* Parses the specified set of files and builds a {@link TestResult} object that represents them.
*
* <p>
* The implementation is encouraged to do the following:
*
* <ul>
* <li>
* If the build is successful but GLOB didn't match anything, report that as an error. This is
* to detect the error in GLOB. But don't do this if the build has already failed (for example,
* think of a failure in SCM checkout.)
*
* <li>
* Examine time stamp of test report files and if those are younger than the build, ignore them.
* This is to ignore test reports created by earlier executions. Take the possible timestamp
* difference in the master/slave into account.
* </ul>
*
* @param testResultLocations
* GLOB pattern relative to the {@linkplain AbstractBuild#getWorkspace() workspace} that
* specifies the locations of the test result files. Never null.
* @param build
* Build for which these tests are parsed. Never null.
* @param launcher
* Can be used to fork processes on the machine where the build is running. Never null.
* @param listener
* Use this to report progress and other problems. Never null.
*
* @throws InterruptedException
* If the user cancels the build, it will be received as a thread interruption. Do not catch
* it, and instead just forward that through the call stack.
* @throws IOException
* If you don't care about handling exceptions gracefully, you can just throw IOException
* and let the default exception handling in Hudson takes care of it.
* @throws AbortException
* If you encounter an error that you handled gracefully, throw this exception and Hudson
* will not show a stack trace.
*/
public abstract TestResult parse(String testResultLocations,
AbstractBuild build, Launcher launcher,
TaskListener listener)
throws InterruptedException, IOException;
}
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts
Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts, Yahoo!, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......@@ -47,7 +47,7 @@ THE SOFTWARE.
<l:main-panel>
<H2>${%title(it.testObject.displayName)}</H2>
<j:choose>
<j:when test="${it.testObject.previousResult != null}">
<j:when test="${it.historyAvailable()}">
<div align="center">
<img id="graph" src="durationGraph/png" width="600" height="300" lazymap="durationGraph/map" alt="[Duration graph]"/>
</div>
......
# The MIT License
#
# Copyright (c) 2004-2010, Sun Microsystems, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
All\ Failed\ Tests=Todos los test que han fallado
Test\ Name=Nombre del test
Duration=Duración
Age=Antigüedad
Loading...=Cargando...
All\ Tests=Todos los tests
Fail=Fallos
diff=Diferencias
Skip=Omitidos
Total=Total
# The MIT License
#
# Copyright (c) 2004-2010, Sun Microsystems, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
All\ Failed\ Tests=Alla fallerande tester
All\ Tests=Alla tester
Duration=Tid
Fail=Fel
Test\ Name=Testnamn
Total=Totalt
diff=skillnad
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Simon Wiest
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Build=Build
Description=Beschreibung
Duration=Dauer
Fail=Fehlgeschlagen
Skip=Ausgelassen
Total=Summe
# The MIT License
#
# Copyright (c) 2004-2010, Sun Microsystems, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Build=Ejecución
Description=Descripción
Duration=Duración
Fail=Fallos
Skip=Omitidos
Total=Total
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Simon Wiest
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
took=Dauerte {0}.
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
took=Tardó {0}.
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Simon Wiest
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
History=Verlauf
Previous\ Build=Vorheriger Build
Next\ Build=Nachfolgender Build
# The MIT License
#
# Copyright (c) 2004-2010, Sun Microsystems, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
History=Historia
Previous\ Build=Ejecución previa
Next\ Build=Ejecución siguiente
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, Yahoo!, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
# The MIT License
#
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, id:cactusman, Tom Huybrechts
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, id:cactusman, Tom Huybrechts, Yahoo!, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......@@ -27,14 +27,14 @@ THE SOFTWARE.
<script type="text/javascript">
<!-- TODO make sure load doesn't happen every time -->
function showStackTrace(id,query) {
var element = document.getElementById(id);
var element = document.getElementById(id)
element.style.display = "";
document.getElementById(id + "-showlink").style.display = "none";
document.getElementById(id + "-hidelink").style.display = "";
var rqo = new XMLHttpRequest();
rqo.open('GET', query, true);
rqo.onreadystatechange = function() { element.innerHTML = rqo.responseText; };
rqo.onreadystatechange = function() { element.innerHTML = rqo.responseText; }
rqo.send(null);
}
......@@ -56,11 +56,11 @@ THE SOFTWARE.
<j:forEach var="f" items="${it.failedTests}" varStatus="i">
<tr>
<td class="pane">
<a id="test-${f.fullName}-showlink" href="#"
onclick='showStackTrace("test-${f.fullName}","${f.getRelativePathFrom(it)}/summary"); return false;'>&gt;&gt;&gt;</a>
<a id="test-${f.fullName}-hidelink" href="#" style="display:none"
onclick='hideStackTrace("test-${f.fullName}"); return false;'>&lt;&lt;&lt;</a>
<st:nbsp/>
<a id="test-${f.fullName}-showlink" href="#"
onclick='javascript:showStackTrace("test-${f.fullName}","${f.getRelativePathFrom(it)}/summary")'>&gt;&gt;&gt;</a>
<a id="test-${f.fullName}-hidelink" href="#" style="display:none"
onclick='javascript:hideStackTrace("test-${f.fullName}")'>&lt;&lt;&lt;</a>
<st:nbsp/>
<a href="${f.getRelativePathFrom(it)}"><st:out value="${f.fullName}"/></a>
<st:nbsp/>
<j:forEach var="badge" items="${f.testActions}">
......
......@@ -20,13 +20,11 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
All\ Failed\ Tests=Alle fehlgeschlagenen Tests
Test\ Name=Testname
Duration=Dauer
Age=Alter
All\ Tests=Alle Tests
Fail=Fehlgeschlagen
Skip=Ausgelassen
diff=Veränderung
Total=Summe
Loading...=Daten werden geladen...
\ No newline at end of file
All\ Failed\ Tests=Alle fehlgeschlagenen Tests
Test\ Name=Testname
Duration=Dauer
Age=Alter
All\ Tests=Alle Tests
Fail=Fehlgeschlagen
diff=Veränderung
Total=Summe
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts
Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts, Yahoo!, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......@@ -51,8 +51,8 @@ THE SOFTWARE.
<td class="pane" style="text-align:right">${p.skipCount}</td>
<td class="pane" style="text-align:right">${p.totalCount}</td>
</tr>
</j:if>
</j:forEach>
</j:if>
</j:forEach>
</tbody>
</table>
</j:jelly>
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, Yahoo!, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......@@ -30,9 +30,9 @@ THE SOFTWARE.
<l:side-panel>
<l:tasks>
<j:set var="buildUrl" value="${h.decompose(request)}" />
<st:include it="${it.owner}" page="tasks.jelly"/>
<st:include it="${it.owner}" page="tasks.jelly" optional="true"/>
<l:task icon="images/24x24/graph.gif" href="history" title="${%History}"/>
<st:include it="${it.owner}" page="actions.jelly" />
<st:include it="${it.owner}" page="actions.jelly" optional="true" />
<j:forEach var="action" items="${it.testActions}">
<j:if test="${action.iconFileName!=null}">
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo!, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:f="/lib/form">
<st:documentation>
Generates an input field <tt>&lt;input type="text" ... /></tt> to be
used inside &lt;f:entry/>
<st:attribute name="field">
Used for databinding. TBD.
</st:attribute>
<st:attribute name="name">
This becomes @name of the &lt;input> tag.
If @field is specified, this value is inferred from it.
</st:attribute>
<st:attribute name="value">
The initial value of the field. This becomes the @value of the &lt;input> tag.
If @field is specified, the current property from the "instance" object
will be set as the initial value automatically,
which is the recommended approach.
</st:attribute>
<st:attribute name="default">
The default value of the text box, in case both @value is and 'instance[field]' is null.
</st:attribute>
<!-- Tomcat doesn't like us using the attribute called 'class' -->
<st:attribute name="clazz">
Additional CSS class(es) to add (such as client-side validation clazz="required",
"number" or "positive-number"; these may be combined, as clazz="required number").
</st:attribute>
<st:attribute name="checkMessage">
Override the default error message when client-side validation fails,
as with clazz="required", etc.
</st:attribute>
<st:attribute name="checkUrl">
If specified, the value entered in this input field will be checked (via AJAX)
against this URL, and errors will be rendered under the text field.
If @field is specified, this will be inferred automatically,
which is the recommended approach.
</st:attribute>
</st:documentation>
<f:prepareDatabinding />
<input class="setting-input ${attrs.checkUrl!=null?'validated':''} ${attrs.clazz}"
name="${attrs.name ?: '_.'+attrs.field}"
value="${attrs.value ?: instance[attrs.field] ?: attrs.default}"
id="${attrs.id}"
type="text"
readonly="readonly"
checkUrl="${attrs.checkUrl}" checkMethod="${attrs.checkMethod}"
checkMessage="${attrs.checkMessage}"
onchange="${attrs.onchange}" onkeyup="${attrs.onkeyup}"/>
</j:jelly>
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Xavier Le Vourch
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Xavier Le Vourch, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -30,6 +30,8 @@ import java.net.URISyntaxException;
import junit.framework.TestCase;
import org.dom4j.DocumentException;
import hudson.XmlFile;
/**
* Test cases for parsing JUnit report XML files.
* As there are no XML schema for JUnit xml files, Hudson needs to handle
......@@ -118,4 +120,27 @@ public class SuiteResultTest extends TestCase {
}
assertEquals("this normally has the string like, expected mullet, but got bream", cases.get(0).getErrorDetails());
}
public void testSuiteResultPersistence() throws Exception {
SuiteResult source = parseOne(getDataFile("junit-report-1233.xml"));
File dest = File.createTempFile("testSuiteResultPersistence", ".xml");
try {
XmlFile xmlFile = new XmlFile(dest);
xmlFile.write(source);
SuiteResult result = (SuiteResult)xmlFile.read();
assertNotNull(result);
assertEquals(source.getName(), result.getName());
assertEquals(source.getTimestamp(), result.getTimestamp());
assertEquals(source.getDuration(), result.getDuration());
assertEquals(source.getStderr(), result.getStderr());
assertEquals(source.getStdout(), result.getStdout());
assertEquals(source.getCases().size(), result.getCases().size());
assertNotNull(result.getCase("testGetBundle"));
} finally {
dest.delete();
}
}
}
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.junit;
import com.thoughtworks.xstream.XStream;
import hudson.XmlFile;
import hudson.util.StringConverter2;
import hudson.util.XStream2;
import java.io.File;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.List;
import junit.framework.TestCase;
import static org.junit.Assert.*;
/**
*
* @author dty
*/
public class TestResultTest extends TestCase {
private File getDataFile(String name) throws URISyntaxException {
return new File(TestResultTest.class.getResource(name).toURI());
}
/**
* This test verifies compatibility of JUnit test results persisted to
* XML prior to the test code refactoring.
*
* @throws Exception
*/
public void testXmlCompatibility() throws Exception {
XmlFile xmlFile = new XmlFile(XSTREAM, getDataFile("junitResult.xml"));
TestResult result = (TestResult)xmlFile.read();
// Regenerate the transient data
result.tally();
assertEquals(9, result.getTotalCount());
assertEquals(1, result.getSkipCount());
assertEquals(1, result.getFailCount());
// XStream seems to produce some weird rounding errors...
assertEquals(0.576, result.getDuration(), 0.0001);
Collection<SuiteResult> suites = result.getSuites();
assertEquals(6, suites.size());
List<CaseResult> failedTests = result.getFailedTests();
assertEquals(1, failedTests.size());
SuiteResult failedSuite = result.getSuite("broken");
assertNotNull(failedSuite);
CaseResult failedCase = failedSuite.getCase("becomeUglier");
assertNotNull(failedCase);
assertFalse(failedCase.isSkipped());
assertFalse(failedCase.isPassed());
assertEquals(5, failedCase.getFailedSince());
}
private static final XStream XSTREAM = new XStream2();
static {
XSTREAM.alias("result",TestResult.class);
XSTREAM.alias("suite",SuiteResult.class);
XSTREAM.alias("case",CaseResult.class);
XSTREAM.registerConverter(new StringConverter2(),100);
}
}
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<result>
<suites>
<suite>
<file>./TEST-TestSuite.xml</file>
<name>TestSuite</name>
<duration>0.128</duration>
<cases>
<case>
<duration>0.128</duration>
<className>service.EchoServletIT</className>
<testName>sayHello</testName>
<skipped>true</skipped>
<failedSince>0</failedSince>
</case>
</cases>
</suite>
<suite>
<file>./failsafe-summary.xml</file>
<name>(failsafe-summary.xml)</name>
<duration>0.0</duration>
<cases/>
</suite>
<suite>
<file>./testng-results.xml</file>
<name>(testng-results.xml)</name>
<duration>0.0</duration>
<cases/>
</suite>
<suite>
<file>./TEST-TestSuite.xml</file>
<name>TestSuite</name>
<duration>0.293</duration>
<cases>
<case>
<duration>0.293</duration>
<className>service.EchoServletTest</className>
<testName>sayHello</testName>
<skipped>false</skipped>
<failedSince>0</failedSince>
</case>
</cases>
</suite>
<suite>
<file>./testng-results.xml</file>
<name>(testng-results.xml)</name>
<duration>0.0</duration>
<cases/>
</suite>
<suite>
<file>./TEST-TestSuite.xml</file>
<name>broken</name>
<duration>0.155</duration>
<cases>
<case>
<duration>0.0020</duration>
<className>breakable.misc.StupidTest</className>
<testName>doSomething</testName>
<skipped>false</skipped>
<failedSince>0</failedSince>
</case>
<case>
<duration>0.0</duration>
<className>breakable.misc.StupidTest</className>
<testName>jumparound</testName>
<skipped>false</skipped>
<failedSince>0</failedSince>
</case>
<case>
<duration>0.0</duration>
<className>breakable.misc.UglyTest</className>
<testName>jumpAroundYouUglyPerson</testName>
<skipped>false</skipped>
<failedSince>0</failedSince>
</case>
<case>
<duration>0.0</duration>
<className>breakable.misc.UglyTest</className>
<testName>doSomethingUgly</testName>
<skipped>false</skipped>
<failedSince>0</failedSince>
</case>
<case>
<duration>0.0010</duration>
<className>breakable.misc.UglyTest</className>
<testName>becomeUglier</testName>
<skipped>false</skipped>
<errorStackTrace>java.lang.AssertionError: Yeah, ugly and broken.
at org.testng.Assert.fail(Assert.java:84)
at breakable.misc.UglyTest.becomeUglier(UglyTest.java:25)
</errorStackTrace>
<errorDetails>Yeah, ugly and broken.</errorDetails>
<failedSince>5</failedSince>
</case>
<case>
<duration>0.15</duration>
<className>breakable.service.EchoServletTest</className>
<testName>sayHello</testName>
<skipped>false</skipped>
<failedSince>0</failedSince>
</case>
<case>
<duration>0.0020</duration>
<className>service.EchoServletTest</className>
<testName>sayHello</testName>
<skipped>false</skipped>
<failedSince>0</failedSince>
</case>
</cases>
</suite>
</suites>
<duration>0.576</duration>
</result>
\ No newline at end of file
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, id:cactusman
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, id:cactusman, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -82,7 +82,6 @@ public class SurefireAggregatedReport extends AggregatedTestResultAction impleme
/**
*
*/
@Override
public String getTestResultPath(CaseResult it) {
StringBuilder path = new StringBuilder("../");
path.append(it.getOwner().getProject().getShortUrl());
......
......@@ -145,13 +145,13 @@ import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequestSettings;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import com.gargoylesoftware.htmlunit.html.*;
import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;
import com.gargoylesoftware.htmlunit.javascript.host.Stylesheet;
import hudson.model.Computer;
import hudson.slaves.ComputerListener;
import java.util.concurrent.CountDownLatch;
/**
* Base class for all Hudson test cases.
......@@ -552,6 +552,43 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
}
}
/**
* Create a new slave on the local host and wait for it to come onilne
* before returning.
*/
public DumbSlave createOnlineSlave() throws Exception {
return createOnlineSlave(null);
}
/**
* Create a new slave on the local host and wait for it to come onilne
* before returning.
*/
public DumbSlave createOnlineSlave(Label l) throws Exception {
return createOnlineSlave(l, null);
}
/**
* Create a new slave on the local host and wait for it to come online
* before returning
*/
public DumbSlave createOnlineSlave(Label l, EnvVars env) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
ComputerListener waiter = new ComputerListener() {
@Override
public void onOnline(Computer C, TaskListener t) {
latch.countDown();
unregister();
}
};
waiter.register();
DumbSlave s = createSlave(l, env);
latch.await();
return s;
}
/**
* Blocks until the ENTER key is hit.
* This is useful during debugging a test so that one can inspect the state of Hudson through the web browser.
......@@ -604,6 +641,8 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
return new WebClient().search(q);
}
/**
* Asserts that the outcome of the build is a specific outcome.
*/
......@@ -623,6 +662,26 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
return r;
}
/** Determines whether the specifed HTTP status code is generally "good" */
public boolean isGoodHttpStatus(int status) {
if ((400 <= status) && (status <= 417)) {
return false;
}
if ((500 <= status) && (status <= 505)) {
return false;
}
return true;
}
/** Assert that the specifed page can be served with a "good" HTTP status,
* eg, the page is not missing and can be served without a server error
* @param page
*/
public void assertGoodStatus(Page page) {
assertTrue(isGoodHttpStatus(page.getWebResponse().getStatusCode()));
}
public <R extends Run> R assertBuildStatusSuccess(R r) throws Exception {
assertBuildStatus(Result.SUCCESS,r);
return r;
......@@ -656,6 +715,73 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
page.getDocumentElement().selectSingleNode(xpath));
}
/** Asserts that the XPath matches the contents of a DomNode page. This
* variant of assertXPath(HtmlPage page, String xpath) allows us to
* examine XmlPages.
* @param page
* @param xpath
*/
public void assertXPath(DomNode page, String xpath) {
List< ? extends Object> nodes = page.getByXPath(xpath);
assertFalse("There should be an object that matches XPath:"+xpath, nodes.isEmpty());
}
public void assertXPathValue(DomNode page, String xpath, String expectedValue) {
Object node = page.getFirstByXPath(xpath);
assertNotNull("no node found", node);
assertTrue("the found object was not a Node " + xpath, node instanceof org.w3c.dom.Node);
org.w3c.dom.Node n = (org.w3c.dom.Node) node;
String textString = n.getTextContent();
assertEquals("xpath value should match for " + xpath, expectedValue, textString);
}
public void assertXPathValueContains(DomNode page, String xpath, String needle) {
Object node = page.getFirstByXPath(xpath);
assertNotNull("no node found", node);
assertTrue("the found object was not a Node " + xpath, node instanceof org.w3c.dom.Node);
org.w3c.dom.Node n = (org.w3c.dom.Node) node;
String textString = n.getTextContent();
assertTrue("needle found in haystack", textString.contains(needle));
}
public void assertXPathResultsContainText(DomNode page, String xpath, String needle) {
List<? extends Object> nodes = page.getByXPath(xpath);
assertFalse("no nodes matching xpath found", nodes.isEmpty());
boolean found = false;
for (Object o : nodes) {
if (o instanceof org.w3c.dom.Node) {
org.w3c.dom.Node n = (org.w3c.dom.Node) o;
String textString = n.getTextContent();
if ((textString != null) && textString.contains(needle)) {
found = true;
break;
}
}
}
assertTrue("needle found in haystack", found);
}
public void assertStringContains(String message, String haystack, String needle) {
if (haystack.contains(needle)) {
// good
return;
} else {
fail(message + " (seeking '" + needle + "')");
}
}
public void assertStringContains(String haystack, String needle) {
if (haystack.contains(needle)) {
// good
return;
} else {
fail("Could not find '" + needle + "'.");
}
}
/**
* Submits the form.
*/
......@@ -1165,6 +1291,22 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
return p;
}
/** Loads a page as XML. Useful for testing Hudson's xml api, in concert with
* assertXPath(DomNode page, String xpath)
* @param path the path part of the url to visit
* @return the XmlPage found at that url
* @throws IOException
* @throws SAXException
*/
public XmlPage goToXml(String path) throws IOException, SAXException {
Page page = goTo(path, "application/xml");
if (page instanceof XmlPage)
return (XmlPage) page;
else
return null;
}
/**
* Returns the URL of the webapp top page.
* URL ends with '/'.
......
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jvnet.hudson.test;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.tasks.Builder;
import java.io.IOException;
import java.io.Serializable;
public class TouchBuilder extends Builder implements Serializable {
@Override
public boolean perform(AbstractBuild<?, ?> build,
Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
for (FilePath f : build.getWorkspace().list()) {
f.touch(System.currentTimeMillis());
}
return true;
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -70,6 +70,12 @@ public class ApiTest extends HudsonTestCase {
assertEquals("<name>All</name>", page.getWebResponse().getContentAsString());
}
public void testUnwrappedLongString() throws Exception {
hudson.setSystemMessage("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
Page page = new WebClient().goTo("api/xml?xpath=/hudson/description/text()", "text/plain");
assertEquals(hudson.getSystemMessage(), page.getWebResponse().getContentAsString());
}
public void testUnwrappedMultipleItems() throws Exception {
createFreeStyleProject();
createFreeStyleProject();
......
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.junit;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Project;
import hudson.model.Result;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.recipes.LocalData;
import java.util.List;
public class HistoryTest extends HudsonTestCase {
private FreeStyleProject project;
private static final String PROJECT_NAME = "wonky";
@Override
protected void setUp() throws Exception {
super.setUp();
List<Project> projects = this.hudson.getProjects();
Project theProject = null;
for (Project p : projects) {
if (p.getName().equals(PROJECT_NAME)) theProject = p;
}
assertNotNull("We should have a project named " + PROJECT_NAME, theProject);
assertTrue( theProject instanceof FreeStyleProject);
project = (FreeStyleProject) theProject;
}
@LocalData
public void testFailedSince() throws Exception {
assertNotNull("project should exist", project);
// Check the status of a few builds
FreeStyleBuild build4 = project.getBuildByNumber(4);
assertNotNull("build4", build4);
assertBuildStatus(Result.FAILURE, build4);
FreeStyleBuild build7 = project.getBuildByNumber(7);
assertNotNull("build7", build7);
assertBuildStatus(Result.SUCCESS, build7);
TestResult tr = build4.getAction(TestResultAction.class).getResult();
assertEquals(2,tr.getFailedTests().size());
// In build 4, we expect these tests to have failed since these builds
// org.jvnet.hudson.examples.small.deep.DeepTest.testScubaGear failed since 3
// org.jvnet.hudson.examples.small.MiscTest.testEleanor failed since 3
PackageResult deepPackage = tr.byPackage("org.jvnet.hudson.examples.small.deep");
assertNotNull("deepPackage", deepPackage);
assertTrue("package is failed", !deepPackage.isPassed());
ClassResult deepClass = deepPackage.getClassResult("DeepTest");
assertNotNull(deepClass);
assertTrue("class is failed", !deepClass.isPassed());
CaseResult scubaCase = deepClass.getCaseResult("testScubaGear");
assertNotNull(scubaCase);
assertTrue("scubaCase case is failed", !scubaCase.isPassed());
int scubaFailedSince = scubaCase.getFailedSince();
assertEquals("scubaCase should have failed since build 3", 3, scubaFailedSince);
// In build 5 the scuba test begins to pass
TestResult tr5 = project.getBuildByNumber(5).getAction(TestResultAction.class).getResult();
assertEquals(1,tr5.getFailedTests().size());
deepPackage = tr5.byPackage("org.jvnet.hudson.examples.small.deep");
assertNotNull("deepPackage", deepPackage);
assertTrue("package is passed", deepPackage.isPassed());
deepClass = deepPackage.getClassResult("DeepTest");
assertNotNull(deepClass);
assertTrue("class is passed", deepClass.isPassed());
scubaCase = deepClass.getCaseResult("testScubaGear");
assertNotNull(scubaCase);
assertTrue("scubaCase case is passed", scubaCase.isPassed());
// In build5, testEleanor has been failing since build 3
PackageResult smallPackage = tr5.byPackage("org.jvnet.hudson.examples.small");
ClassResult miscClass = smallPackage.getClassResult("MiscTest");
CaseResult eleanorCase = miscClass.getCaseResult("testEleanor");
assertTrue("eleanor failed", !eleanorCase.isPassed());
assertEquals("eleanor has failed since build 3", 3, eleanorCase.getFailedSince());
}
}
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.junit;
import hudson.Launcher;
import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.tasks.Builder;
import hudson.tasks.test.TestResult;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.recipes.LocalData;
import java.io.IOException;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.*;
/**
* Test the mechanism for calling a JUnitParser separately
* from the JUnitResultsArchiver
*
*/
public class JUnitParserTest extends HudsonTestCase {
static hudson.tasks.junit.TestResult theResult = null;
public static final class JUnitParserTestBuilder extends Builder implements Serializable {
private String testResultLocation;
public JUnitParserTestBuilder(String testResultLocation) {
this.testResultLocation = testResultLocation;
}
@Override
public boolean perform(AbstractBuild<?, ?> build,
Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
System.out.println("in perform...");
// First touch all the files, so they will be recently modified
for (FilePath f : build.getWorkspace().list()) {
f.touch(System.currentTimeMillis());
}
System.out.println("...touched everything");
hudson.tasks.junit.TestResult result = (new JUnitParser()).parse( testResultLocation, build, launcher, listener);
System.out.println("back from parse");
assertNotNull("we should have a non-null result", result);
assertTrue("result should be a TestResult", result instanceof hudson.tasks.junit.TestResult);
System.out.println("We passed some assertions in the JUnitParserTestBuilder");
theResult = result;
return (result != null);
}
}
private FreeStyleProject project;
private String projectName = "junit_parser_test";
@Override
protected void setUp() throws Exception {
super.setUp();
theResult = null;
project = createFreeStyleProject(projectName);
project.getBuildersList().add(new JUnitParserTestBuilder("*.xml"));
}
@LocalData
public void testJustParsing() throws Exception {
FreeStyleBuild build = project.scheduleBuild2(0).get(100, TimeUnit.MINUTES);
assertNotNull(build);
// Now let's examine the result. We know lots of stuff about it because
// we've analyzed the xml source files by hand.
assertNotNull("we should have a result in the static member", theResult);
// Check the overall counts. We should have 1 failure, 0 skips, and 132 passes.
Collection<? extends TestResult> children = theResult.getChildren();
assertFalse("Should have several packages", children.isEmpty());
assertTrue("Should have several pacakges", children.size() > 3);
int passCount = theResult.getPassCount();
assertEquals("expecting many passes", 131, passCount);
int failCount = theResult.getFailCount();
assertEquals("we should have one failure", 1, failCount);
assertEquals("expected 0 skips", 0, theResult.getSkipCount());
assertEquals("expected 132 total tests", 132, theResult.getTotalCount());
// Dig in to the failed test
final String EXPECTED_FAILING_TEST_NAME = "testDataCompatibilityWith1_282";
final String EXPECTED_FAILING_TEST_CLASSNAME = "hudson.security.HudsonPrivateSecurityRealmTest";
Collection<? extends TestResult> failingTests = theResult.getFailedTests();
assertEquals("should have one failed test", 1, failingTests.size());
Map<String, TestResult> failedTestsByName = new HashMap<String, hudson.tasks.test.TestResult>();
for (TestResult r: failingTests) {
failedTestsByName.put(r.getName(), r);
}
assertTrue("we've got the expected failed test", failedTestsByName.containsKey(EXPECTED_FAILING_TEST_NAME));
TestResult firstFailedTest = failedTestsByName.get(EXPECTED_FAILING_TEST_NAME);
assertFalse("should not have passed this test", firstFailedTest.isPassed());
// TODO: Dig in to the passed tests, too
}
}
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.junit;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Hudson;
import hudson.tasks.Builder;
import hudson.slaves.DumbSlave;
import hudson.tasks.test.TestObject;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.TimeUnit;
import junit.framework.Assert;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.TouchBuilder;
import org.jvnet.hudson.test.recipes.LocalData;
import org.xml.sax.SAXException;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
public class JUnitResultArchiverTest extends HudsonTestCase {
public static final class TouchBuilder extends Builder implements Serializable {
@Override
public boolean perform(AbstractBuild<?, ?> build,
Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
for (FilePath f: build.getWorkspace().list()) {
f.touch(System.currentTimeMillis());
}
return true;
}
}
private FreeStyleProject project;
private JUnitResultArchiver archiver;
......@@ -69,6 +73,20 @@ public class JUnitResultArchiverTest extends HudsonTestCase {
}
@LocalData
public void testSlave() throws Exception {
DumbSlave s = createOnlineSlave();
project.setAssignedLabel(s.getSelfLabel());
FilePath src = new FilePath(hudson.getRootPath(), "jobs/junit/workspace/");
assertNotNull(src);
FilePath dest = s.getWorkspaceFor(project);
assertNotNull(dest);
src.copyRecursiveTo("*.xml", dest);
testBasic();
}
private void assertTestResults(FreeStyleBuild build) {
TestResultAction testResultAction = build.getAction(TestResultAction.class);
assertNotNull("no TestResultAction", testResultAction);
......@@ -85,7 +103,7 @@ public class JUnitResultArchiverTest extends HudsonTestCase {
@LocalData
public void testPersistence() throws Exception {
project.scheduleBuild2(0).get(10, TimeUnit.SECONDS);
project.scheduleBuild2(0).get(60, TimeUnit.SECONDS);
reloadHudson();
......
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.junit;
import org.jvnet.hudson.test.HudsonTestCase;
import hudson.FilePath;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Hudson;
import hudson.model.Result;
import hudson.slaves.DumbSlave;
import hudson.tasks.test.TestObject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.TouchBuilder;
import org.jvnet.hudson.test.recipes.LocalData;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.Page;
/**
* User: Benjamin Shine bshine@yahoo-inc.com
* Date: Dec 7, 2009
* Time: 7:52:55 PM
*/
public class TestResultLinksTest extends HudsonTestCase {
private FreeStyleProject project;
private JUnitResultArchiver archiver;
@Override
protected void setUp() throws Exception {
super.setUp();
project = createFreeStyleProject("taqueria");
archiver = new JUnitResultArchiver("*.xml");
project.getPublishersList().add(archiver);
project.getBuildersList().add(new TouchBuilder());
}
@LocalData
public void testFailureLinks() throws Exception {
FreeStyleBuild build = project.scheduleBuild2(0).get(10, TimeUnit.SECONDS);
assertBuildStatus(Result.UNSTABLE, build);
TestResult theOverallTestResult = build.getAction(TestResultAction.class).getResult();
CaseResult theFailedTestCase = theOverallTestResult.getFailedTests().get(0);
String relativePath = theFailedTestCase.getRelativePathFrom(theOverallTestResult);
System.out.println("relative path seems to be: " + relativePath);
HudsonTestCase.WebClient wc = new HudsonTestCase.WebClient();
String testReportPageUrl = project.getLastBuild().getUrl() + "/testReport";
HtmlPage testReportPage = wc.goTo( testReportPageUrl );
Page packagePage = testReportPage.getFirstAnchorByText("tacoshack.meals").click();
assertGoodStatus(packagePage); // I expect this to work; just checking that my use of the APIs is correct.
// Now we're on that page. We should be able to find a link to the failed test in there.
HtmlAnchor anchor = testReportPage.getFirstAnchorByText("tacoshack.meals.NachosTest.testBeanDip");
String href = anchor.getHrefAttribute();
System.out.println("link is : " + href);
Page failureFromLink = anchor.click();
assertGoodStatus(failureFromLink);
// Now check the >>> link -- this is harder, because we can't do the javascript click handler properly
// The summary page is just tack on /summary to the url for the test
}
// Exercises the b-is-not-a-descendant-of-a path.
@LocalData
public void testNonDescendantRelativePath() throws Exception {
FreeStyleBuild build = project.scheduleBuild2(0).get(10, TimeUnit.MINUTES); // leave time for interactive debugging
assertBuildStatus(Result.UNSTABLE, build);
TestResult theOverallTestResult = build.getAction(TestResultAction.class).getResult();
CaseResult theFailedTestCase = theOverallTestResult.getFailedTests().get(0);
String relativePath = theFailedTestCase.getRelativePathFrom(theOverallTestResult);
System.out.println("relative path seems to be: " + relativePath);
assertNotNull("relative path exists", relativePath);
assertFalse("relative path doesn't start with a slash", relativePath.startsWith("/"));
// Now ask for the relative path from the child to the parent -- we should get an absolute path
String relativePath2 = theOverallTestResult.getRelativePathFrom(theFailedTestCase);
System.out.println("relative path2 seems to be: " + relativePath2);
// I know that in a HudsonTestCase we don't have a meaningful root url, so I expect an empty string here.
// If somehow we start being able to produce a root url, then I'll also tolerate a url that starts with that.
boolean pathIsEmptyOrNull = relativePath2 == null || relativePath2.isEmpty();
boolean pathStartsWithRootUrl = !pathIsEmptyOrNull && relativePath2.startsWith(hudson.getRootUrl());
assertTrue("relative path is empty OR begins with the app root", pathIsEmptyOrNull || pathStartsWithRootUrl );
}
}
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.junit;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.*;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import hudson.FilePath;
import hudson.Functions;
import hudson.model.*;
import hudson.slaves.DumbSlave;
import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.TouchBuilder;
import org.jvnet.hudson.test.recipes.LocalData;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class TestResultPublishingTest extends HudsonTestCase {
private FreeStyleProject project;
private JUnitResultArchiver archiver;
private final String BASIC_TEST_PROJECT = "percival";
private final String TEST_PROJECT_WITH_HISTORY = "wonky";
@Override
protected void setUp() throws Exception {
super.setUp();
project = createFreeStyleProject(BASIC_TEST_PROJECT);
archiver = new JUnitResultArchiver("*.xml");
project.getPublishersList().add(archiver);
project.getBuildersList().add(new TouchBuilder());
}
@LocalData
public void testBasic() throws Exception {
FreeStyleBuild build = project.scheduleBuild2(0).get(30, TimeUnit.SECONDS);
assertTestResults(build);
HudsonTestCase.WebClient wc = new HudsonTestCase.WebClient();
wc.getPage(project); // project page
wc.getPage(build); // build page
wc.getPage(build, "testReport"); // test report
wc.getPage(build, "testReport/hudson.security"); // package
wc.getPage(build, "testReport/hudson.security/HudsonPrivateSecurityRealmTest/"); // class
wc.getPage(build, "testReport/hudson.security/HudsonPrivateSecurityRealmTest/testDataCompatibilityWith1_282/"); // method
}
@LocalData
public void testSlave() throws Exception {
DumbSlave s = createOnlineSlave();
project.setAssignedLabel(s.getSelfLabel());
FilePath src = new FilePath(hudson.getRootPath(), "jobs/" + BASIC_TEST_PROJECT + "/workspace/");
assertNotNull(src);
FilePath dest = s.getWorkspaceFor(project);
assertNotNull(dest);
src.copyRecursiveTo("*.xml", dest);
testBasic();
}
/**
* Verify that we can successfully parse and display test results in the
* open junit test result publishing toolchain. Ensure that we meet this
* compatibility requirement:
* From users' point of view, Hudson core JUnit should continue
* to work as if nothing has changed
* - Old testReport URLs should still work
*/
@LocalData
public void testOpenJUnitPublishing() throws IOException, SAXException {
List<Project> projects = this.hudson.getProjects();
// Make sure there's a project named TEST_PROJECT_WITH_HISTORY
Project proj = null;
for (Project p : projects) {
if (p.getName().equals(TEST_PROJECT_WITH_HISTORY)) proj = p;
}
assertNotNull("We should have a project named " + TEST_PROJECT_WITH_HISTORY, proj);
// Validate that there are test results where I expect them to be:
HudsonTestCase.WebClient wc = new HudsonTestCase.WebClient();
// On the project page:
HtmlPage projectPage = wc.getPage(proj);
// we should have a link that reads "Latest Test Result"
// that link should go to http://localhost:8080/job/breakable/lastBuild/testReport/
assertXPath(projectPage, "//a[@href='lastCompletedBuild/testReport/']");
assertXPathValue(projectPage, "//a[@href='lastCompletedBuild/testReport/']", "Latest Test Result");
assertXPathValueContains(projectPage, "//a[@href='lastCompletedBuild/testReport/']", "Latest Test Result");
// after "Latest Test Result" it should say "no failures"
assertXPathResultsContainText(projectPage, "//td", "(no failures)");
// there should be a test result trend graph
assertXPath(projectPage, "//img[@src='test/trend']");
// the trend graph should be served up with a good http status
Page trendGraphPage = wc.goTo(proj.getUrl() + "/test/trend", "image/png");
assertGoodStatus(trendGraphPage);
// The trend graph should be clickable and take us to a run details page
Object imageNode = projectPage.getFirstByXPath("//img[@src='test/trend']");
assertNotNull("couldn't find any matching nodes", imageNode);
assertTrue("image node should be an HtmlImage object", imageNode instanceof HtmlImage);
// TODO: Check that we can click on the graph and get to a particular run. How do I do this with HtmlUnit?
XmlPage xmlProjectPage = wc.goToXml(proj.getUrl() + "/lastBuild/testReport/api/xml");
assertXPath(xmlProjectPage, "/testResult");
assertXPath(xmlProjectPage, "/testResult/suite");
assertXPath(xmlProjectPage, "/testResult/failCount");
assertXPathValue(xmlProjectPage, "/testResult/failCount", "0");
assertXPathValue(xmlProjectPage, "/testResult/passCount", "4");
assertXPathValue(xmlProjectPage, "/testResult/skipCount", "0");
String[] packages = {"org.jvnet.hudson.examples.small.AppTest", "org.jvnet.hudson.examples.small.MiscTest", "org.jvnet.hudson.examples.small.deep.DeepTest"};
for (String packageName : packages) {
assertXPath(xmlProjectPage, "/testResult/suite/case/className[text()='" + packageName + "']");
}
// Go to a page that we know has a failure
HtmlPage buildPage = wc.getPage(proj.getBuildByNumber(3));
assertGoodStatus(buildPage);
// We expect to see one failure, for com.yahoo.breakable.misc.UglyTest.becomeUglier
// which should link to http://localhost:8080/job/wonky/3/testReport/org.jvnet.hudson.examples.small/MiscTest/testEleanor/
assertXPathResultsContainText(buildPage, "//a", "org.jvnet.hudson.examples.small.MiscTest.testEleanor");
HtmlAnchor failingTestLink = buildPage.getFirstAnchorByText("org.jvnet.hudson.examples.small.MiscTest.testEleanor");
assertNotNull(failingTestLink);
Page failingTestPage = failingTestLink.click();
assertGoodStatus(failingTestPage);
// Go to the xml page for a build we know has failures
XmlPage xmlBuildPage = wc.goToXml(proj.getBuildByNumber(3).getUrl() + "/api/xml");
assertXPathValue(xmlBuildPage, "//failCount", "2");
assertXPathValue(xmlBuildPage, "//skipCount", "0");
assertXPathValue(xmlBuildPage, "//totalCount", "4");
assertXPathValue(xmlBuildPage, "//result", "FAILURE");
// Check overall test result counts
XmlPage xmlTestReportPage = wc.goToXml(proj.getBuildByNumber(3).getUrl() + "/testReport/api/xml");
assertXPathValue(xmlTestReportPage, "/testResult/failCount", "2");
assertXPathValue(xmlTestReportPage, "/testResult/passCount", "2");
assertXPathValue(xmlTestReportPage, "/testResult/skipCount", "0");
// Make sure the right tests passed and failed
assertXPathValue(xmlTestReportPage, "/testResult/suite/case[className/text()='org.jvnet.hudson.examples.small.AppTest']/status", "PASSED");
assertXPathValue(xmlTestReportPage, "/testResult/suite/case[name/text()='testEleanor']/status", "FAILED");
// TODO: implement more of these tests
// On the lastBuild/testReport page:
// Breadcrumbs should read #6 > Test Result where Test Result is a link to this page
// inside of div id="main-panel" we should find the text "0 failures (-1)"
// we should have a blue bar which is blue all the way across: div style="width: 100%; height: 1em; background-color: rgb(114, 159, 207);
// we should find the words "7 tests (?0)"
// we should find the words "All Tests"
// we should find a table
// Inside that table, there should be the following rows:
// org.jvnet.hudson.examples.small 0ms 0 -1 0 3
// org.jvnet.hudson.examples.small.deep 4ms 0 0 0 1
Run theRun = proj.getBuildByNumber(7);
assertTestResultsAsExpected(wc, theRun, "/testReport",
"org.jvnet.hudson.examples.small", "0 ms", "SUCCESS",
/* total tests expected, diff */ 3, 0,
/* fail count expected, diff */ 0, -1,
/* skip count expected, diff */ 0, 0);
assertTestResultsAsExpected(wc, theRun, "/testReport",
"org.jvnet.hudson.examples.small.deep", "4 ms", "SUCCESS",
/* total tests expected, diff */ 1, 0,
/* fail count expected, diff */ 0, 0,
/* skip count expected, diff */ 0, 0);
// TODO: more, more, more.
// TODO: test report history by package
}
/**
* Test to demonstrate bug HUDSON-5246, inter-build diffs for junit test results are wrong
*/
@Bug(5246)
@LocalData
public void testInterBuildDiffs() throws IOException, SAXException {
List<Project> projects = this.hudson.getProjects();
// Make sure there's a project named TEST_PROJECT_WITH_HISTORY
Project proj = null;
for (Project p : projects) {
if (p.getName().equals(TEST_PROJECT_WITH_HISTORY)) proj = p;
}
assertNotNull("We should have a project named " + TEST_PROJECT_WITH_HISTORY, proj);
// Validate that there are test results where I expect them to be:
HudsonTestCase.WebClient wc = new HudsonTestCase.WebClient();
Run theRun = proj.getBuildByNumber(4);
assertTestResultsAsExpected(wc, theRun, "/testReport",
"org.jvnet.hudson.examples.small", "12 ms", "FAILURE",
/* total tests expected, diff */ 3, 0,
/* fail count expected, diff */ 1, 0,
/* skip count expected, diff */ 0, 0);
}
/**
* Make sure the open junit publisher shows junit history
* @throws IOException
* @throws SAXException
*/
@LocalData
public void testHistoryPageOpenJunit() throws IOException, SAXException {
List<Project> projects = this.hudson.getProjects();
// Make sure there's a project named breakable
Project proj = null;
for (Project p : projects) {
if (p.getName().equals(TEST_PROJECT_WITH_HISTORY)) {
proj = p;
break;
}
}
assertNotNull("We should have a project named " + TEST_PROJECT_WITH_HISTORY, proj);
// Validate that there are test results where I expect them to be:
HudsonTestCase.WebClient wc = new HudsonTestCase.WebClient();
HtmlPage historyPage = wc.getPage(proj.getBuildByNumber(7),"/testReport/history/");
assertGoodStatus(historyPage);
assertXPath(historyPage, "//img[@id='graph']");
assertXPath(historyPage, "//table[@id='testresult']");
HtmlElement wholeTable = historyPage.getElementById("testresult");
assertNotNull("table with id 'testresult' exists", wholeTable);
assertTrue("wholeTable is a table", wholeTable instanceof HtmlTable);
HtmlTable table = (HtmlTable) wholeTable;
// We really want to call table.getRowCount(), but
// it returns 1, not the real answer,
// because this table has *two* tbody elements,
// and getRowCount() only seems to count the *first* tbody.
// Maybe HtmlUnit can't handle the two tbody's. In any case,
// the tableText.contains tests do a (ahem) passable job
// of detecting whether the history results are present.
String tableText = table.getTextContent();
assertTrue("Table text is missing the project name",
tableText.contains(TEST_PROJECT_WITH_HISTORY));
assertTrue("Table text is missing the build number",
tableText.contains("7"));
assertTrue("Table text is missing the test duration",
tableText.contains("4 ms"));
}
void assertStringEmptyOrNull(String msg, String str) {
if (str==null)
return;
if (str.equals(""))
return;
fail(msg + "(should be empty or null) : \'" + str + "\'");
}
void assertPaneDiffText(String msg, int expectedValue, Object paneObj) {
assertTrue( "paneObj should be an HtmlElement", paneObj instanceof HtmlElement );
String paneText = ((HtmlElement) paneObj).asText();
if (expectedValue==0) {
assertStringEmptyOrNull(msg, paneText);
} else {
String expectedString =
(expectedValue >= 1 ? "+" : "-")
+ Math.abs(expectedValue);
assertEquals(msg, expectedString, paneText);
}
}
void assertTestResultsAsExpected(WebClient wc, Run run, String restOfUrl,
String packageName,
String expectedResult, String expectedDurationStr,
int expectedTotalTests, int expectedTotalDiff,
int expectedFailCount, int expectedFailDiff,
int expectedSkipCount, int expectedSkipDiff) throws IOException, SAXException {
// TODO: verify expectedResult
// TODO: verify expectedDuration
XmlPage xmlPage = wc.goToXml(run.getUrl() + restOfUrl + "/" + packageName + "/api/xml");
int expectedPassCount = expectedTotalTests - expectedFailCount - expectedSkipCount;
// Verify xml results
assertXPathValue(xmlPage, "/packageResult/failCount", Integer.toString(expectedFailCount));
assertXPathValue(xmlPage, "/packageResult/skipCount", Integer.toString(expectedSkipCount));
assertXPathValue(xmlPage, "/packageResult/passCount", Integer.toString(expectedPassCount));
assertXPathValue(xmlPage, "/packageResult/name", packageName);
// TODO: verify html results
HtmlPage testResultPage = wc.getPage(run, restOfUrl);
// Verify inter-build diffs in html table
String xpathToFailDiff = "//table[@id='testresult']//tr[td//span[text()=\"" + packageName + "\"]]/td[4]";
String xpathToSkipDiff = "//table[@id='testresult']//tr[td//span[text()=\"" + packageName + "\"]]/td[6]";
String xpathToTotalDiff = "//table[@id='testresult']//tr[td//span[text()=\"" + packageName + "\"]]/td[last()]";
Object totalDiffObj = testResultPage.getFirstByXPath(xpathToTotalDiff);
assertPaneDiffText("total diff", expectedTotalDiff, totalDiffObj);
Object failDiffObj = testResultPage.getFirstByXPath(xpathToFailDiff);
assertPaneDiffText("failure diff", expectedFailDiff, failDiffObj);
Object skipDiffObj = testResultPage.getFirstByXPath(xpathToSkipDiff);
assertPaneDiffText("skip diff", expectedSkipDiff, skipDiffObj);
// TODO: The link in the table for each of the three packages in the testReport table should link to a by-package page,
// TODO: for example, http://localhost:8080/job/breakable/lastBuild/testReport/com.yahoo.breakable.misc/
}
// TODO: Make sure that we meet this compatibility requirement:
// TODO: From users' point of view, Open Source *Unit publishers should continue to work as if nothing has changed
// TODO: * Old testReport URLs should still work
private void assertTestResults(FreeStyleBuild build) {
TestResultAction testResultAction = build.getAction(TestResultAction.class);
assertNotNull("no TestResultAction", testResultAction);
TestResult result = testResultAction.getResult();
assertNotNull("no TestResult", result);
assertEquals("should have 1 failing test", 1, testResultAction.getFailCount());
assertEquals("should have 1 failing test", 1, result.getFailCount());
assertEquals("should have 132 total tests", 132, testResultAction.getTotalCount());
assertEquals("should have 132 total tests", 132, result.getTotalCount());
}
}
/*
* The MIT License
*
* Copyright (c) 2009, Yahoo!, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.tasks.test;
import hudson.Extension;
/**
* A class to exercise the TestResult extension mechanism.
*/
@Extension
public class TrivialTestResult extends SimpleCaseResult {
/** A list only containing this one result. Useful for returning
* from getXXXTests() methods.
*/
/** A silly world that is all the data associated with this test result */
private String sillyWord;
public TrivialTestResult() {
this("unwordy");
}
public TrivialTestResult(String sillyWord) {
this.sillyWord = sillyWord;
}
public String getSillyWord() {
return sillyWord;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册