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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi, Michael B. Donohue, Yahoo!, Inc., Andrew Bayer
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.tasks.test;
25
26 import hudson.model.AbstractBuild;
27 import hudson.model.AbstractProject;
28 import hudson.model.AutoCompletionCandidates;
29 import hudson.Extension;
30 import hudson.Launcher;
31 import hudson.Util;
32 import static hudson.Util.fixNull;
33 import hudson.model.BuildListener;
34 import hudson.model.Fingerprint.RangeSet;
35 import hudson.model.ItemGroup;
36 import jenkins.model.Jenkins;
37 import hudson.model.Item;
38 import hudson.model.Job;
39 import hudson.model.Result;
40 import hudson.model.Run;
41 import hudson.model.TaskListener;
42 import hudson.model.listeners.RunListener;
43 import hudson.tasks.BuildStepDescriptor;
44 import hudson.tasks.BuildStepMonitor;
45 import hudson.tasks.Fingerprinter.FingerprintAction;
46 import hudson.tasks.Publisher;
47 import hudson.tasks.Recorder;
48 import hudson.util.FormValidation;
49 import net.sf.json.JSONObject;
50 import org.kohsuke.stapler.AncestorInPath;
51 import org.kohsuke.stapler.QueryParameter;
52 import org.kohsuke.stapler.StaplerRequest;
53
54 import java.io.IOException;
55 import java.util.ArrayList;
56 import java.util.Collection;
57 import java.util.Collections;
58 import java.util.List;
59
60 /**
61 * Aggregates downstream test reports into a single consolidated report,
62 * so that people can see the overall test results in one page
63 * when tests are scattered across many different jobs.
64 *
65 * @author Kohsuke Kawaguchi
66 */
67 public class AggregatedTestResultPublisher extends Recorder {
68 /**
69 * Jobs to aggregate. Comma separated.
70 * Null if triggering downstreams.
71 */
72 public final String jobs;
73
74 /**
75 * Should failed builds be included?
76 */
77 public final boolean includeFailedBuilds;
78
79 public AggregatedTestResultPublisher(String jobs) {
80 this(jobs, false);
81 }
82
83 public AggregatedTestResultPublisher(String jobs, boolean includeFailedBuilds) {
84 this.jobs = Util.fixEmptyAndTrim(jobs);
85 this.includeFailedBuilds = includeFailedBuilds;
86 }
87
88 public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
89 // add a TestResult just so that it can show up later.
90 build.addAction(new TestResultAction(jobs,includeFailedBuilds,build));
91 return true;
92 }
93
94 public BuildStepMonitor getRequiredMonitorService() {
95 return BuildStepMonitor.NONE;
96 }
97
98 /**
99 * Action that serves the aggregated record.
100 *
101 * TODO: persist some information so that even when some of the individuals
102 * are gone, we can still retain some useful information.
103 */
104 public static final class TestResultAction extends AbstractTestResultAction {
105 /**
106 * Jobs to aggregate. Comma separated.
107 * Never null.
108 */
109 private final String jobs;
110
111 /**
112 * Should failed builds be included?
113 */
114 private final boolean includeFailedBuilds;
115
116 /**
117 * The last time the fields of this object is computed from the rest.
118 */
119 private transient long lastUpdated = 0;
120 /**
121 * When was the last time any build completed?
122 */
123 private static long lastChanged = 0;
124
125 private transient int failCount;
126 private transient int totalCount;
127 private transient List<AbstractTestResultAction> individuals;
128 /**
129 * Projects that haven't run yet.
130 */
131 private transient List<AbstractProject> didntRun;
132 private transient List<AbstractProject> noFingerprints;
133
134 public TestResultAction(String jobs, boolean includeFailedBuilds, AbstractBuild<?,?> owner) {
135 super(owner);
136 this.includeFailedBuilds = includeFailedBuilds;
137
138 if(jobs==null) {
139 // resolve null as the transitive downstream jobs
140 StringBuilder buf = new StringBuilder();
141 for (AbstractProject p : getProject().getTransitiveDownstreamProjects()) {
142 if(buf.length()>0) buf.append(',');
143 buf.append(p.getFullName());
144 }
145 jobs = buf.toString();
146 }
147 this.jobs = jobs;
148 }
149
150 /**
151 * Gets the jobs to be monitored.
152 */
153 public Collection<AbstractProject> getJobs() {
154 List<AbstractProject> r = new ArrayList<AbstractProject>();
155 for (String job : Util.tokenize(jobs,",")) {
156 AbstractProject j = Jenkins.getInstance().getItemByFullName(job.trim(), AbstractProject.class);
157 if(j!=null)
158 r.add(j);
159 }
160 return r;
161 }
162
163 public boolean getIncludeFailedBuilds() {
164 return includeFailedBuilds;
165 }
166
167 private AbstractProject<?,?> getProject() {
168 return owner.getProject();
169 }
170
171 public int getFailCount() {
172 upToDateCheck();
173 return failCount;
174 }
175
176 public int getTotalCount() {
177 upToDateCheck();
178 return totalCount;
179 }
180
181 public Object getResult() {
182 upToDateCheck();
183 return this;
184 }
185
186 /**
187 * Since there's no TestObject that points this action as the owner
188 * (aggregated {@link TestObject}s point to their respective real owners, not 'this'),
189 * so this method should be never invoked.
190 *
191 * @deprecated
192 * so that IDE warns you if you accidentally try to call it.
193 */
194 @Override
195 protected String getDescription(TestObject object) {
196 throw new AssertionError();
197 }
198
199 /**
200 * See {@link #getDescription(TestObject)}
201 *
202 * @deprecated
203 * so that IDE warns you if you accidentally try to call it.
204 */
205 @Override
206 protected void setDescription(TestObject object, String description) {
207 throw new AssertionError();
208 }
209
210 /**
211 * Returns the individual test results that are aggregated.
212 */
213 public List<AbstractTestResultAction> getIndividuals() {
214 upToDateCheck();
215 return Collections.unmodifiableList(individuals);
216 }
217
218 /**
219 * Gets the downstream projects that haven't run yet, but
220 * expected to produce test results.
221 */
222 public List<AbstractProject> getDidntRun() {
223 return Collections.unmodifiableList(didntRun);
224 }
225
226 /**
227 * Gets the downstream projects that have available test results, but
228 * do not appear to have fingerprinting enabled.
229 */
230 public List<AbstractProject> getNoFingerprints() {
231 return Collections.unmodifiableList(noFingerprints);
232 }
233
234 /**
235 * Makes sure that the data fields are up to date.
236 */
237 private synchronized void upToDateCheck() {
238 // up to date check
239 if(lastUpdated>lastChanged) return;
240 lastUpdated = lastChanged+1;
241
242 int failCount = 0;
243 int totalCount = 0;
244 List<AbstractTestResultAction> individuals = new ArrayList<AbstractTestResultAction>();
245 List<AbstractProject> didntRun = new ArrayList<AbstractProject>();
246 List<AbstractProject> noFingerprints = new ArrayList<AbstractProject>();
247 for (AbstractProject job : getJobs()) {
248 RangeSet rs = owner.getDownstreamRelationship(job);
249 if(rs.isEmpty()) {
250 // is this job expected to produce a test result?
251 Run b;
252 if (includeFailedBuilds) {
253 b = job.getLastBuild();
254 } else {
255 b = job.getLastSuccessfulBuild();
256 }
257 if(b!=null && b.getAction(AbstractTestResultAction.class)!=null) {
258 if(b.getAction(FingerprintAction.class)!=null) {
259 didntRun.add(job);
260 } else {
261 noFingerprints.add(job);
262 }
263 }
264 } else {
265 for (int n : rs.listNumbersReverse()) {
266 Run b = job.getBuildByNumber(n);
267 if(b==null) continue;
268 Result targetResult;
269 if (includeFailedBuilds) {
270 targetResult = Result.FAILURE;
271 } else {
272 targetResult = Result.UNSTABLE;
273 }
274
275 if(b.isBuilding() || b.getResult().isWorseThan(targetResult))
276 continue; // don't count them
277
278 for( AbstractTestResultAction ta : b.getActions(AbstractTestResultAction.class)) {
279 failCount += ta.getFailCount();
280 totalCount += ta.getTotalCount();
281 individuals.add(ta);
282 }
283 break;
284 }
285 }
286 }
287
288 this.failCount = failCount;
289 this.totalCount = totalCount;
290 this.individuals = individuals;
291 this.didntRun = didntRun;
292 this.noFingerprints = noFingerprints;
293 }
294
295 public boolean getHasFingerprintAction() {
296 return this.owner.getAction(FingerprintAction.class)!=null;
297 }
298
299 @Override
300 public String getDisplayName() {
301 return Messages.AggregatedTestResultPublisher_Title();
302 }
303
304 @Override
305 public String getUrlName() {
306 return "aggregatedTestReport";
307 }
308
309 @Extension
310 public static class RunListenerImpl extends RunListener<Run> {
311 @Override
312 public void onCompleted(Run run, TaskListener listener) {
313 lastChanged = System.currentTimeMillis();
314 }
315 }
316 }
317
318 @Extension
319 public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {
320 public boolean isApplicable(Class<? extends AbstractProject> jobType) {
321 return true; // for all types
322 }
323
324 public String getDisplayName() {
325 return Messages.AggregatedTestResultPublisher_DisplayName();
326 }
327
328 @Override
329 public String getHelpFile() {
330 return "/help/tasks/aggregate-test/help.html";
331 }
332
333 public FormValidation doCheck(@AncestorInPath AbstractProject project, @QueryParameter String value) {
334 // Require CONFIGURE permission on this project
335 if(!project.hasPermission(Item.CONFIGURE)) return FormValidation.ok();
336
337 for (String name : Util.tokenize(fixNull(value), ",")) {
338 name = name.trim();
339 if(Jenkins.getInstance().getItem(name,project)==null)
340 return FormValidation.error(hudson.tasks.Messages.BuildTrigger_NoSuchProject(name,AbstractProject.findNearest(name).getName()));
341 }
342
343 return FormValidation.ok();
344 }
345
346 @Override
347 public AggregatedTestResultPublisher newInstance(StaplerRequest req, JSONObject formData) throws FormException {
348 JSONObject s = formData.getJSONObject("specify");
349 if(s.isNullObject())
350 return new AggregatedTestResultPublisher(null, req.getParameter("includeFailedBuilds") != null);
351 else
352 return new AggregatedTestResultPublisher(s.getString("jobs"), req.getParameter("includeFailedBuilds") != null);
353 }
354
355 public AutoCompletionCandidates doAutoCompleteJobs(@QueryParameter String value, @AncestorInPath Item self, @AncestorInPath ItemGroup container) {
356 return AutoCompletionCandidates.ofJobNames(Job.class,value,self,container);
357 }
358 }
359
360 }
361