com.google.jenkins.flakyTestHandler.plugin.HistoryAggregatedFlakyTestResultAction.java Source code

Java tutorial

Introduction

Here is the source code for com.google.jenkins.flakyTestHandler.plugin.HistoryAggregatedFlakyTestResultAction.java

Source

/* Copyright 2014 Google Inc. All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.google.jenkins.flakyTestHandler.plugin;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Maps;
import com.google.jenkins.flakyTestHandler.plugin.FlakyTestResultAction.FlakyRunStats;
import com.google.jenkins.flakyTestHandler.plugin.deflake.DeflakeCause;

import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;

import javax.servlet.ServletException;

import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.Revision;
import hudson.plugins.git.util.BuildData;
import hudson.scm.SCM;

/**
 * Action for aggregate and display information for flaky history for all the tests
 *
 * @author Qingzhou Luo
 */
public class HistoryAggregatedFlakyTestResultAction implements Action {

    /**
     * The project which is running
     */
    AbstractProject<?, ?> project;

    /**
     * Map between test name and flaky stats for a single test
     */
    Map<String, SingleTestFlakyStats> aggregatedFlakyStats;

    /**
     * Map between test name and the map between each scm revision and its running stats (# passes + #
     * fails)
     */
    Map<String, Map<String, SingleTestFlakyStats>> aggregatedTestFlakyStatsWithRevision;

    /**
     * The set of all tests being run in last non-deflake build
     */
    Set<String> allTests;

    /**
     * Whether to only show flaky tests or all tests
     */
    boolean onlyShowFlakyTests;

    public HistoryAggregatedFlakyTestResultAction(AbstractProject<?, ?> project) {
        this.project = project;
        this.aggregatedTestFlakyStatsWithRevision = new TreeMap<String, Map<String, SingleTestFlakyStats>>();
        this.aggregatedFlakyStats = new TreeMap<String, SingleTestFlakyStats>();
        this.allTests = new HashSet<String>();
        this.onlyShowFlakyTests = true;
    }

    /**
     * Aggregate all the previous builds to get flaky stats information for all the tests
     */
    void aggregate() {

        // set of all the previous builds
        Stack<AbstractBuild> builds = new Stack<AbstractBuild>();
        for (AbstractBuild<?, ?> build : project._getRuns().values()) {
            builds.push(build);
        }

        while (!builds.empty()) {
            aggregateOneBuild(builds.pop());
        }
    }

    /**
     * Aggregate flaky runs one previous build and put results into a map between test name and
     * its map between scm revisions and aggregated flaky stats for that revision
     *
     * @param build the build to be aggregated
     */
    public void aggregateOneBuild(AbstractBuild<?, ?> build) {
        FlakyTestResultAction action = build.getAction(FlakyTestResultAction.class);
        if (action == null) {
            return;
        }

        FlakyRunStats runStats = action.getFlakyRunStats();

        if (runStats == null) {
            return;
        }

        Map<String, SingleTestFlakyStatsWithRevision> testFlakyStatsMap = runStats
                .getTestFlakyStatsWithRevisionMap();

        if (testFlakyStatsMap == null) {
            // Skip old build which doesn't have the map
            return;
        }

        if (build.getCause(DeflakeCause.class) == null) {
            // This is a non-deflake build, update allTests
            allTests = testFlakyStatsMap.keySet();
        }

        for (Map.Entry<String, SingleTestFlakyStatsWithRevision> testFlakyStat : testFlakyStatsMap.entrySet()) {
            String testName = testFlakyStat.getKey();
            String revision = testFlakyStat.getValue().getRevision();
            SingleTestFlakyStats stats = testFlakyStat.getValue().getStats();

            if (aggregatedTestFlakyStatsWithRevision.containsKey(testName)) {
                Map<String, SingleTestFlakyStats> testFlakyStatMap = aggregatedTestFlakyStatsWithRevision
                        .get(testName);

                if (testFlakyStatMap.containsKey(revision)) {
                    // Merge flaky stats with the same test and the same revision
                    testFlakyStatMap.get(revision).merge(stats);
                } else {
                    // First specific revision flaky stat for a given test
                    testFlakyStatMap.put(revision, new SingleTestFlakyStats(stats));
                }
            } else {
                // The first test entry
                Map<String, SingleTestFlakyStats> testFlakyStatMap = new LinkedHashMap<String, SingleTestFlakyStats>();
                testFlakyStatMap.put(revision, new SingleTestFlakyStats(stats));
                aggregatedTestFlakyStatsWithRevision.put(testName, testFlakyStatMap);

            }
        }

        aggregatedFlakyStats = Maps.filterKeys(
                Maps.transformValues(aggregatedTestFlakyStatsWithRevision, REVISION_STATS_MAP_TO_AGGREGATED_STATS),
                Predicates.in(allTests));
    }

    public Map<String, Map<String, SingleTestFlakyStats>> getAggregatedTestFlakyStatsWithRevision() {
        return aggregatedTestFlakyStatsWithRevision;
    }

    public boolean getOnlyShowFlakyTests() {
        return onlyShowFlakyTests;
    }

    /**
     * Function to aggregate flaky stats over revisions
     */
    public static final Function<Map<String, SingleTestFlakyStats>, SingleTestFlakyStats> REVISION_STATS_MAP_TO_AGGREGATED_STATS = new Function<Map<String, SingleTestFlakyStats>, SingleTestFlakyStats>() {
        @Override
        public SingleTestFlakyStats apply(Map<String, SingleTestFlakyStats> revisionStatsMap) {
            SingleTestFlakyStats aggregatedStatsOverRevision = new SingleTestFlakyStats(0, 0, 0);

            for (SingleTestFlakyStats singleTestFlakyStats : revisionStatsMap.values()) {
                if (singleTestFlakyStats.isPassed()) {
                    aggregatedStatsOverRevision.increasePass();
                } else if (singleTestFlakyStats.isFlaked()) {
                    aggregatedStatsOverRevision.increaseFlake();
                } else if (singleTestFlakyStats.isFailed()) {
                    aggregatedStatsOverRevision.increaseFail();
                }
            }

            return aggregatedStatsOverRevision;
        }
    };

    public void doShowAll(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        onlyShowFlakyTests = !onlyShowFlakyTests;
        rsp.sendRedirect("..");
    }

    public Map<String, SingleTestFlakyStats> getAggregatedFlakyStats() {
        return aggregatedFlakyStats;
    }

    /**
     * Get filtered tests to display on the project page. Users can decide whether to show all tests
     * or just flaky tests
     *
     * @return the filtered tests
     */
    public Map<String, SingleTestFlakyStats> getFilteredAggregatedFlakyStats() {
        Predicate<Entry<String, SingleTestFlakyStats>> flakyTestsFilter;
        if (onlyShowFlakyTests) {
            flakyTestsFilter = new Predicate<Entry<String, SingleTestFlakyStats>>() {
                @Override
                public boolean apply(Entry<String, SingleTestFlakyStats> singleTestFlakyStatsEntry) {
                    return singleTestFlakyStatsEntry.getValue().getFlake() > 0;
                }
            };
        } else {
            flakyTestsFilter = Predicates.alwaysTrue();
        }
        return Maps.filterEntries(aggregatedFlakyStats, flakyTestsFilter);
    }

    public String getIconFileName() {
        return null;
    }

    public String getDisplayName() {
        return null;
    }

    public String getUrlName() {
        return "historyAggregate";
    }

    /**
     * Class for flaky information for one single test
     */
    public static class SingleTestFlakyStats {

        int flake;

        int pass;

        int fail;

        public int getFlake() {
            return flake;
        }

        public int getPass() {
            return pass;
        }

        public int getFail() {
            return fail;
        }

        public void increasePass() {
            pass++;
        }

        public void increaseFail() {
            fail++;
        }

        public void increaseFlake() {
            flake++;
        }

        public SingleTestFlakyStats(int pass, int fail, int flake) {
            this.pass = pass;
            this.fail = fail;
            this.flake = flake;
        }

        public SingleTestFlakyStats(SingleTestFlakyStats stats) {
            this.pass = stats.pass;
            this.fail = stats.fail;
            this.flake = stats.flake;
        }

        public void merge(SingleTestFlakyStats otherTestStats) {
            this.pass += otherTestStats.pass;
            this.fail += otherTestStats.fail;
            this.flake += otherTestStats.flake;
        }

        public boolean isPassed() {
            return pass > 0 && fail == 0 && flake == 0;
        }

        public boolean isFailed() {
            return fail > 0 && pass == 0 && flake == 0;
        }

        public boolean isFlaked() {
            return (pass > 0 && fail > 0) || flake > 0;
        }

        public boolean isUnknown() {
            return pass == 0 && fail == 0 && flake == 0;
        }
    }

    /**
     * A class which augments {@link SingleTestFlakyStats} with a revision string.
     */
    public static class SingleTestFlakyStatsWithRevision {

        /**
         * Embedded {@link SingleTestFlakyStats} object
         */
        private SingleTestFlakyStats stats;

        /**
         * The revision with this test stats. If using GIT for scm, then it will be the git Shal string;
         * Otherwise it will be the build number.
         */
        private String revision;

        /**
         * Construct a SingleTestFlakyStatsWithRevision object with {@link SingleTestFlakyStats} and
         * build information.
         *
         * @param stats Embedded {@link SingleTestFlakyStats} object
         * @param build The {@link hudson.model.AbstractBuild} object to get SCM information from.
         */
        public SingleTestFlakyStatsWithRevision(SingleTestFlakyStats stats, AbstractBuild build) {
            this.stats = stats;
            revision = Integer.toString(build.getNumber());

            SCM scm = build.getProject().getScm();
            if (scm != null && scm instanceof GitSCM) {
                GitSCM gitSCM = (GitSCM) scm;
                BuildData buildData = gitSCM.getBuildData(build);
                if (buildData != null) {
                    Revision gitRevision = buildData.getLastBuiltRevision();
                    if (gitRevision != null) {
                        revision = gitRevision.getSha1String();
                    }
                }
            }
        }

        public SingleTestFlakyStatsWithRevision(SingleTestFlakyStats stats, String revision) {
            this.stats = stats;
            this.revision = revision;
        }

        public String getRevision() {
            return revision;
        }

        public SingleTestFlakyStats getStats() {
            return stats;
        }
    }
}