com.microfocus.application.automation.tools.octane.executor.UFTTestDetectionService.java Source code

Java tutorial

Introduction

Here is the source code for com.microfocus.application.automation.tools.octane.executor.UFTTestDetectionService.java

Source

/*
 *
 *  Certain versions of software and/or documents (Material?) accessible here may contain branding from
 *  Hewlett-Packard Company (now HP Inc.) and Hewlett Packard Enterprise Company.  As of September 1, 2017,
 *  the Material is now offered by Micro Focus, a separately owned and operated company.  Any reference to the HP
 *  and Hewlett Packard Enterprise/HPE marks is historical in nature, and the HP and Hewlett Packard Enterprise/HPE
 *  marks are the property of their respective owners.
 * __________________________________________________________________
 * MIT License
 *
 *  Copyright 2012-2018 Micro Focus or one of its affiliates.
 *
 * The only warranties for products and services of Micro Focus and its affiliates
 * and licensors (Micro Focus?) are set forth in the express warranty statements
 * accompanying such products and services. Nothing herein should be construed as
 * constituting an additional warranty. Micro Focus shall not be liable for technical
 * or editorial errors or omissions contained herein.
 * The information contained herein is subject to change without notice.
 * ___________________________________________________________________
 *
 */

package com.microfocus.application.automation.tools.octane.executor;

import com.hp.octane.integrations.uft.UftTestDiscoveryUtils;
import com.hp.octane.integrations.uft.items.*;
import com.hp.octane.integrations.utils.SdkConstants;
import com.hp.octane.integrations.utils.SdkStringUtils;
import com.microfocus.application.automation.tools.octane.executor.scmmanager.ScmPluginFactory;
import com.microfocus.application.automation.tools.octane.executor.scmmanager.ScmPluginHandler;
import hudson.ExtensionList;
import hudson.FilePath;
import hudson.model.*;
import hudson.scm.ChangeLogSet;
import hudson.scm.EditType;
import jenkins.model.Jenkins;
import org.apache.commons.lang.reflect.FieldUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.xml.bind.JAXBException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;

/**
 * Service is responsible to detect changes according to SCM change and to put it to queue of UftTestDiscoveryDispatcher
 */
public class UFTTestDetectionService {
    private static final Logger logger = LogManager.getLogger(UFTTestDetectionService.class);
    private static final String INITIAL_DETECTION_FILE = "INITIAL_DETECTION_FILE.txt";
    private static final String DETECTION_RESULT_FILE = "detection_result.xml";

    public static UftTestDiscoveryResult startScanning(AbstractBuild<?, ?> build, String configurationId,
            String workspaceId, String scmRepositoryId, BuildListener buildListener) {
        ChangeLogSet<? extends ChangeLogSet.Entry> changeSet = build.getChangeSet();
        Object[] changeSetItems = changeSet.getItems();
        UftTestDiscoveryResult result = null;

        try {

            boolean fullScan = build.getId().equals("1") || !initialDetectionFileExist(build.getWorkspace())
                    || isFullScan((build));
            if (fullScan) {
                printToConsole(buildListener, "Executing full sync");
                result = UftTestDiscoveryUtils.doFullDiscovery(new File(build.getWorkspace().getRemote()));
            } else {
                printToConsole(buildListener, "Executing changeSet sync");
                result = doChangeSetDetection(changeSetItems, new File(build.getWorkspace().getRemote()));
                removeTestDuplicatedForUpdateTests(result);
                removeFalsePositiveDataTables(result, result.getDeletedTests(),
                        result.getDeletedScmResourceFiles());
                removeFalsePositiveDataTables(result, result.getNewTests(), result.getNewScmResourceFiles());
            }

            Map<OctaneStatus, Integer> testStatusMap = computeStatusMap(result.getAllTests());
            for (Map.Entry<OctaneStatus, Integer> entry : testStatusMap.entrySet()) {
                printToConsole(buildListener,
                        String.format("Found %s tests with status %s", entry.getValue(), entry.getKey()));
            }

            Map<OctaneStatus, Integer> resourceFilesStatusMap = computeStatusMap(result.getAllScmResourceFiles());
            for (Map.Entry<OctaneStatus, Integer> entry : resourceFilesStatusMap.entrySet()) {
                printToConsole(buildListener,
                        String.format("Found %s data tables with status %s", entry.getValue(), entry.getKey()));
            }

            if (!result.getDeletedFolders().isEmpty()) {
                printToConsole(buildListener,
                        String.format("Found %s deleted folders", result.getDeletedFolders().size()));

                //This situation is relevant for SVN only.
                //Deleting folder - SCM event doesn't supply information about deleted items in deleted folder - only top-level directory.
                //In this case need to do for each deleted folder - need to check with Octane what tests and data tables were under this folder.
                //so for each deleted folder - need to do at least 2 requests. In this situation - decided to activate full sync as it already tested scenario.
                //Full sync wil be triggered with delay of 60 secs to give the dispatcher possibility to sync other found changes

                //triggering full sync
                printToConsole(buildListener,
                        "To sync deleted items - full sync required. Triggering job with full sync parameter.");

                FreeStyleProject proj = (FreeStyleProject) build.getParent();
                List<ParameterValue> newParameters = new ArrayList<>();
                for (ParameterValue param : build.getAction(ParametersAction.class).getParameters()) {
                    ParameterValue paramForSet;
                    if (param.getName().equals(UftConstants.FULL_SCAN_PARAMETER_NAME)) {
                        paramForSet = new BooleanParameterValue(UftConstants.FULL_SCAN_PARAMETER_NAME, true);
                    } else {
                        paramForSet = param;
                    }
                    newParameters.add(paramForSet);
                }

                ParametersAction parameters = new ParametersAction(newParameters);
                CauseAction causeAction = new CauseAction(new FullSyncRequiredCause(build.getId()));
                proj.scheduleBuild2(60, parameters, causeAction);
            }

            if (result.isHasQuotedPaths()) {
                printToConsole(buildListener, "This run may not have discovered all updated tests. \n"
                        + "It seems that the changes in this build included filenames with Unicode characters, which Git did not list correctly.\n"
                        + "To make sure Git can properly list such file names, configure Git as follows : git config --global core.quotepath false\n"
                        + "To discover the updated tests that were missed in this run and send them to ALM Octane, run this job manually with the \"Full sync\" parameter selected.\n");
            }

            result.setScmRepositoryId(scmRepositoryId);
            result.setConfigurationId(configurationId);
            result.setWorkspaceId(workspaceId);
            result.setFullScan(fullScan);

            //we add test runner only for discovery jobs that were created for test runners
            ParametersAction parameterAction = build.getAction(ParametersAction.class);
            if (parameterAction != null) {
                ParameterValue testRunnerParameter = parameterAction
                        .getParameter(UftConstants.TEST_RUNNER_ID_PARAMETER_NAME);
                if (testRunnerParameter != null && testRunnerParameter.getValue() instanceof String) {
                    result.setTestRunnerId((String) testRunnerParameter.getValue());
                }
            }

            result.sortItems();
            publishDetectionResults(getReportXmlFile(build), buildListener, result);

            if (result.hasChanges()) {
                UftTestDiscoveryDispatcher dispatcher = getExtension(UftTestDiscoveryDispatcher.class);
                dispatcher.enqueueResult(build.getProject().getName(), build.getNumber());
            }
            createInitialDetectionFile(build.getWorkspace());

        } catch (Exception e) {
            logger.error("Fail in startScanning : " + e.getMessage());
        }

        return result;
    }

    private static Map<OctaneStatus, Integer> computeStatusMap(List<? extends SupportsOctaneStatus> entities) {
        Map<OctaneStatus, Integer> statusMap = new HashMap<>();
        for (SupportsOctaneStatus item : entities) {
            if (!statusMap.containsKey(item.getOctaneStatus())) {
                statusMap.put(item.getOctaneStatus(), 0);
            }
            statusMap.put(item.getOctaneStatus(), statusMap.get(item.getOctaneStatus()) + 1);
        }
        return statusMap;
    }

    /**
     * Deleted data table might be part of deleted test. During discovery its very hard to know.
     * Here we pass through all deleted data tables, if we found data table parent is test folder - we know that the delete was part of test delete
     *
     * @param tests
     * @param scmResourceFiles
     */
    private static void removeFalsePositiveDataTables(UftTestDiscoveryResult result, List<AutomatedTest> tests,
            List<ScmResourceFile> scmResourceFiles) {
        if (!scmResourceFiles.isEmpty() && !tests.isEmpty()) {

            List<ScmResourceFile> falsePositive = new ArrayList<>();
            for (ScmResourceFile item : scmResourceFiles) {
                int parentSplitterIndex = item.getRelativePath()
                        .lastIndexOf(SdkConstants.FileSystem.WINDOWS_PATH_SPLITTER);
                if (parentSplitterIndex != -1) {
                    String parentName = item.getRelativePath().substring(0, parentSplitterIndex);
                    for (AutomatedTest test : tests) {
                        String testPath = SdkStringUtils.isEmpty(test.getPackage()) ? test.getName()
                                : test.getPackage() + SdkConstants.FileSystem.WINDOWS_PATH_SPLITTER
                                        + test.getName();
                        if (parentName.contains(testPath)) {
                            falsePositive.add(item);
                            break;
                        }
                    }
                }
            }

            result.getAllScmResourceFiles().removeAll(falsePositive);
        }
    }

    private static boolean isFullScan(AbstractBuild<?, ?> build) {
        ParametersAction parameters = build.getAction(ParametersAction.class);
        if (parameters != null) {
            ParameterValue parameterValue = parameters.getParameter(UftConstants.FULL_SCAN_PARAMETER_NAME);
            if (parameterValue != null) {
                return (Boolean) parameterValue.getValue();
            }
        }
        return false;
    }

    private static <T> T getExtension(Class<T> clazz) {
        ExtensionList<T> items = Jenkins.getInstance().getExtensionList(clazz);
        return items.get(0);
    }

    private static void removeTestDuplicatedForUpdateTests(UftTestDiscoveryResult result) {
        Set<String> keys = new HashSet<>();
        List<AutomatedTest> testsToRemove = new ArrayList<>();
        for (AutomatedTest test : result.getUpdatedTests()) {
            String key = test.getPackage() + "_" + test.getName();
            if (keys.contains(key)) {
                testsToRemove.add(test);
            }
            keys.add(key);

        }
        result.getAllTests().removeAll(testsToRemove);
    }

    private static void printToConsole(BuildListener buildListener, String msg) {
        if (buildListener != null) {
            buildListener.getLogger().println("UFTTestDetectionService : " + msg);
        }
    }

    private static UftTestDiscoveryResult doChangeSetDetection(Object[] changeSetItems, File workspace) {
        UftTestDiscoveryResult result = new UftTestDiscoveryResult();
        if (changeSetItems.length == 0) {
            return result;
        }

        for (int i = 0; i < changeSetItems.length; i++) {
            ChangeLogSet.Entry changeSet = (ChangeLogSet.Entry) changeSetItems[i];
            for (ChangeLogSet.AffectedFile affectedFileChange : changeSet.getAffectedFiles()) {
                if (affectedFileChange.getPath().startsWith("\"")) {
                    result.setHasQuotedPaths(true);
                }
                boolean isDir = isDir(affectedFileChange);
                String affectedFileFullPath = workspace + File.separator + affectedFileChange.getPath();
                if (!isDir) {
                    if (UftTestDiscoveryUtils.isTestMainFilePath(affectedFileChange.getPath())) {
                        File testFolder = UftTestDiscoveryUtils.getTestFolderForTestMainFile(affectedFileFullPath);
                        File affectedFile = new File(affectedFileFullPath);
                        boolean fileExist = affectedFile.exists();
                        UftTestType uftTestType = UftTestDiscoveryUtils
                                .getUftTestType(affectedFileChange.getPath());

                        AutomatedTest test = UftTestDiscoveryUtils.createAutomatedTest(workspace, testFolder,
                                uftTestType);
                        addChangeSetSrcAndDst(test, affectedFileChange);

                        if (EditType.ADD.equals(affectedFileChange.getEditType())) {
                            if (fileExist) {
                                result.getAllTests().add(test);
                            }
                        } else if (EditType.DELETE.equals(affectedFileChange.getEditType())) {
                            if (!fileExist) {
                                test.setOctaneStatus(OctaneStatus.DELETED);
                                test.setExecutable(false);
                                result.getAllTests().add(test);
                            }
                        } else if (EditType.EDIT.equals(affectedFileChange.getEditType())) {
                            if (fileExist) {
                                test.setOctaneStatus(OctaneStatus.MODIFIED);
                                result.getAllTests().add(test);
                            }
                        }
                    } else if (UftTestDiscoveryUtils.isUftDataTableFile(affectedFileChange.getPath())) {
                        File affectedFile = new File(affectedFileFullPath);
                        ScmResourceFile resourceFile = UftTestDiscoveryUtils.createDataTable(workspace,
                                affectedFile);
                        addChangeSetSrcAndDst(resourceFile, affectedFileChange);
                        if (EditType.ADD.equals(affectedFileChange.getEditType())) {
                            UftTestType testType = UftTestDiscoveryUtils
                                    .isUftTestFolder(affectedFile.getParentFile().listFiles());
                            if (testType.isNone()) {
                                if (affectedFile.exists()) {
                                    result.getAllScmResourceFiles().add(resourceFile);
                                }
                            }
                        } else if (EditType.DELETE.equals(affectedFileChange.getEditType())) {
                            if (!affectedFile.exists()) {
                                resourceFile.setOctaneStatus(OctaneStatus.DELETED);
                                result.getAllScmResourceFiles().add(resourceFile);
                            }
                        }
                    }
                } else //isDir
                {
                    if (EditType.DELETE.equals(affectedFileChange.getEditType())) {

                        FilePath filePath = new FilePath(new File(affectedFileChange.getPath()));
                        String deletedFolder = filePath.getRemote().replace(
                                SdkConstants.FileSystem.LINUX_PATH_SPLITTER,
                                SdkConstants.FileSystem.WINDOWS_PATH_SPLITTER);
                        result.getDeletedFolders().add(deletedFolder);
                    }
                }
            }
        }

        return result;
    }

    private static boolean isDir(ChangeLogSet.AffectedFile path) {
        //ONLY for SVN plugin : Check if path is directory
        if (path.getClass().getName().equals("hudson.scm.SubversionChangeLogSet$Path")) {
            try {
                String value = (String) FieldUtils.readDeclaredField(path, "kind", true);
                return "dir".equals(value);
            } catch (Exception e) {
                //treat it as false
            }
        }
        return false;
    }

    private static void addChangeSetSrcAndDst(SupportsMoveDetection entity,
            ChangeLogSet.AffectedFile affectedFile) {
        if (affectedFile != null) {
            ScmPluginHandler handler = ScmPluginFactory
                    .getScmHandlerByChangePathClass(affectedFile.getClass().getName());
            if (handler != null) {
                entity.setChangeSetSrc(handler.getChangeSetSrc(affectedFile));
                entity.setChangeSetDst(handler.getChangeSetDst(affectedFile));
            }
        }
    }

    private static boolean initialDetectionFileExist(FilePath workspace) {
        try {
            File rootFile = new File(workspace.toURI());
            File file = new File(rootFile, INITIAL_DETECTION_FILE);
            return file.exists();

        } catch (Exception e) {
            return false;
        }
    }

    private static void createInitialDetectionFile(FilePath workspace) {
        try {
            File rootFile = new File(workspace.toURI());
            File file = new File(rootFile, INITIAL_DETECTION_FILE);
            logger.info("Initial detection file path : " + file.getPath());
            file.createNewFile();
        } catch (IOException | InterruptedException e) {
            logger.error("Failed to createInitialDetectionFile : " + e.getMessage());
        }
    }

    /**
     * Serialize detectionResult to file in XML format
     *
     * @param fileToWriteTo
     * @param taskListenerLog
     * @param detectionResult
     */
    public static void publishDetectionResults(File fileToWriteTo, TaskListener taskListenerLog,
            UftTestDiscoveryResult detectionResult) {

        try {
            detectionResult.writeToFile(fileToWriteTo);
        } catch (JAXBException e) {
            String msg = "Failed to persist detection results because of JAXBException : " + e.getMessage();
            if (taskListenerLog != null) {
                taskListenerLog.error(msg);
            }
            logger.error(msg);
        }
    }

    public static UftTestDiscoveryResult readDetectionResults(Run run) {

        File file = getReportXmlFile(run);

        try {
            return UftTestDiscoveryResult.readFromFile(file);
        } catch (JAXBException | FileNotFoundException e) {
            return null;
        }
    }

    public static File getReportXmlFile(Run run) {
        return new File(run.getRootDir(), DETECTION_RESULT_FILE);
    }
}