jenkins.plugins.testopia.TestopiaBuilder.java Source code

Java tutorial

Introduction

Here is the source code for jenkins.plugins.testopia.TestopiaBuilder.java

Source

/*
 * The MIT License
 *
 * Copyright (c) <2012> <Bruno P. Kinoshita>
 * 
 * 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 jenkins.plugins.testopia;

import hudson.AbortException;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.Action;
import hudson.model.BuildListener;
import hudson.model.EnvironmentContributingAction;
import hudson.model.Result;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStep;
import hudson.tasks.Builder;

import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import jenkins.plugins.testopia.result.ResultSeeker;
import jenkins.plugins.testopia.result.ResultSeekerException;
import jenkins.plugins.testopia.result.TestCaseWrapper;
import jenkins.plugins.testopia.util.Messages;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.mozilla.testopia.TestopiaAPI;
import org.mozilla.testopia.model.TestRun;

/**
 * Testopia Builder.
 * @author Bruno P. Kinoshita - http://www.kinoshita.eti.br
 * @since 0.1
 */
public class TestopiaBuilder extends Builder {
    // Used for HTTP basic auth
    private static final String BASIC_HTTP_PASSWORD = "basicPassword";

    private static final Logger LOGGER = Logger.getLogger("jenkins.plugins.testopia");

    /**
     * Testopia installation name.
     */
    protected final String testopiaInstallationName;
    /**
     * Testopia test run ID.
     */
    protected final Integer testRunId;
    /**
     * List of build steps that are executed only once per job execution. 
     */
    protected final List<BuildStep> singleBuildSteps;
    /**
     * List of build steps that are executed before iterating all test cases.
     */
    protected final List<BuildStep> beforeIteratingAllTestCasesBuildSteps;
    /**
     * List of build steps that are executed for each test case.
     */
    protected final List<BuildStep> iterativeBuildSteps;
    /**
     * List of build steps that are executed after iterating all test cases.
     */
    protected final List<BuildStep> afterIteratingAllTestCasesBuildSteps;
    /**
     * If <code>true</code> and a test fails, the build is marked as FAILURE. Otherwise UNSTABLE.
     */
    protected final Boolean failedTestsMarkBuildAsFailure;
    /**
     * List of result seeking strategies.
     */
    protected List<ResultSeeker> resultSeekers;
    /**
     * Le descriptor.
     */
    @Extension
    public static final TestopiaBuilderDescriptor DESCRIPTOR = new TestopiaBuilderDescriptor();

    /**
     * This constructor is bound to a stapler request. The parameters are 
     * passed from Jenkins UI.
     * @param testopiaInstallationName
     * @param testRunId
     * @param singleBuildSteps
     * @param beforeIteratingAllTestCasesBuildSteps
     * @param iterativeBuildSteps
     * @param afterIteratingAllTestCasesBuildSteps
     * @param failedTestsMarkBuildAsFailure
     * @param resultSeekers
     */
    @DataBoundConstructor
    public TestopiaBuilder(String testopiaInstallationName, Integer testRunId, List<BuildStep> singleBuildSteps,
            List<BuildStep> beforeIteratingAllTestCasesBuildSteps, List<BuildStep> iterativeBuildSteps,
            List<BuildStep> afterIteratingAllTestCasesBuildSteps, Boolean failedTestsMarkBuildAsFailure,
            List<ResultSeeker> resultSeekers) {
        this.testopiaInstallationName = testopiaInstallationName;
        this.testRunId = testRunId;
        this.singleBuildSteps = singleBuildSteps;
        this.beforeIteratingAllTestCasesBuildSteps = beforeIteratingAllTestCasesBuildSteps;
        this.iterativeBuildSteps = iterativeBuildSteps;
        this.afterIteratingAllTestCasesBuildSteps = afterIteratingAllTestCasesBuildSteps;
        this.failedTestsMarkBuildAsFailure = failedTestsMarkBuildAsFailure;
        this.resultSeekers = resultSeekers;
    }

    /**
     * @return the testopiaInstallationName
     */
    public String getTestopiaInstallationName() {
        return testopiaInstallationName;
    }

    /**
     * @return the testRunId
     */
    public Integer getTestRunId() {
        return testRunId;
    }

    /**
     * @return the singleBuildSteps
     */
    public List<BuildStep> getSingleBuildSteps() {
        return singleBuildSteps;
    }

    /**
     * @return the beforeIteratingAllTestCasesBuildSteps
     */
    public List<BuildStep> getBeforeIteratingAllTestCasesBuildSteps() {
        return beforeIteratingAllTestCasesBuildSteps;
    }

    /**
     * @return the iterativeBuildSteps
     */
    public List<BuildStep> getIterativeBuildSteps() {
        return iterativeBuildSteps;
    }

    /**
     * @return the afterIteratingAllTestCasesBuildSteps
     */
    public List<BuildStep> getAfterIteratingAllTestCasesBuildSteps() {
        return afterIteratingAllTestCasesBuildSteps;
    }

    /**
     * @return the failedTestsMarkBuildAsFailure
     */
    public Boolean getFailedTestsMarkBuildAsFailure() {
        return failedTestsMarkBuildAsFailure;
    }

    /**
     * @return the resultSeekers
     */
    public List<ResultSeeker> getResultSeekers() {
        return resultSeekers;
    }

    /**
     * @param resultSeekers the resultSeekers to set
     */
    public void setResultSeekers(List<ResultSeeker> resultSeekers) {
        this.resultSeekers = resultSeekers;
    }

    /* (non-Javadoc)
     * @see hudson.tasks.BuildStepCompatibilityLayer#getProjectAction(hudson.model.AbstractProject)
     */
    @Override
    public Action getProjectAction(AbstractProject<?, ?> project) {
        return new TestopiaProjectAction(project);
    }

    /**
     * {@inheritDoc}
     * 
     * Executes Testopia automated tests.
     */
    @Override
    public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
            throws InterruptedException, IOException {
        listener.getLogger().println(Messages.Testopia_Builder_Connecting());
        TestopiaInstallation installation = DESCRIPTOR.getInstallationByName(this.testopiaInstallationName);
        if (installation == null) {
            throw new AbortException(Messages.Testopia_Builder_InvalidInstallation());
        }
        if (StringUtils.isNotBlank(installation.getProperties())) {
            listener.getLogger().println(Messages.Testopia_Builder_PreparingConnectionProperties());
            setProperties(installation.getProperties(), listener);
        }
        TestopiaAPI api = new TestopiaAPI(new URL(installation.getUrl()));
        String token = "";
        try {
            token = api.login(installation.getUsername(), installation.getPassword());
        } catch (Exception e) {
            e.printStackTrace(listener.getLogger());
            throw new AbortException(e.getMessage());
        }
        //TestRun testRun = testRunSvc.get(this.getTestRunId());
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, Messages.Testopia_Builder_Filtering());
        }
        TestRun testCaseRun = api.getTestRun(this.getTestRunId(), token);
        TestopiaSite testopia = new TestopiaSite(api);
        TestCaseWrapper[] testCases = testopia.getTestCases(testCaseRun, api.getTestCases(this.getTestRunId()));
        if (LOGGER.isLoggable(Level.FINE)) {
            for (TestCaseWrapper tc : testCases) {
                LOGGER.log(Level.FINE, Messages.Testopia_Builder_AutomatedTestCase(tc.getId(), tc.getSummary()));
            }
        }
        // sort and filter test cases
        listener.getLogger().println(Messages.Testopia_Builder_SingleBuildSteps());
        this.executeSingleBuildSteps(build, launcher, listener);
        listener.getLogger().println(Messages.Testopia_Builder_IterativeBuildSteps());
        this.executeIterativeBuildSteps(testCases, build, launcher, listener);

        // Here we search for test results. The return if a wrapped Test Case
        // that
        // contains attachments, platform and notes.
        try {
            listener.getLogger().println(Messages.Testopia_Builder_Seeking());

            if (getResultSeekers() != null) {
                for (ResultSeeker resultSeeker : getResultSeekers()) {
                    LOGGER.log(Level.INFO, Messages
                            .Testopia_Builder_SeekingDetails(resultSeeker.getDescriptor().getDisplayName()));
                    resultSeeker.seek(testCases, build, launcher, listener, testopia);
                }
            }
        } catch (ResultSeekerException trse) {
            trse.printStackTrace(listener.fatalError(trse.getMessage()));
            throw new AbortException(Messages.Testopia_Builder_SeekingError(trse.getMessage()));
        }

        // This report is used to generate the graphs and to store the list of
        // test cases with each found status.
        final Report report = testopia.getReport();

        listener.getLogger().println(Messages.Testopia_Builder_Found(report.getTestsTotal()));

        final TestopiaResult result = new TestopiaResult(report, build);
        final TestopiaBuildAction buildAction = new TestopiaBuildAction(build, result);
        build.addAction(buildAction);

        if (report.getFailed() > 0) {
            if (this.failedTestsMarkBuildAsFailure != null && this.failedTestsMarkBuildAsFailure) {
                build.setResult(Result.FAILURE);
            } else {
                build.setResult(Result.UNSTABLE);
            }
        }

        LOGGER.log(Level.INFO, Messages.Testopia_Builder_Finished());

        // end
        return Boolean.TRUE;
    }

    /**
     * Executes the list of single build steps.
     * 
     * @param build
     *            Jenkins build.
     * @param launcher
     * @param listener
     * @throws IOException
     * @throws InterruptedException
     */
    protected void executeSingleBuildSteps(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
            throws IOException, InterruptedException {
        if (singleBuildSteps != null) {
            for (BuildStep b : singleBuildSteps) {
                boolean success = b.perform(build, launcher, listener);
                if (!success) {
                    build.setResult(Result.UNSTABLE);
                }
            }
        }
    }

    /**
     * <p>
     * Executes iterative build steps. For each automated test case found in the
     * array of automated test cases, this method executes the iterative builds
     * steps using Jenkins objects.
     * </p>
     * 
     * @param testCases
     *            array of automated test cases
     * @param launcher
     * @param listener
     * @throws InterruptedException
     * @throws IOException
     */
    protected void executeIterativeBuildSteps(TestCaseWrapper[] testCases, AbstractBuild<?, ?> build,
            Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
        if (beforeIteratingAllTestCasesBuildSteps != null) {
            for (BuildStep b : beforeIteratingAllTestCasesBuildSteps) {
                final boolean success = b.perform(build, launcher, listener);
                if (!success) {
                    build.setResult(Result.UNSTABLE);
                }
            }
        }
        if (iterativeBuildSteps != null) {
            for (TestCaseWrapper automatedTestCase : testCases) {
                if (automatedTestCase == null) {
                    continue;
                }
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.log(Level.FINE, Messages.Testopia_Builder_IterativeBuildStep());
                    LOGGER.log(Level.FINE, Messages.Testopia_Builder_AutomatedTestCase(automatedTestCase.getId(),
                            automatedTestCase.getScript()));
                }
                final EnvVars iterativeEnvVars = Utils.buildTestCaseEnvVars(automatedTestCase);
                build.addAction(new EnvironmentContributingAction() {
                    public void buildEnvVars(AbstractBuild<?, ?> build, EnvVars env) {
                        env.putAll(iterativeEnvVars);
                    }

                    public String getUrlName() {
                        return null;
                    }

                    public String getIconFileName() {
                        return null;
                    }

                    public String getDisplayName() {
                        return null;
                    }
                });
                for (BuildStep b : iterativeBuildSteps) {
                    final boolean success = b.perform(build, launcher, listener);
                    if (!success) {
                        build.setResult(Result.UNSTABLE);
                    }
                }
            }
        }
        if (afterIteratingAllTestCasesBuildSteps != null) {
            for (BuildStep b : afterIteratingAllTestCasesBuildSteps) {
                final boolean success = b.perform(build, launcher, listener);
                if (!success) {
                    build.setResult(Result.UNSTABLE);
                }
            }
        }
    }

    /**
     * <p>Define properties. Following is the list of available properties.</p>
     * 
     * <ul>
     *     <li>xmlrpc.basicEncoding</li>
      *     <li>xmlrpc.basicPassword</li>
      *     <li>xmlrpc.basicUsername</li>
      *     <li>xmlrpc.connectionTimeout</li>
      *     <li>xmlrpc.contentLengthOptional</li>
      *     <li>xmlrpc.enabledForExceptions</li>
      *     <li>xmlrpc.encoding</li>
      *     <li>xmlrpc.gzipCompression</li>
      *     <li>xmlrpc.gzipRequesting</li>
      *     <li>xmlrpc.replyTimeout</li>
      *     <li>xmlrpc.userAgent</li>
     * </ul>
     * 
     * @param properties List of comma separated properties
     * @param listener Jenkins Build listener
     */
    public static void setProperties(String properties, BuildListener listener) {
        if (StringUtils.isNotBlank(properties)) {
            final StringTokenizer tokenizer = new StringTokenizer(properties, ",");

            if (tokenizer.countTokens() > 0) {
                while (tokenizer.hasMoreTokens()) {
                    String systemProperty = tokenizer.nextToken();
                    maybeAddSystemProperty(systemProperty, listener);
                }
            }
        }
    }

    /**
     * Maybe adds a system property if it is in format <key>=<value>.
     * 
     * @param systemProperty System property entry in format <key>=<value>.
     * @param listener Jenkins Build listener
     */
    public static void maybeAddSystemProperty(String systemProperty, BuildListener listener) {
        final StringTokenizer tokenizer = new StringTokenizer(systemProperty, "=:");
        if (tokenizer.countTokens() == 2) {
            final String key = tokenizer.nextToken();
            final String value = tokenizer.nextToken();

            if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
                if (key.contains(BASIC_HTTP_PASSWORD)) {
                    listener.getLogger().println(Messages.Testopia_Builder_Password(key));
                } else {
                    listener.getLogger().println(Messages.Testopia_Builder_Setting(key, value));
                }
                try {
                    System.setProperty(key, value);
                } catch (SecurityException se) {
                    se.printStackTrace(listener.getLogger());
                }

            }
        }
    }
}