hudson.tasks.test.TestObject.java Source code

Java tutorial

Introduction

Here is the source code for hudson.tasks.test.TestObject.java

Source

/*
 * The MIT License
 * 
 * Copyright (c) 2004-2010, 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 jenkins.model.Jenkins;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.*;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

import com.google.common.collect.MapMaker;

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;

    /**
     * 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();
    }

    public String getFullDisplayName() {
        return getDisplayName();
    }

    /**
     * Returns the top level test result data.
     *
     * @deprecated This method returns a JUnit specific class. Use
     * {@link #getTopLevelTestResult()} instead for a more general interface.
     */
    @Override
    public hudson.tasks.junit.TestResult getTestResult() {
        TestObject parent = getParent();

        return (parent == null ? null : getParent().getTestResult());
    }

    /**
     * Returns the top level test result data.
     */
    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 ancestors 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
            Run<?, ?> myBuild = cur.getRun();
            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 = Jenkins.getInstance().getRootUrl();
                if (hudsonRootUrl == null || hudsonRootUrl.length() == 0) {
                    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() {
        Run<?, ?> owner = getRun();
        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. 
     */
    @Override
    @Exported(visibility = 3)
    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
     */
    @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();

    @Deprecated
    public TestResult getResultInBuild(AbstractBuild<?, ?> build) {
        return (TestResult) super.getResultInBuild(build);
    }

    /**
     * Gets the counterpart of this {@link TestResult} in the specified run.
     *
     * @return null if no such counter part exists.
     */
    @Override
    public TestResult getResultInRun(Run<?, ?> run) {
        return (TestResult) super.getResultInRun(run);
    }

    /**
     * Find the test result corresponding to the one identified by <code>id></code>
     * within 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 full name of this object.
     * @since 1.551
     */
    public String getFullName() {
        StringBuilder sb = new StringBuilder(getName());
        if (getParent() != null) {
            sb.insert(0, " : ");
            sb.insert(0, getParent().getFullName());
        }
        return sb.toString();
    }

    /**
     * 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 String uniquifyName(Collection<? extends TestObject> siblings, String base) {
        synchronized (UNIQUIFIED_NAMES) {
            String uniquified = base;
            Map<TestObject, Void> taken = UNIQUIFIED_NAMES.get(base);
            if (taken == null) {
                taken = new WeakHashMap<TestObject, Void>();
                UNIQUIFIED_NAMES.put(base, taken);
            } else {
                Set<TestObject> similars = new HashSet<TestObject>(taken.keySet());
                similars.retainAll(new HashSet<TestObject>(siblings));
                if (!similars.isEmpty()) {
                    uniquified = base + '_' + (similars.size() + 1);
                }
            }
            taken.put(this, null);
            return uniquified;
        }
    }

    private static final Map<String, Map<TestObject, Void>> UNIQUIFIED_NAMES = new MapMaker().makeMap();

    /**
     * Replaces URL-unsafe characters.
     *
     * If s is an empty string, returns "(empty)" otherwise the generated URL would contain
     * two slashes one after the other and getDynamic() would fail
     */
    public static String safe(String s) {
        if (StringUtils.isEmpty(s)) {
            return "(empty)";
        } else {
            // this still seems to be a bit faster than a single replace with regexp
            return s.replace('/', '_').replace('\\', '_').replace(':', '_').replace('?', '_').replace('#', '_')
                    .replace('%', '_');
            // Note: we probably should some helpers like Commons URIEscapeUtils here to escape all invalid URL chars, but then we
            // still would have to escape /, ? and so on
        }
    }

    /**
     * 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 (getRun() == null) {
            LOGGER.severe("getRun() is null, can't save description.");
        } else {
            getRun().checkPermission(Run.UPDATE);
            setDescription(description);
            getRun().save();
        }

        return new HttpRedirect(".");
    }

    private static final long serialVersionUID = 1L;
}