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

com.capitalone.dashboard.evaluator.RegressionTestResultEvaluator Maven / Gradle / Ivy

There is a newer version: 3.7.33
Show newest version
package com.capitalone.dashboard.evaluator;

import com.capitalone.dashboard.ApiSettings;
import com.capitalone.dashboard.model.AuditException;
import com.capitalone.dashboard.model.CollectorItem;
import com.capitalone.dashboard.model.CollectorType;
import com.capitalone.dashboard.model.Dashboard;
import com.capitalone.dashboard.model.DashboardType;
import com.capitalone.dashboard.model.TestResult;
import com.capitalone.dashboard.model.TestSuiteType;
import com.capitalone.dashboard.model.TestCapability;
import com.capitalone.dashboard.model.TestSuite;
import com.capitalone.dashboard.model.Traceability;
import com.capitalone.dashboard.model.Widget;
import com.capitalone.dashboard.model.Feature;
import com.capitalone.dashboard.model.StoryIndicator;

import com.capitalone.dashboard.repository.FeatureRepository;
import com.capitalone.dashboard.repository.TestResultRepository;
import com.capitalone.dashboard.request.ArtifactAuditRequest;
import com.capitalone.dashboard.response.TestResultsAuditResponse;
import com.capitalone.dashboard.status.TestResultAuditStatus;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Date;
import java.util.Comparator;
import java.util.Optional;
import java.util.Map;

import java.util.regex.Pattern;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.stream.Collectors;

@Component
public class RegressionTestResultEvaluator extends Evaluator {

    private final TestResultRepository testResultRepository;
    private final FeatureRepository featureRepository;
    private static final Logger LOGGER = LoggerFactory.getLogger(RegressionTestResultEvaluator.class);
    private long beginDate;
    private long endDate;
    private Dashboard dashboard;
    private static final String WIDGET_FEATURE = "feature";
    private static final String STR_TEAM_ID = "teamId";
    private static final String STR_UNDERSCORE = "_";
    private static final String STR_HYPHEN = "-";
    private static final String STR_AT = "@";
    private static final String STR_EMPTY = "";
    private static final String SUCCESS_COUNT = "successCount";
    private static final String FAILURE_COUNT = "failureCount";
    private static final String SKIP_COUNT = "skippedCount";
    private static final String TOTAL_COUNT = "totalCount";
    private static final String TEST_CASE_SUCCESS_COUNT = "successTestCaseCount";
    private static final String TEST_CASE_FAILURE_COUNT = "failureTestCaseCount";
    private static final String TEST_CASE_SKIPPED_COUNT = "skippedTestCaseCount";
    private static final String TEST_CASE_TOTAL_COUNT = "totalTestCaseCount";
    private static final String PRIORITY_HIGH = "High";
    public static final String FUNCTIONAL = "Functional";

    @Autowired
    public RegressionTestResultEvaluator(TestResultRepository testResultRepository, FeatureRepository featureRepository) {
        this.testResultRepository = testResultRepository;
        this.featureRepository = featureRepository;
    }

    @Override
    public Collection evaluate(Dashboard dashboard, long beginDate, long endDate, Map dummy,  String altIdentifier, String identifierName) throws AuditException {
        this.beginDate = beginDate-1;
        this.endDate = endDate+1;
        this.dashboard = getDashboard(dashboard.getTitle(), DashboardType.Team);
        List testItems = getCollectorItems(this.dashboard, CollectorType.Test, FUNCTIONAL);
        Collection testResultsAuditResponse = new ArrayList<>();
        if (CollectionUtils.isEmpty(testItems)) {
            throw new AuditException("No tests configured", AuditException.NO_COLLECTOR_ITEM_CONFIGURED);
        }
        testItems.forEach(testItem -> testResultsAuditResponse.add(getRegressionTestResultAudit(dashboard, testItem)));
        return testResultsAuditResponse;
    }

    @Override
    public Collection evaluateNextGen(ArtifactAuditRequest artifactAuditRequest, Dashboard dashboard, long beginDate, long endDate, Map data) throws AuditException {
        return null;
    }


    @Override
    public TestResultsAuditResponse evaluate(CollectorItem collectorItem, long beginDate, long endDate, Map data) {
        return new TestResultsAuditResponse();
    }

    /**
     * Gets the json response from test_results collection with story information based on tags.
     * @param testItem
     * @return
     */
    protected TestResultsAuditResponse getRegressionTestResultAudit(Dashboard dashboard, CollectorItem testItem) {
        List testResults = testResultRepository.findByCollectorItemIdAndTimestampIsBetweenOrderByTimestampDesc(testItem.getId(), beginDate, endDate);
        return performTestResultAudit(dashboard, testItem, testResults);
    }

    /**
     * Perform test result audit
     *
     * @param testItem
     * @param testResults
     * @return testResultsAuditResponse
     */
    private TestResultsAuditResponse performTestResultAudit(Dashboard dashboard, CollectorItem testItem, List testResults) {

        TestResultsAuditResponse testResultsAuditResponse = new TestResultsAuditResponse();
        testResultsAuditResponse.setAuditEntity(testItem.getOptions());
        testResultsAuditResponse.setLastUpdated(testItem.getLastUpdated());
        if (CollectionUtils.isEmpty(testResults) || !isValidTestResultTestSuitType(testResults)){
            testResultsAuditResponse.addAuditStatus(TestResultAuditStatus.TEST_RESULT_MISSING);
            return testResultsAuditResponse;
        }
        TestResult testResult = testResults.stream().sorted(Comparator.comparing(TestResult::getTimestamp).reversed()).findFirst().get();
        testResultsAuditResponse.setBuildArtifact(testResult.getBuildArtifact());
        testResultsAuditResponse.setLastExecutionTime(testResult.getStartTime());
        testResultsAuditResponse.setType(testResult.getType().toString());
        testResultsAuditResponse.setFeatureTestResult(getFeatureTestResult(testResult));
        testResultsAuditResponse = updateTraceabilityDetails(dashboard, testResult, testResultsAuditResponse);

        List testCapabilities = testResult.getTestCapabilities().stream().collect(Collectors.toList());
        testResultsAuditResponse = updateTestResultAuditStatuses(testCapabilities, testResultsAuditResponse);

        // Clearing for readability in response
        for(TestCapability test: testCapabilities){
            test.setTestSuites(null);
        }
        testResultsAuditResponse.setTestCapabilities(testCapabilities);
        return testResultsAuditResponse;

    }

    /***
     * Update traceability details with calculated percent value
     * @param testResult,testResultsAuditResponse
     * @return testResultsAuditResponse
     */
    private TestResultsAuditResponse updateTraceabilityDetails(Dashboard dashboard, TestResult testResult, TestResultsAuditResponse testResultsAuditResponse) {

        Traceability traceability = new Traceability();
        List totalStoriesList = new ArrayList<>();
        List totalCompletedStories = new ArrayList<>();
        List totalStories = new ArrayList<>();
        double traceabilityThreshold = settings.getTraceabilityThreshold();

        Widget featureWidget = getFeatureWidget(dashboard);
        Optional teamIdOpt = Optional.ofNullable(featureWidget.getOptions().get(STR_TEAM_ID));
        String teamId = teamIdOpt.isPresent() ? teamIdOpt.get().toString() : "";
        List featureList = featureRepository.getStoryByTeamID(teamId);

        featureList.stream().forEach(feature -> {
            HashMap storyAuditStatusMap = new HashMap<>();
            totalStoriesList.add(feature.getsNumber());

            if(isValidChangeDate(feature)) {
                if(this.isValidStoryStatus(feature.getsStatus())){
                    totalCompletedStories.add(feature.getsNumber());
                    storyAuditStatusMap.put(feature.getsNumber(), TestResultAuditStatus.TEST_RESULTS_TRACEABILITY_STORY_MATCH.name());
                } else{
                    storyAuditStatusMap.put(feature.getsNumber(), TestResultAuditStatus.TEST_RESULTS_TRACEABILITY_STORY_STATUS_INVALID.name());
                }
            } else {
                storyAuditStatusMap.put(feature.getsNumber(), TestResultAuditStatus.TEST_RESULTS_TRACEABILITY_STORY_NOT_FOUND.name());
            }
            totalStories.add(storyAuditStatusMap);
        });
        if (totalCompletedStories.size() > NumberUtils.INTEGER_ZERO) {
            int totalStoryIndicatorCount = getTotalStoryIndicators(testResult).size();
            double percentage = (totalStoryIndicatorCount * 100) / totalCompletedStories.size();
            traceability.setPercentage(percentage);

            if (traceabilityThreshold == NumberUtils.DOUBLE_ZERO) {
                testResultsAuditResponse.addAuditStatus(TestResultAuditStatus.TEST_RESULTS_TRACEABILITY_THRESHOLD_DEFAULT);
            }
            if(percentage == NumberUtils.DOUBLE_ZERO){
                testResultsAuditResponse.addAuditStatus(TestResultAuditStatus.TEST_RESULTS_TRACEABILITY_NOT_FOUND);
            }
        } else {
            testResultsAuditResponse.addAuditStatus(TestResultAuditStatus.TEST_RESULTS_TRACEABILITY_NOT_FOUND_IN_GIVEN_DATE_RANGE);
        }
        traceability.setTotalCompletedStories(totalCompletedStories);
        traceability.setTotalStories(totalStories);
        traceability.setTotalStoryCount(totalStories.size());
        traceability.setThreshold(traceabilityThreshold);
        testResultsAuditResponse.setTraceability(traceability);
        return testResultsAuditResponse;
    }

    /**
     * Get story indicators by matching test case tags with feature stories
     * @param testResult
     * @return
     */
    private  List getTotalStoryIndicators(TestResult testResult) {

        Pattern featureIdPattern = Pattern.compile(settings.getFeatureIDPattern());
        List totalStoryIndicatorList = new ArrayList<>();
        testResult.getTestCapabilities().stream()
                .map(TestCapability::getTestSuites).flatMap(Collection::stream)
                .map(TestSuite::getTestCases).flatMap(Collection::stream)
                .forEach(testCase -> {
                    List storyIndicatorList = new ArrayList<>();
                    testCase.getTags().forEach(tag -> {
                        if (featureIdPattern.matcher(getValidFeatureId(tag)).find()) {
                            List features = featureRepository.getStoryByNumber(tag);
                            features.forEach(feature -> {
                                if (isValidChangeDate(feature) && isValidStoryStatus(feature.getsStatus())) {
                                    StoryIndicator storyIndicator = new StoryIndicator();
                                    storyIndicator.setStoryId(feature.getsId());
                                    storyIndicator.setStoryType(feature.getsTypeName());
                                    storyIndicator.setStoryNumber(feature.getsNumber());
                                    storyIndicator.setStoryName(feature.getsName());
                                    storyIndicator.setEpicNumber(feature.getsEpicNumber());
                                    storyIndicator.setEpicName(feature.getsEpicName());
                                    storyIndicator.setProjectName(feature.getsProjectName());
                                    storyIndicator.setTeamName(feature.getsTeamName());
                                    storyIndicator.setSprintName(feature.getsSprintName());
                                    storyIndicator.setStoryStatus(feature.getsStatus());
                                    storyIndicator.setStoryState(feature.getsState());
                                    storyIndicatorList.add(storyIndicator);
                                }
                            });
                        }
                    });
                    storyIndicatorList.forEach(storyIndicator -> {
                        if (!totalStoryIndicatorList.contains(storyIndicator)) {
                            totalStoryIndicatorList.add(storyIndicator);
                        }
                    });
                    testCase.setStoryIndicators(storyIndicatorList);
                });
        return totalStoryIndicatorList;
    }

    private CharSequence getValidFeatureId(String tag) {
        tag = tag.replaceAll(STR_UNDERSCORE, STR_HYPHEN).replaceAll(STR_AT, STR_EMPTY);
        return tag;
    }

    /**
     * Coverts the Human readable time date to Epoch Time Stamp in Milliseconds
     * @param feature
     * @return
     */
    private long getEpochChangeDate(Feature feature) {
        String datePattern = "yyyy-MM-dd'T'HH:mm:ss.SSS";
        long changeDate = 0;

        try {
            SimpleDateFormat sdf = new SimpleDateFormat(datePattern);
            Date dt = sdf.parse(feature.getChangeDate());
            changeDate = dt.getTime();
        } catch(ParseException e) {
            e.printStackTrace();
            LOGGER.error("Error in RegressionTestResultEvaluator.getEpochChangeDate() - Unable to match date pattern - " + e.getMessage());
        }

        return changeDate;
    }

    /**
     * Check whether the story status is valid
     * @param storyStatus
     * @return
     */
    private boolean isValidStoryStatus(String storyStatus) {
        final List validStatus = settings.getValidStoryStatus();
        return validStatus.contains(storyStatus.toUpperCase());
    }

    /**
     * Check whether the feature date is valid
     * @param feature
     * @return
     */
    private boolean isValidChangeDate(Feature feature){
        return (this.getEpochChangeDate(feature) >= beginDate && this.getEpochChangeDate(feature) <= endDate);
    }

    /**
     * Get dashboard by title and type
     * @param title
     * @param dashboardType
     * @return
     */
    private Dashboard getDashboard(String title, DashboardType dashboardType) {
        return dashboardRepository.findByTitleAndType(title, dashboardType);
    }

    /**
     * Check whether the test result test suit type is valid
     * @param testResults
     * @return
     */
    public boolean isValidTestResultTestSuitType(List testResults) {
        return testResults.stream()
                .anyMatch(testResult -> testResult.getType().equals(TestSuiteType.Functional)
                        || testResult.getType().equals(TestSuiteType.Manual)
                        || testResult.getType().equals(TestSuiteType.Regression));
    }

    /**
     * Get feature widget
     * @return
     */
    public Widget getFeatureWidget(Dashboard dashboard) {
        return dashboard.getWidgets()
                .stream()
                .filter(widget -> widget.getName().equalsIgnoreCase(WIDGET_FEATURE))
                .findFirst().orElse(new Widget());
    }
    /**
     * Builds feature test result data map
     * @param testResult
     * @return featureTestResultMap
     */
    protected HashMap getFeatureTestResult(TestResult testResult) {
        HashMap featureTestResultMap = new HashMap<>();
        featureTestResultMap.put(SUCCESS_COUNT, testResult.getSuccessCount());
        featureTestResultMap.put(FAILURE_COUNT, testResult.getFailureCount());
        featureTestResultMap.put(SKIP_COUNT, testResult.getSkippedCount());
        featureTestResultMap.put(TOTAL_COUNT,testResult.getTotalCount());

        Collection testCapabilities = testResult.getTestCapabilities();
        int totalTestCaseCount = testCapabilities.stream().mapToInt(testCapability ->
                testCapability.getTestSuites().parallelStream().mapToInt(TestSuite::getTotalTestCaseCount).sum()).sum();
        int testCaseSuccessCount = testCapabilities.stream().mapToInt(testCapability ->
                testCapability.getTestSuites().parallelStream().mapToInt(TestSuite::getSuccessTestCaseCount).sum()).sum();
        int testCaseFailureCount = testCapabilities.stream().mapToInt(testCapability ->
                testCapability.getTestSuites().parallelStream().mapToInt(TestSuite::getFailedTestCaseCount).sum()).sum();
        int testCaseSkippedCount = testCapabilities.stream().mapToInt(testCapability ->
                testCapability.getTestSuites().parallelStream().mapToInt(TestSuite::getSkippedTestCaseCount).sum()).sum();
        
        featureTestResultMap.put(TEST_CASE_TOTAL_COUNT, totalTestCaseCount);
        featureTestResultMap.put(TEST_CASE_SUCCESS_COUNT, testCaseSuccessCount);
        featureTestResultMap.put(TEST_CASE_FAILURE_COUNT, testCaseFailureCount);
        featureTestResultMap.put(TEST_CASE_SKIPPED_COUNT, testCaseSkippedCount);

        return featureTestResultMap;
    }

    /**
     * update test result audit statuses
     * @param testCapabilities
     * @param testResultsAuditResponse
     * @return
     */
    private TestResultsAuditResponse updateTestResultAuditStatuses(List testCapabilities, TestResultsAuditResponse testResultsAuditResponse) {

        boolean isSuccessHighPriority = settings.getTestResultSuccessPriority().equalsIgnoreCase(PRIORITY_HIGH);
        boolean isFailureHighPriority = settings.getTestResultFailurePriority().equalsIgnoreCase(PRIORITY_HIGH);

        if(isAllTestCasesSkipped(testCapabilities)){
            testResultsAuditResponse.addAuditStatus(TestResultAuditStatus.TEST_RESULT_SKIPPED);
            return testResultsAuditResponse;
        }
        double testCasePassPercent = this.getTestCasePassPercent(testCapabilities);
        if (isFailureHighPriority){
            if (testCasePassPercent < settings.getTestResultThreshold()) {
                testResultsAuditResponse.addAuditStatus(TestResultAuditStatus.TEST_RESULT_AUDIT_FAIL);
            } else {
                testResultsAuditResponse.addAuditStatus(TestResultAuditStatus.TEST_RESULT_AUDIT_OK);
            }
        }else if (isSuccessHighPriority){
            if (testCasePassPercent > NumberUtils.INTEGER_ZERO) {
                testResultsAuditResponse.addAuditStatus(TestResultAuditStatus.TEST_RESULT_AUDIT_OK);
            } else {
                testResultsAuditResponse.addAuditStatus(TestResultAuditStatus.TEST_RESULT_AUDIT_FAIL);
            }
        }else {
            testResultsAuditResponse.addAuditStatus(TestResultAuditStatus.TEST_RESULT_MISSING);
        }
        return testResultsAuditResponse;
    }

    /**
     * Get test result pass percent
     * @param testCapabilities
     * @return
     */
    private double getTestCasePassPercent(List testCapabilities) {
        double testCaseSuccessCount = testCapabilities.stream().mapToDouble(testCapability ->
             testCapability.getTestSuites().parallelStream().mapToDouble(TestSuite::getSuccessTestCaseCount).sum()
        ).sum();
        double totalTestCaseCount = testCapabilities.stream().mapToDouble(testCapability ->
                testCapability.getTestSuites().parallelStream().mapToDouble(TestSuite::getTotalTestCaseCount).sum()
        ).sum();

        return (testCaseSuccessCount/totalTestCaseCount) * 100;
    }

    public void setSettings(ApiSettings settings) {
        this.settings = settings;
    }

    /**
     * Check if all the test cases are skipped
     * @param testCapabilities
     * @return
     */
    public boolean isAllTestCasesSkipped(List testCapabilities) {
        int totalTestCaseCount = testCapabilities.stream().mapToInt(testCapability ->
                testCapability.getTestSuites().parallelStream().mapToInt(TestSuite::getTotalTestCaseCount).sum()
        ).sum();
        int testCaseSkippedCount = testCapabilities.stream().mapToInt(testCapability ->
                testCapability.getTestSuites().parallelStream().mapToInt(TestSuite::getSkippedTestCaseCount).sum()
        ).sum();

        boolean isSkippedHighPriority = settings.getTestResultSkippedPriority().equalsIgnoreCase(PRIORITY_HIGH);

        if ((testCaseSkippedCount >= totalTestCaseCount) && isSkippedHighPriority){
            return true;
        }
        return false;
    }
}