All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.tasks.test.AggregatedTestResultPublisher Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *
 * Copyright (c) 2004-2009 Oracle Corporation.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * 
 *    Kohsuke Kawaguchi, Michael B. Donohue, Yahoo!, Inc.
 *
 *
 *******************************************************************************/ 

package hudson.tasks.test;

import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.Extension;
import hudson.Launcher;
import hudson.Util;
import static hudson.Util.fixNull;
import hudson.model.BuildListener;
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.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 java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * Aggregates downstream test reports into a single consolidated report, so that
 * people can see the overall test results in one page when tests are scattered
 * across many different jobs.
 *
 * @author Kohsuke Kawaguchi
 */
public class AggregatedTestResultPublisher extends Recorder {

    /**
     * Jobs to aggregate. Comma separated. Null if triggering downstreams.
     */
    public final String jobs;

    public AggregatedTestResultPublisher(String jobs) {
        this.jobs = Util.fixEmptyAndTrim(jobs);
    }

    public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
        // add a TestResult just so that it can show up later.
        build.addAction(new TestResultAction(jobs, build));
        return true;
    }

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

    /**
     * Action that serves the aggregated record.
     *
     * TODO: persist some information so that even when some of the individuals
     * are gone, we can still retain some useful information.
     */
    public static final class TestResultAction extends AbstractTestResultAction {

        /**
         * Jobs to aggregate. Comma separated. Never null.
         */
        private final String jobs;
        /**
         * The last time the fields of this object is computed from the rest.
         */
        private transient long lastUpdated = 0;
        /**
         * When was the last time any build completed?
         */
        private static long lastChanged = 0;
        private transient int failCount;
        private transient int totalCount;
        private transient List individuals;
        /**
         * Projects that haven't run yet.
         */
        private transient List didntRun;
        private transient List noFingerprints;

        public TestResultAction(String jobs, AbstractBuild owner) {
            super(owner);
            if (jobs == null) {
                // resolve null as the transitive downstream jobs
                StringBuilder buf = new StringBuilder();
                for (AbstractProject p : getProject().getTransitiveDownstreamProjects()) {
                    if (buf.length() > 0) {
                        buf.append(',');
                    }
                    buf.append(p.getFullName());
                }
                jobs = buf.toString();
            }
            this.jobs = jobs;
        }

        /**
         * Gets the jobs to be monitored.
         */
        public Collection getJobs() {
            List r = new ArrayList();
            for (String job : Util.tokenize(jobs, ",")) {
                AbstractProject j = Hudson.getInstance().getItemByFullName(job.trim(), AbstractProject.class);
                if (j != null) {
                    r.add(j);
                }
            }
            return r;
        }

        private AbstractProject getProject() {
            return owner.getProject();
        }

        public int getFailCount() {
            upToDateCheck();
            return failCount;
        }

        public int getTotalCount() {
            upToDateCheck();
            return totalCount;
        }

        public Object getResult() {
            upToDateCheck();
            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 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 void setDescription(TestObject object, String description) {
            throw new AssertionError();
        }

        /**
         * Returns the individual test results that are aggregated.
         */
        public List getIndividuals() {
            upToDateCheck();
            return Collections.unmodifiableList(individuals);
        }

        /**
         * Gets the downstream projects that haven't run yet, but expected to
         * produce test results.
         */
        public List getDidntRun() {
            return Collections.unmodifiableList(didntRun);
        }

        /**
         * Gets the downstream projects that have available test results, but do
         * not appear to have fingerprinting enabled.
         */
        public List getNoFingerprints() {
            return Collections.unmodifiableList(noFingerprints);
        }

        /**
         * Makes sure that the data fields are up to date.
         */
        private synchronized void upToDateCheck() {
            // up to date check
            if (lastUpdated > lastChanged) {
                return;
            }
            lastUpdated = lastChanged + 1;

            int failCount = 0;
            int totalCount = 0;
            List individuals = new ArrayList();
            List didntRun = new ArrayList();
            List noFingerprints = new ArrayList();
            for (AbstractProject job : getJobs()) {
                RangeSet rs = owner.getDownstreamRelationship(job);
                if (rs.isEmpty()) {
                    // is this job expected to produce a test result?
                    Run b = job.getLastSuccessfulBuild();
                    if (b != null && b.getAction(AbstractTestResultAction.class) != null) {
                        if (b.getAction(FingerprintAction.class) != null) {
                            didntRun.add(job);
                        } else {
                            noFingerprints.add(job);
                        }
                    }
                } else {
                    for (int n : rs.listNumbersReverse()) {
                        Run b = job.getBuildByNumber(n);
                        if (b == null) {
                            continue;
                        }
                        if (b.isBuilding() || b.getResult().isWorseThan(Result.UNSTABLE)) {
                            continue;   // don't count them
                        }
                        for (AbstractTestResultAction ta : b.getActions(AbstractTestResultAction.class)) {
                            failCount += ta.getFailCount();
                            totalCount += ta.getTotalCount();
                            individuals.add(ta);
                        }
                        break;
                    }
                }
            }

            this.failCount = failCount;
            this.totalCount = totalCount;
            this.individuals = individuals;
            this.didntRun = didntRun;
            this.noFingerprints = noFingerprints;
        }

        public boolean getHasFingerprintAction() {
            return this.owner.getAction(FingerprintAction.class) != null;
        }

        @Override
        public String getDisplayName() {
            return Messages.AggregatedTestResultPublisher_Title();
        }

        @Override
        public String getUrlName() {
            return "aggregatedTestReport";
        }

        @Extension
        public static class RunListenerImpl extends RunListener {

            @Override
            public void onCompleted(Run run, TaskListener listener) {
                lastChanged = System.currentTimeMillis();
            }
        }
    }

    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor {

        public boolean isApplicable(Class jobType) {
            return true;    // for all types
        }

        public String getDisplayName() {
            return Messages.AggregatedTestResultPublisher_DisplayName();
        }

        @Override
        public String getHelpFile() {
            return "/help/tasks/aggregate-test/help.html";
        }

        public FormValidation doCheck(@AncestorInPath AbstractProject project, @QueryParameter String value) {
            // Require CONFIGURE permission on this project
            if (!project.hasPermission(Item.CONFIGURE)) {
                return FormValidation.ok();
            }

            for (String name : Util.tokenize(fixNull(value), ",")) {
                name = name.trim();
                if (Hudson.getInstance().getItemByFullName(name) == null) {
                    return FormValidation.error(hudson.tasks.Messages.BuildTrigger_NoSuchProject(name, AbstractProject.findNearest(name).getName()));
                }
            }

            return FormValidation.ok();
        }

        @Override
        public AggregatedTestResultPublisher newInstance(StaplerRequest req, JSONObject formData) throws FormException {
            JSONObject s = formData.getJSONObject("specify");
            if (s.isNullObject()) {
                return new AggregatedTestResultPublisher(null);
            } else {
                return new AggregatedTestResultPublisher(s.getString("jobs"));
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy