com.michelin.cio.hudson.plugins.qc.QualityCenter.java Source code

Java tutorial

Introduction

Here is the source code for com.michelin.cio.hudson.plugins.qc.QualityCenter.java

Source

/*
 * The MIT License
 *
 * Copyright (c) 2010-2012, Manufacture Franaise des Pneumatiques Michelin,
 * Thomas Maurel, Romain Seguy
 *
 * 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 com.michelin.cio.hudson.plugins.qc;

import com.michelin.cio.hudson.plugins.qc.client.QualityCenterClientInstallation;
import com.michelin.cio.hudson.plugins.qc.qtpaddins.QualityCenterQTPAddinsInstallation;
import hudson.AbortException;
import hudson.CopyOnWrite;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Computer;
import hudson.model.Hudson;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.tools.ToolInstallation;
import hudson.util.ArgumentListBuilder;
import hudson.util.FormValidation;
import hudson.util.VariableResolver;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

/**
 * {@link BuildStep} to run a TestSet from a Quality Center server.
 * 
 * @author Thomas Maurel
 */
public class QualityCenter extends Builder {

    private final static String VB_SCRIPT_NAME = "runTestSet.vbs";

    public final static String RUN_MODE_LOCAL = "RUN_LOCAL";
    public final static String RUN_MODE_PLANNED_HOST = "RUN_PLANNED_HOST";
    public final static String RUN_MODE_REMOTE = "RUN_REMOTE";
    public final static String[] RUN_MODES = { RUN_MODE_PLANNED_HOST, RUN_MODE_REMOTE, RUN_MODE_LOCAL };

    public final static int DEFAULT_TIMEOUT = 600;

    /** Quality Center installation name. */
    private final String qcClientInstallationName;
    /** QTP Addin for Quality Center installation name. */
    private final String qcQTPAddinInstallationName;
    /** Quality Center server URL. */
    private final String qcServerURL;
    /** Username to log into the Quality Center server. */
    private final String qcLogin;
    /** The password associated to the username to log into the Quality Center server. */
    private final String qcPass;
    /** The domain where the Quality Center project is located. */
    private final String qcDomain;
    /** The Quality Center project to connect to. */
    private final String qcProject;
    /** The folder where the TestSet to run is located. */
    private final String qcTSFolder;
    /** The name of the TestSet to run. */
    private final String qcTSName;
    /** The name of the report file. */
    private final String qcTSLogFile;
    /** Timeout */
    private final int qcTimeOut;
    /** The parsed name (env vars) of the report file. */
    private String parsedQcTSLogFile;
    private String runMode;
    private String runHost;

    // parsedQcTSLogFiles is used by QualityCenterResultArchiver in order to gather
    // the name of the report files which have been generated by the build step. This
    // is mandatory to have this because the name of the generated report files may
    // not be the one specified by the user (which happens if several test sets have
    // to be run in one build step and if the name specified by the user can't be
    // unique).
    private transient List<String> testSetLogFiles;

    @DataBoundConstructor
    public QualityCenter(String qcClientInstallationName, String qcQTPAddinInstallationName, String qcServerURL,
            String qcLogin, String qcPass, String qcDomain, String qcProject, String qcTSFolder, String qcTSName,
            String qcTSLogFile, int qcTimeOut, String runMode, String runHost) {
        this.qcClientInstallationName = qcClientInstallationName;
        this.qcQTPAddinInstallationName = qcQTPAddinInstallationName;
        this.qcServerURL = qcServerURL;
        this.qcLogin = qcLogin;
        this.qcPass = qcPass;
        this.qcDomain = qcDomain;
        this.qcProject = qcProject;
        this.qcTSFolder = qcTSFolder;
        this.qcTSName = qcTSName;
        this.qcTSLogFile = qcTSLogFile;
        if (qcTimeOut <= 0) {
            this.qcTimeOut = DEFAULT_TIMEOUT;
        } else {
            this.qcTimeOut = qcTimeOut;
        }
        if (Arrays.asList(RUN_MODES).contains(runMode)) {
            this.runMode = runMode;
        } else {
            this.runMode = RUN_MODE_PLANNED_HOST;
        }
        if (this.runMode.equals(RUN_MODE_REMOTE)) {
            this.runHost = runHost;
        } else {
            this.runHost = "";
        }
    }

    public String getQcDomain() {
        return qcDomain;
    }

    public String getQcClientInstallationName() {
        return qcClientInstallationName;
    }

    public String getQcQTPAddinInstallationName() {
        return qcQTPAddinInstallationName;
    }

    public String getQcLogin() {
        return qcLogin;
    }

    public String getQcPass() {
        return qcPass;
    }

    public String getQcProject() {
        return qcProject;
    }

    public String getQcServerURL() {
        return qcServerURL;
    }

    public String getQcTSFolder() {
        return qcTSFolder;
    }

    public String getQcTSLogFile() {
        return qcTSLogFile;
    }

    public String getParsedQcTSLogFile() {
        return parsedQcTSLogFile;
    }

    public String getQcTSName() {
        return qcTSName;
    }

    public int getQcTimeOut() {
        return qcTimeOut;
    }

    public String getRunMode() {
        return runMode;
    }

    public String getRunHost() {
        return runHost;
    }

    /**
     * @see QualityCenterResultArchiver#perform(hudson.model.AbstractBuild, hudson.Launcher, hudson.model.BuildListener) 
     */
    List<String> getTestSetLogFiles() {
        return testSetLogFiles;
    }

    public static String getVbScriptName() {
        return VB_SCRIPT_NAME;
    }

    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl) super.getDescriptor();
    }

    public QualityCenterClientInstallation getQualityCenterClientInstallation() {
        for (QualityCenterClientInstallation installation : getDescriptor().getClientInstallations()) {
            if (this.qcClientInstallationName != null
                    && installation.getName().equals(this.qcClientInstallationName)) {
                return installation;
            }
        }
        return null;
    }

    public QualityCenterQTPAddinsInstallation getQualityCenterQTPAddinInstallation() {
        for (QualityCenterQTPAddinsInstallation installation : getDescriptor().getQTPAddinsInstallations()) {
            if (this.qcQTPAddinInstallationName != null
                    && installation.getName().equals(this.qcQTPAddinInstallationName)) {
                return installation;
            }
        }
        return null;
    }

    @Override
    public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
            throws InterruptedException, IOException {
        EnvVars env = build.getEnvironment(listener);

        // Has a QC installation been set? If yes, is it really a QC installation?
        QualityCenterClientInstallation qcInstallation = getQualityCenterClientInstallation();
        if (qcInstallation == null) {
            // No installation has been set
            listener.fatalError(Messages.QualityCenter_NoInstallationSet());
            return false;
        } else {
            // Get an installation instance for this specific node.
            // Will run the QC Client auto-installer if not installed on this node
            qcInstallation = qcInstallation.forNode(Computer.currentComputer().getNode(), listener);
            // Will be null if not on a Windows Node
            if (qcInstallation == null) {
                listener.fatalError(Messages.QualityCenter_NotAvailableOnThisOS());
                return false;
            }

            qcInstallation = qcInstallation.forEnvironment(env);
            String qcDll = qcInstallation.getQCDll(launcher);
            // If we cant find the OTAClient DLL, then we can't run the testSet
            if (qcDll == null) {
                listener.fatalError(Messages.QualityCenter_DllNotFound());
                return false;
            }

            // Get an installation instance of the QTP Addin
            // QTP Addin must be installed but we dont need to call it directly.
            // It means defining QTPAddinsInstallations is only useful to auto-install it on nodes
            QualityCenterQTPAddinsInstallation qcQTPInstallation = getQualityCenterQTPAddinInstallation();

            if (qcQTPInstallation != null) {
                // Get the QTPAddin for this node, and install it if necessary
                qcQTPInstallation = qcQTPInstallation.forNode(Computer.currentComputer().getNode(), listener);
                qcQTPInstallation = qcQTPInstallation.forEnvironment(env);
            }

            FilePath projectWS = build.getWorkspace();
            // Get the URL to the VBScript used to run the test, which is bundled in the plugin
            URL vbsUrl = Hudson.getInstance().pluginManager.uberClassLoader.getResource(VB_SCRIPT_NAME);
            if (vbsUrl == null) {
                listener.fatalError(Messages.QualityCenter_VBSNotFound());
                return false;
            }

            // Copy the script to the project workspace
            FilePath vbScript = projectWS.child(VB_SCRIPT_NAME);
            vbScript.copyFrom(vbsUrl);

            try {
                testSetLogFiles = new ArrayList<String>();

                // For each TestSet, run the VBScript
                String[] testSetNames = Util
                        .replaceMacro(env.expand(this.qcTSName), build.getBuildVariableResolver())
                        .split("[\t\r\n,]+");
                for (String testSetName : testSetNames) {
                    String logFile = runVBScript(testSetName, build, launcher, listener, vbScript,
                            (testSetNames.length == 1));
                    // Has the report been successfuly generated?
                    if (!projectWS.child(logFile).exists()) {
                        listener.fatalError(Messages.QualityCenter_ReportNotGenerated());
                        return false;
                    }
                }
            } catch (IOException ioe) {
                Util.displayIOException(ioe, listener);
                return false;
            } finally {
                // Remove the VBScript from workspace
                vbScript.delete();
            }
        }

        return true;
    }

    /**
     * Adds some {@link EnvVars} to the project's {@link EnvVars} table.
     * 
     * <p>This {@link EnvVars} can be used in the report file name.</p>
     */
    private void pushEnvVars(EnvVars env) {
        env.put("QC_DOMAIN", this.qcDomain);
        env.put("QC_PROJECT", this.qcProject);
        env.put("TS_FOLDER", this.qcTSFolder);
        env.put("TS_NAME", this.qcTSName);
    }

    /**
     * Removes the {@link EnvVars} from the project's {@link EnvVars} table.
     */
    private void removeEnvVars(EnvVars env) {
        env.remove("QC_DOMAIN");
        env.remove("QC_PROJECT");
        env.remove("TS_FOLDER");
        env.remove("TS_NAME");
    }

    /**
     * Runs the given TestSet ({@code testSetName}) through VBScript.
     */
    private String runVBScript(String testSetName, AbstractBuild<?, ?> build, Launcher launcher,
            BuildListener listener, FilePath file, boolean isRunOnce) throws IOException, InterruptedException {
        ArgumentListBuilder args = new ArgumentListBuilder();
        EnvVars env = build.getEnvironment(listener);
        VariableResolver<String> varResolver = build.getBuildVariableResolver();
        PrintStream out = listener.getLogger();

        // Add the qc specific env vars
        pushEnvVars(env);

        // Parse the report file name using env vars
        this.parsedQcTSLogFile = Util.replaceMacro(env.expand(this.qcTSLogFile), varResolver);
        if (!parsedQcTSLogFile.endsWith(".xml")) {
            parsedQcTSLogFile = parsedQcTSLogFile + ".xml";
        }
        if (!isRunOnce && !parsedQcTSLogFile.contains(testSetName)) {
            // JENKINS-12384: One file must be generated per test set. As such we must
            // ensure that the name of each file is unigue. We consider it is the case
            // if the name of the file contains the name of the test set. Otherwise,
            // we add it.
            parsedQcTSLogFile = parsedQcTSLogFile.substring(0, parsedQcTSLogFile.length() - 4) + '_' + testSetName
                    + ".xml";
        }
        testSetLogFiles.add(parsedQcTSLogFile);

        // Use cscript to run the vbscript and get the console output

        args.add("cscript");
        args.add("/nologo");
        args.add(file);
        args.add(Util.replaceMacro(env.expand(this.qcServerURL), varResolver));
        args.add(Util.replaceMacro(env.expand(this.qcLogin), varResolver));

        // If no password, then replace by ""
        if (StringUtils.isNotBlank(this.qcPass)) {
            args.addMasked(Util.replaceMacro(env.expand(this.qcPass), varResolver));
        } else {
            args.addMasked("\"\"");
        }
        args.add(Util.replaceMacro(env.expand(this.qcDomain), varResolver));
        args.add(Util.replaceMacro(env.expand(this.qcProject), varResolver));
        args.add(Util.replaceMacro(env.expand(this.qcTSFolder), varResolver));
        args.add(Util.replaceMacro(env.expand(testSetName), varResolver));
        args.add(this.parsedQcTSLogFile);
        args.add(this.qcTimeOut);
        args.add(runMode);
        if (runMode.equals(RUN_MODE_REMOTE)) {
            args.add(runHost);
        }

        // Remove qc specific environment variables
        removeEnvVars(env);

        // Run the script on node
        // Execution result should be 0
        if (launcher.launch().cmds(args).stdout(out).pwd(file.getParent()).join() != 0) {
            listener.fatalError(Messages.QualityCenter_TSSchedulerFailed());

            // log file is in UTF-16
            InputStream is = new FileInputStream(this.parsedQcTSLogFile);
            InputStreamReader in = new InputStreamReader(is, "UTF-16");
            try {
                // Copy the console output to our logger
                IOUtils.copy(in, new OutputStreamWriter(out));
            } finally {
                in.close();
                is.close();
            }
            throw new AbortException();
        }

        return this.parsedQcTSLogFile;
    }

    @Extension
    public static class DescriptorImpl extends BuildStepDescriptor<Builder> {

        /**
         * Installations of the QCClient
         */
        @CopyOnWrite
        private volatile QualityCenterClientInstallation[] clientInstallations = new QualityCenterClientInstallation[0];

        /**
         * Installations of the QTP Addin
         */
        @CopyOnWrite
        private volatile QualityCenterQTPAddinsInstallation[] qtpAddinsInstallations = new QualityCenterQTPAddinsInstallation[0];

        public DescriptorImpl() {
            load();
        }

        protected DescriptorImpl(Class<? extends QualityCenter> clazz) {
            super(clazz);
        }

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

        public QualityCenterClientInstallation.DescriptorImpl getToolDescriptor() {
            return ToolInstallation.all().get(QualityCenterClientInstallation.DescriptorImpl.class);
        }

        @Override
        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
            if (getClientInstallations() != null && getClientInstallations().length > 0
                    && getQTPAddinsInstallations() != null && getQTPAddinsInstallations().length > 0) {
                return true;
            }
            return false;
        }

        @Override
        public Builder newInstance(StaplerRequest req, JSONObject formData) throws FormException {
            return req.bindJSON(QualityCenter.class, formData);
        }

        public QualityCenterClientInstallation[] getClientInstallations() {
            return clientInstallations;
        }

        public void setClientInstallations(QualityCenterClientInstallation... installations) {
            this.clientInstallations = installations;
            save();
        }

        public QualityCenterQTPAddinsInstallation[] getQTPAddinsInstallations() {
            return qtpAddinsInstallations;
        }

        public void setQTPAddinsInstallations(QualityCenterQTPAddinsInstallation... installations) {
            this.qtpAddinsInstallations = installations;
            save();
        }

        public FormValidation doCheckQcServerURL(@QueryParameter String value) {
            return QualityCenterUtils.checkQcServerURL(value);
        }

        public FormValidation doCheckQcLogin(@QueryParameter String value) {
            if (StringUtils.isBlank(value)) {
                return FormValidation.error(Messages.QualityCenter_UsernameShouldBeDefined());
            }
            return FormValidation.ok();
        }

        public FormValidation doCheckQcDomain(@QueryParameter String value) {
            if (StringUtils.isBlank(value)) {
                return FormValidation.error(Messages.QualityCenter_DomainShouldBeDefined());
            }
            return FormValidation.ok();
        }

        public FormValidation doCheckQcProject(@QueryParameter String value) {
            if (StringUtils.isBlank(value)) {
                return FormValidation.error(Messages.QualityCenter_ProjectShouldBeDefined());
            }
            return FormValidation.ok();
        }

        public FormValidation doCheckQcTSFolder(@QueryParameter String value) {
            if (StringUtils.isBlank(value)) {
                return FormValidation.error(Messages.QualityCenter_TSFolderShouldBeDefined());
            }
            return FormValidation.ok();
        }

        public FormValidation doCheckQcTSName(@QueryParameter String value) {
            if (StringUtils.isBlank(value)) {
                return FormValidation.error(Messages.QualityCenter_TSNameShouldBeDefined());
            }
            return FormValidation.ok();
        }

        /**
         * Returns the possible run modes.
         *
         * <p>This method needs to be placed here so that the list can be
         * accessible from QualityCenter's config.jelly file: config.jelly
         * is not able to access such a method if it is placed, even statically,
         * directly into QualityCenter.</p>
         */
        public String[] getRunModes() {
            return QualityCenter.RUN_MODES;
        }
    }

}