JUnitResultArchiver.java 9.4 KB
Newer Older
K
kohsuke 已提交
1 2 3
/*
 * The MIT License
 * 
R
rseguy 已提交
4 5
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Martin Eigenbrodt,
 * Tom Huybrechts, Yahoo!, Inc., Richard Hierlmeier
K
kohsuke 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 * 
 * 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.
 */
K
kohsuke 已提交
25 26
package hudson.tasks.junit;

27
import hudson.AbortException;
K
kohsuke 已提交
28 29
import hudson.Extension;
import hudson.FilePath;
30
import hudson.Launcher;
K
kohsuke 已提交
31 32
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
33
import hudson.model.Action;
K
kohsuke 已提交
34
import hudson.model.BuildListener;
35 36 37
import hudson.model.Descriptor;
import hudson.model.Result;
import hudson.model.Saveable;
38
import hudson.tasks.BuildStepDescriptor;
39
import hudson.tasks.BuildStepMonitor;
K
kohsuke 已提交
40
import hudson.tasks.Publisher;
41
import hudson.tasks.Recorder;
42
import hudson.tasks.junit.TestResultAction.Data;
43
import hudson.tasks.test.TestResultProjectAction;
44
import hudson.util.DescribableList;
K
kohsuke 已提交
45
import hudson.util.FormValidation;
46
import net.sf.json.JSONObject;
K
kohsuke 已提交
47 48
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;
K
kohsuke 已提交
49
import org.kohsuke.stapler.AncestorInPath;
50
import org.kohsuke.stapler.DataBoundConstructor;
K
kohsuke 已提交
51
import org.kohsuke.stapler.QueryParameter;
52
import org.kohsuke.stapler.StaplerRequest;
K
kohsuke 已提交
53

K
kohsuke 已提交
54
import java.io.IOException;
55
import java.text.MessageFormat;
K
kohsuke 已提交
56
import java.util.ArrayList;
57 58
import java.util.Collection;
import java.util.Collections;
K
kohsuke 已提交
59 60
import java.util.List;

K
kohsuke 已提交
61 62
/**
 * Generates HTML report from JUnit test result XML files.
63
 * 
K
kohsuke 已提交
64 65
 * @author Kohsuke Kawaguchi
 */
66
public class JUnitResultArchiver extends Recorder {
67

68 69 70 71
    /**
     * {@link FileSet} "includes" string, like "foo/bar/*.xml"
     */
    private final String testResults;
72

73 74 75 76
    /**
     * If true, retain a suite's complete stdout/stderr even if this is huge and the suite passed.
     * @since 1.358
     */
77
    private final boolean keepLongStdio;
78

K
kohsuke 已提交
79 80 81 82 83
    /**
     * {@link TestDataPublisher}s configured for this archiver, to process the recorded data.
     * For compatibility reasons, can be null.
     * @since 1.320
     */
84
    private final DescribableList<TestDataPublisher, Descriptor<TestDataPublisher>> testDataPublishers;
85

86
    private final Double healthScaleFactor;
87

88 89 90 91 92
	/**
	 * left for backwards compatibility
         * @deprecated since 2009-08-09.
	 */
	@Deprecated
93
	public JUnitResultArchiver(String testResults) {
94
		this(testResults, false, null);
95
	}
96 97 98 99 100 101

    @Deprecated
    public JUnitResultArchiver(String testResults,
            DescribableList<TestDataPublisher, Descriptor<TestDataPublisher>> testDataPublishers) {
        this(testResults, false, testDataPublishers);
    }
102
	
103
	@Deprecated
104 105
	public JUnitResultArchiver(
			String testResults,
106
            boolean keepLongStdio,
107
			DescribableList<TestDataPublisher, Descriptor<TestDataPublisher>> testDataPublishers) {
108 109 110
        this(testResults, keepLongStdio, testDataPublishers, 1.0);
    }

111
	@DataBoundConstructor
112 113 114 115 116
	public JUnitResultArchiver(
			String testResults,
            boolean keepLongStdio,
			DescribableList<TestDataPublisher, Descriptor<TestDataPublisher>> testDataPublishers,
            double healthScaleFactor) {
117
		this.testResults = testResults;
118 119 120
        this.keepLongStdio = keepLongStdio;
		this.testDataPublishers = testDataPublishers;
        this.healthScaleFactor = Math.max(0.0,healthScaleFactor);
121 122
	}

123 124 125
    /**
     * In progress. Working on delegating the actual parsing to the JUnitParser.
     */
126 127 128
    protected TestResult parse(String expandedTestResults, AbstractBuild build, Launcher launcher, BuildListener listener)
            throws IOException, InterruptedException
    {
129
        return new JUnitParser(isKeepLongStdio()).parse(expandedTestResults, build, launcher, listener);
130 131
    }

R
rseguy 已提交
132
    @Override
133 134
	public boolean perform(AbstractBuild build, Launcher launcher,
			BuildListener listener) throws InterruptedException, IOException {
135 136 137 138 139 140
		listener.getLogger().println(Messages.JUnitResultArchiver_Recording());
		TestResultAction action;
		
		final String testResults = build.getEnvironment(listener).expand(this.testResults);

		try {
141
			TestResult result = parse(testResults, build, launcher, listener);
142

143 144 145 146 147 148
			try {
                // TODO can the build argument be omitted now, or is it used prior to the call to addAction?
				action = new TestResultAction(build, result, listener);
			} catch (NullPointerException npe) {
				throw new AbortException(Messages.JUnitResultArchiver_BadXML(testResults));
			}
149
            action.setHealthScaleFactor(getHealthScaleFactor()); // TODO do we want to move this to the constructor?
K
kohsuke 已提交
150
            result.freeze(action);
151 152
			if (result.isEmpty()) {
			    // most likely a configuration error in the job - e.g. false pattern to match the JUnit result files
K
kohsuke 已提交
153
				throw new AbortException(Messages.JUnitResultArchiver_ResultIsEmpty());
154
			}
155

K
kohsuke 已提交
156
            // TODO: Move into JUnitParser [BUG 3123310]
157 158 159
			List<Data> data = new ArrayList<Data>();
			if (testDataPublishers != null) {
				for (TestDataPublisher tdp : testDataPublishers) {
160
					Data d = tdp.getTestData(build, launcher, listener, result);
161 162 163 164 165 166 167 168 169 170 171
					if (d != null) {
						data.add(d);
					}
				}
			}

			action.setData(data);
		} catch (AbortException e) {
			if (build.getResult() == Result.FAILURE)
				// most likely a build failed before it gets to the test phase.
				// don't report confusing error message.
172
				return true;
173 174 175

			listener.getLogger().println(e.getMessage());
			build.setResult(Result.FAILURE);
176
			return true;
177 178 179
		} catch (IOException e) {
			e.printStackTrace(listener.error("Failed to archive test reports"));
			build.setResult(Result.FAILURE);
180
			return true;
181 182
		}

183
		build.addAction(action);
184 185 186

		if (action.getResult().getFailCount() > 0)
			build.setResult(Result.UNSTABLE);
187 188

		return true;
189 190
	}

191 192 193
	/**
	 * Not actually used, but left for backward compatibility
	 * 
M
mindless 已提交
194
	 * @deprecated since 2009-08-10.
195
	 */
196 197 198 199 200 201 202 203 204 205 206 207 208
	protected TestResult parseResult(DirectoryScanner ds, long buildTime)
			throws IOException {
		return new TestResult(buildTime, ds);
	}

	public BuildStepMonitor getRequiredMonitorService() {
		return BuildStepMonitor.NONE;
	}

	public String getTestResults() {
		return testResults;
	}

209 210 211 212 213
    public double getHealthScaleFactor() {
        return healthScaleFactor == null ? 1.0 : healthScaleFactor;
    }

    public DescribableList<TestDataPublisher, Descriptor<TestDataPublisher>> getTestDataPublishers() {
214 215 216
		return testDataPublishers;
	}

217 218 219 220
	@Override
	public Collection<Action> getProjectActions(AbstractProject<?, ?> project) {
		return Collections.<Action>singleton(new TestResultProjectAction(project));
	}
221

R
rseguy 已提交
222 223 224 225 226 227 228
	/**
	 * @return the keepLongStdio
	 */
	public boolean isKeepLongStdio() {
		return keepLongStdio;
	}

229 230
	private static final long serialVersionUID = 1L;

231 232
    @Extension
    public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
233 234 235 236
		public String getDisplayName() {
			return Messages.JUnitResultArchiver_DisplayName();
		}

237 238 239 240
        @Override
        public String getHelpFile() {
            return "/help/tasks/junit/report.html";
        }
241 242 243 244 245

		@Override
		public Publisher newInstance(StaplerRequest req, JSONObject formData)
				throws hudson.model.Descriptor.FormException {
			String testResults = formData.getString("testResults");
246
            boolean keepLongStdio = formData.getBoolean("keepLongStdio");
247 248 249 250 251 252 253 254
			DescribableList<TestDataPublisher, Descriptor<TestDataPublisher>> testDataPublishers = new DescribableList<TestDataPublisher, Descriptor<TestDataPublisher>>(Saveable.NOOP);
            try {
                testDataPublishers.rebuild(req, formData, TestDataPublisher.all());
            } catch (IOException e) {
                throw new FormException(e,null);
            }

            return new JUnitResultArchiver(testResults, keepLongStdio, testDataPublishers);
255 256 257 258 259 260 261 262 263 264 265 266 267 268
		}

		/**
		 * Performs on-the-fly validation on the file mask wildcard.
		 */
		public FormValidation doCheckTestResults(
				@AncestorInPath AbstractProject project,
				@QueryParameter String value) throws IOException {
			return FilePath.validateFileMask(project.getSomeWorkspace(), value);
		}

		public boolean isApplicable(Class<? extends AbstractProject> jobType) {
			return true;
		}
269 270 271 272 273 274 275 276 277 278

        public FormValidation doCheckHealthScaleFactor(@QueryParameter double value) {
            if (value < 1e-7) return FormValidation.warning("Test health reporting disabled");
            return FormValidation.ok(Messages.JUnitResultArchiver_HealthScaleFactorAnalysis(
                    1,
                    (int) (100.0 - Math.max(0.0, Math.min(100.0, 1 * value))),
                    5,
                    (int) (100.0 - Math.max(0.0, Math.min(100.0, 5 * value)))
            ));
        }
279
    }
K
kohsuke 已提交
280
}