org.jenkinsci.plugins.workflow.pipelinegraphanalysis.StatusAndTimingTest.java Source code

Java tutorial

Introduction

Here is the source code for org.jenkinsci.plugins.workflow.pipelinegraphanalysis.StatusAndTimingTest.java

Source

/*
 * The MIT License
 *
 * Copyright (c) 2016, CloudBees, Inc.
 *
 * 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 org.jenkinsci.plugins.workflow.pipelinegraphanalysis;

import hudson.model.Result;
import hudson.model.queue.QueueTaskFuture;
import org.apache.commons.lang.SystemUtils;
import org.jenkinsci.plugins.workflow.actions.TimingAction;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.support.steps.input.InputAction;
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

public class StatusAndTimingTest {
    @Rule
    public JenkinsRule j = new JenkinsRule();

    // Helper
    static FlowNode[] getNodes(FlowExecution exec, int[] ids) throws IOException {
        FlowNode[] output = new FlowNode[ids.length];
        for (int i = 0; i < ids.length; i++) {
            output[i] = exec.getNode(Integer.toString(ids[i]));
        }
        return output;
    }

    // Helper
    static public long doTiming(FlowExecution exec, int firstNodeId, int nodeAfterEndId) throws IOException {
        long startTime = TimingAction.getStartTime(exec.getNode(Integer.toString(firstNodeId)));
        long endTime = TimingAction.getStartTime(exec.getNode(Integer.toString(nodeAfterEndId)));
        return endTime - startTime;
    }

    @Test
    public void testBasicPass() throws Exception {
        WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "Passes");
        job.setDefinition(
                new CpsFlowDefinition("" + "sleep 1 \n" + "stage 'first' \n" + "sleep 1 \n" + "echo 'done' \n"));

        /** Node dump follows, format:
         [ID]{parent,ids}(millisSinceStartOfRun) flowClassName displayName [st=startId if a block node]
         Action format:
         - actionClassName actionDisplayName
            
         [2]{}(N/A)FlowStartNode Start of Pipeline
         [3]{2}(814)StepAtomNode Sleep
         [4]{3}(1779)StepAtomNode first
         -LogActionImpl Console Output
         -LabelAction first
         -StageActionImpl null
         [5]{4}(1787)StepAtomNode Sleep
         [6]{5}(2793)StepAtomNode Print Message
         -LogActionImpl Console Output
         [7]{6}(2796)FlowEndNode End of Pipeline  [st=2]
         */
        QueueTaskFuture<WorkflowRun> build = job.scheduleBuild2(0);
        j.assertBuildStatusSuccess(build);
        WorkflowRun run = build.get();

        // Test status handling with the first few nodes
        FlowNode[] n = getNodes(run.getExecution(), new int[] { 2, 3, 4, 5, 6, 7 });
        GenericStatus status = StatusAndTiming.computeChunkStatus(run, null, n[0], n[1], n[2]);
        TimingInfo timing = StatusAndTiming.computeChunkTiming(run, 0, n[0], n[1], n[2]);
        Assert.assertEquals(GenericStatus.SUCCESS, status);
        Assert.assertEquals(0, timing.getPauseDurationMillis());
        Assert.assertEquals(run.getStartTimeInMillis(), timing.getStartTimeMillis());
        Assert.assertEquals(TimingAction.getStartTime(n[2]) - run.getStartTimeInMillis(),
                timing.getTotalDurationMillis());

        // Everything but start/end
        status = StatusAndTiming.computeChunkStatus(run, n[0], n[1], n[4], n[5]);
        timing = StatusAndTiming.computeChunkTiming(run, 2, n[1], n[4], n[5]);
        Assert.assertEquals(GenericStatus.SUCCESS, status);
        Assert.assertEquals(timing.getPauseDurationMillis(), 2);
        Assert.assertEquals(TimingAction.getStartTime(n[5]) - TimingAction.getStartTime(n[1]),
                timing.getTotalDurationMillis());

        // Whole flow
        status = StatusAndTiming.computeChunkStatus(run, null, n[0], n[5], null);
        timing = StatusAndTiming.computeChunkTiming(run, 0, n[0], n[5], null);
        Assert.assertEquals(GenericStatus.SUCCESS, status);
        Assert.assertEquals(0, timing.getPauseDurationMillis());
        Assert.assertEquals(run.getDuration(), timing.getTotalDurationMillis());

        // Custom unstable status
        run.setResult(Result.UNSTABLE);
        status = StatusAndTiming.computeChunkStatus(run, null, n[0], n[1], n[2]);
        Assert.assertEquals(GenericStatus.UNSTABLE, status);

        // Failure should assume last chunk ran is where failure happened
        run.setResult(Result.FAILURE);
        status = StatusAndTiming.computeChunkStatus(run, null, n[0], n[1], n[2]);
        Assert.assertEquals(GenericStatus.SUCCESS, status);

        // First non-start node to final end node
        status = StatusAndTiming.computeChunkStatus(run, n[0], n[1], n[5], null);
        Assert.assertEquals(GenericStatus.FAILURE, status);

        // Whole flow except for end... since no errors here, failure must be at end!
        status = StatusAndTiming.computeChunkStatus(run, n[0], n[1], n[4], n[5]);
        Assert.assertEquals(GenericStatus.SUCCESS, status);
    }

    /** Tests the assignment of error nodes to flows */
    @Test
    public void testFail() throws Exception {
        WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "Fails");
        job.setDefinition(
                new CpsFlowDefinition("" + "sleep 1 \n" + "stage 'first' \n" + "sleep 1 \n" + "error('fails') \n"));
        /**  Node dump follows, format:
        [ID]{parent,ids} flowClassName displayName [st=startId if a block node]
        Action format:
        - actionClassName actionDisplayName
            ------------------------------------------------------------------------------------------
        [2]{}FlowStartNode Start of Pipeline
        [3]{2}StepAtomNode Sleep
        [4]{3}StepAtomNode first
         -LogActionImpl Console Output
         -LabelAction first
         -StageActionImpl null
        [5]{4}StepAtomNode Sleep
        [6]{5}StepAtomNode Error signal
         -ErrorAction fails
         -ErrorAction fails
        [7]{6}FlowEndNode End of Pipeline  [st=2]
         -ErrorAction fails
        */
        QueueTaskFuture<WorkflowRun> build = job.scheduleBuild2(0);
        WorkflowRun run = build.get();
        FlowExecution exec = run.getExecution();
        j.assertBuildStatus(Result.FAILURE, run);

        // Whole flow
        Assert.assertEquals(GenericStatus.FAILURE,
                StatusAndTiming.computeChunkStatus(run, null, exec.getNode("2"), exec.getNode("7"), null));

        // Start through to failure point
        Assert.assertEquals(GenericStatus.FAILURE, StatusAndTiming.computeChunkStatus(run, null, exec.getNode("2"),
                exec.getNode("6"), exec.getNode("7")));

        // All but first/last node
        Assert.assertEquals(GenericStatus.FAILURE, StatusAndTiming.computeChunkStatus(run, exec.getNode("2"),
                exec.getNode("3"), exec.getNode("6"), exec.getNode("7")));

        // Before failure node
        Assert.assertEquals(GenericStatus.SUCCESS, StatusAndTiming.computeChunkStatus(run, exec.getNode("2"),
                exec.getNode("3"), exec.getNode("5"), exec.getNode("6")));
    }

    @Test
    public void testBasicParallelFail() throws Exception {
        WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "Fails");
        job.setDefinition(new CpsFlowDefinition("" + "stage 'primero'\n" + "def branches = ['failFast': false]\n"
                + "branches['success'] = {sleep 1; echo 'succeed'}\n" + "branches['fail'] = {error('autofail');}\n"
                + "parallel branches"));

        /**
         * Node dump from a run follows, format:
         [ID]{parent,ids}(millisSinceStartOfRun) flowClassName displayName [st=startId if a block node]
         Action format: (key actions only)
         - actionClassName actionDisplayName
         ------------------------------------------------------------------------------------------
         [2]{}(N/A)FlowStartNode Start of Pipeline
         [3]{2}StepAtomNode primero
         -LabelAction primero,
         -StageActionImpl null
         [4]{3}(924)StepStartNode Execute in parallel : Start
         [6]{4}(926)StepStartNode Branch: success
         -ParallelLabelAction Branch: success
         [7]{4}(928)StepStartNode Branch: fail
         -ParallelLabelAction Branch: fail
         [8]{6}(930)StepAtomNode Sleep
         [9]{7}(932)StepAtomNode Error signal
         -ErrorAction autofail
         [10]{9}(938)StepEndNode Execute in parallel : Body : End  [st=7]
         -ErrorAction autofail
         [11]{8}(1827)StepAtomNode Print Message
         [12]{11}(1829)StepEndNode Execute in parallel : Body : End  [st=6]
         [13]{12,10}(1845)StepEndNode Execute in parallel : End  [st=4]
         -ErrorAction Parallel step fail failed
         -ErrorAction Parallel step fail failed
         [14]{13}(1867)FlowEndNode End of Pipeline  [st=2]
         -ErrorAction Parallel step fail failed
         */

        QueueTaskFuture<WorkflowRun> build = job.scheduleBuild2(0);
        WorkflowRun run = build.get();
        j.assertBuildStatus(Result.FAILURE, run);
        FlowExecution exec = run.getExecution();

        // Overall flow
        Assert.assertEquals(GenericStatus.FAILURE,
                StatusAndTiming.computeChunkStatus(run, null, exec.getNode("2"), exec.getNode("14"), null));

        // Failing branch
        Assert.assertEquals(GenericStatus.FAILURE, StatusAndTiming.computeChunkStatus(run, exec.getNode("4"),
                exec.getNode("7"), exec.getNode("10"), exec.getNode("13")));

        // Passing branch
        Assert.assertEquals(GenericStatus.SUCCESS, StatusAndTiming.computeChunkStatus(run, exec.getNode("4"),
                exec.getNode("6"), exec.getNode("12"), exec.getNode("13")));

        // Check that branch statuses match
        List<BlockStartNode> parallelStarts = Arrays.asList((BlockStartNode) (exec.getNode("6")),
                (BlockStartNode) (exec.getNode("7")));
        List<FlowNode> parallelEnds = Arrays.asList(exec.getNode("12"), exec.getNode("10"));
        Map<String, GenericStatus> branchStatuses = StatusAndTiming.computeBranchStatuses(run, exec.getNode("4"),
                parallelStarts, parallelEnds, exec.getNode("13"));

        Assert.assertEquals(2, branchStatuses.size());
        String[] branches = { "fail", "success" };
        List<String> outputBranchList = new ArrayList<String>(branchStatuses.keySet());
        Collections.sort(outputBranchList);
        Assert.assertArrayEquals(branches, outputBranchList.toArray());
        Assert.assertEquals(GenericStatus.FAILURE, branchStatuses.get("fail"));
        Assert.assertEquals(GenericStatus.SUCCESS, branchStatuses.get("success"));

        // Verify that overall status returns as failure
        Assert.assertEquals(GenericStatus.FAILURE, StatusAndTiming.condenseStatus(branchStatuses.values()));

        // Check timing computation for individual branches
        long[] simulatedPauses = { 50L, 5L }; // success, fail
        Map<String, TimingInfo> branchTimings = StatusAndTiming.computeParallelBranchTimings(run, exec.getNode("4"),
                parallelStarts, parallelEnds, exec.getNode("13"), simulatedPauses);
        outputBranchList = new ArrayList<String>(branchTimings.keySet());
        Collections.sort(outputBranchList);
        Assert.assertArrayEquals(branches, outputBranchList.toArray());

        // Passing branch time, 5 ms pause was a present above
        TimingInfo successTiming = branchTimings.get("success");
        Assert.assertEquals(50L, successTiming.getPauseDurationMillis());
        long successRunTime = doTiming(exec, 6, 13);
        Assert.assertEquals(successRunTime, successTiming.getTotalDurationMillis());
        Assert.assertEquals(TimingAction.getStartTime(exec.getNode("6")), successTiming.getStartTimeMillis());

        // Failing branch time, 50 ms pause was a present above
        TimingInfo failTiming = branchTimings.get("fail");
        long failRunTime = doTiming(exec, 7, 13);
        Assert.assertEquals(Math.min(5L, failRunTime), failTiming.getPauseDurationMillis());
        Assert.assertEquals(failRunTime, failTiming.getTotalDurationMillis());
        Assert.assertEquals(TimingAction.getStartTime(exec.getNode("7")), failTiming.getStartTimeMillis());

        // Check timing computation for overall result
        TimingInfo finalTiming = StatusAndTiming.computeOverallParallelTiming(run, branchTimings, exec.getNode("4"),
                exec.getNode("13"));
        long totalBranchTiming = TimingAction.getStartTime(exec.getNode("13"))
                - TimingAction.getStartTime(exec.getNode("4"));
        Assert.assertEquals(50L, finalTiming.getPauseDurationMillis());
        Assert.assertEquals(totalBranchTiming, finalTiming.getTotalDurationMillis());
    }

    @Test
    public void testInProgress() throws Exception {
        WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "Fails");
        job.setDefinition(new CpsFlowDefinition(
                "" + "sleep 1 \n" + "stage 'first' \n" + "sleep 1 \n" + "semaphore('wait') \n"));
        WorkflowRun run = job.scheduleBuild2(0).getStartCondition().get();
        SemaphoreStep.waitForStart("wait/1", run);
        FlowExecution exec = run.getExecution();
        Assert.assertEquals(GenericStatus.IN_PROGRESS,
                StatusAndTiming.computeChunkStatus(run, null, exec.getNode("2"), exec.getNode("6"), null));
        long currTime = System.currentTimeMillis();
        TimingInfo tim = StatusAndTiming.computeChunkTiming(run, 0, exec.getNode("2"), exec.getNode("6"), null);
        Assert.assertEquals((double) (currTime - run.getStartTimeInMillis()),
                (double) (tim.getTotalDurationMillis()), 20.0);
        SemaphoreStep.success("wait/1", null);
    }

    @Test
    public void testInProgressParallel() throws Exception {
        WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "Fails");
        job.setDefinition(new CpsFlowDefinition("" + "stage 'primero'\n" + "def branches = ['failFast': false]\n"
                + "branches['success'] = {echo 'succeed'}\n"
                + "branches['pause'] = { sleep 1; semaphore 'wait'; }\n" + "parallel branches"));
        /**
         * Node dump follows, format:
         [ID]{parentIds,...} flowNodeClassName displayName [st=startId if a block node]
         Action format:
         - actionClassName actionDisplayName
         ------------------------------------------------------------------------------------------
         [2]{}FlowStartNode Start of Pipeline
         [3]{2}StepAtomNode primero
           -LogActionImpl Console Output
           -LabelAction primero
         -StageActionImpl null
         [4]{3}StepStartNode Execute in parallel : Start
           -LogActionImpl Console Output
         [6]{4}StepStartNode Branch: success
           -BodyInvocationAction null
           -ParallelLabelAction Branch: success
         [7]{4}StepStartNode Branch: pause
           -BodyInvocationAction null
           -ParallelLabelAction Branch: pause
         [8]{6}StepAtomNode Print Message
           -LogActionImpl Console Output
         [9]{8}StepEndNode Execute in parallel : Body : End  [st=6]
           -BodyInvocationAction null
         [10]{7}StepAtomNode Sleep
         [11]{10}StepAtomNode Test step
         */
        WorkflowRun run = job.scheduleBuild2(0).getStartCondition().get();
        SemaphoreStep.waitForStart("wait/1", run);
        FlowExecution exec = run.getExecution();

        // Test specific cases for status checking
        Assert.assertEquals(GenericStatus.IN_PROGRESS, // Whole flow, semaphore makes it "in-progress"
                StatusAndTiming.computeChunkStatus(run, null, exec.getNode("2"), exec.getNode("11"), null));
        Assert.assertEquals(GenericStatus.SUCCESS, // Completed branch, waiting on parallel semaphore though
                StatusAndTiming.computeChunkStatus(run, null, exec.getNode("2"), exec.getNode("9"), null));
        Assert.assertEquals(GenericStatus.SUCCESS, // Completed branch, just the branching bit
                StatusAndTiming.computeChunkStatus(run, exec.getNode("4"), exec.getNode("6"), exec.getNode("9"),
                        null));
        Assert.assertEquals(GenericStatus.IN_PROGRESS, // Just the in-progress branch
                StatusAndTiming.computeChunkStatus(run, exec.getNode("4"), exec.getNode("7"), exec.getNode("11"),
                        null));
        Assert.assertEquals(GenericStatus.SUCCESS, // All but the in-progress node in the in-progress branch
                StatusAndTiming.computeChunkStatus(run, exec.getNode("4"), exec.getNode("7"), exec.getNode("10"),
                        exec.getNode("11")));

        List<BlockStartNode> branchStartNodes = new ArrayList<BlockStartNode>();
        branchStartNodes.add((BlockStartNode) (exec.getNode("6")));
        branchStartNodes.add((BlockStartNode) (exec.getNode("7")));
        List<FlowNode> branchEndNodes = Arrays.asList(getNodes(exec, new int[] { 9, 11 }));

        // All branch statuses
        Map<String, GenericStatus> statuses = StatusAndTiming.computeBranchStatuses(run, exec.getNode("4"),
                branchStartNodes, branchEndNodes, null);
        Assert.assertEquals(new String[] { "pause", "success" }, new TreeSet<String>(statuses.keySet()).toArray());
        Assert.assertEquals(GenericStatus.SUCCESS, statuses.get("success"));
        Assert.assertEquals(GenericStatus.IN_PROGRESS, statuses.get("pause"));

        // Timings
        long incompleteBranchTime = System.currentTimeMillis() - TimingAction.getStartTime(exec.getNode("7"));
        Map<String, TimingInfo> timings = StatusAndTiming.computeParallelBranchTimings(run, exec.getNode("4"),
                branchStartNodes, branchEndNodes, null, new long[] { 0, 0 });

        // Completed branch uses time from start to end
        TimingInfo time = timings.get("success");
        Assert.assertEquals(0, time.getPauseDurationMillis());
        Assert.assertEquals(
                (double) (TimingAction.getStartTime(exec.getNode("9"))
                        - TimingAction.getStartTime(exec.getNode("6"))),
                (double) (time.getTotalDurationMillis()), 2.0);

        // In-progress branch uses current time
        time = timings.get("pause");
        Assert.assertEquals(0, time.getPauseDurationMillis());

        TimingInfo info = StatusAndTiming.computeOverallParallelTiming(run, timings, exec.getNode("4"), null);
        Assert.assertEquals((double) (incompleteBranchTime), (double) (info.getTotalDurationMillis()), 2.0);

        SemaphoreStep.success("wait/1", null);
    }

    @Test
    public void inputTest() throws Exception {
        WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "InputJob");
        job.setDefinition(new CpsFlowDefinition("" + // FlowStartNode: ID 2
                "stage 'first' \n" + // FlowNode 3
                "echo 'print something' \n" + // FlowNode 4
                "input 'prompt' \n")); // FlowNode 5, end node will be #6
        QueueTaskFuture<WorkflowRun> buildTask = job.scheduleBuild2(0);
        WorkflowRun run = buildTask.getStartCondition().get();
        CpsFlowExecution e = (CpsFlowExecution) run.getExecutionPromise().get();
        while (run.getAction(InputAction.class) == null) {
            e.waitForSuspension();
        }
        e = (CpsFlowExecution) (run.getExecution());

        // Check that a pipeline paused on input gets the same status, and timing reflects in-progress node running through to current time
        GenericStatus status = StatusAndTiming.computeChunkStatus(run, null, e.getNode("2"), e.getNode("5"), null);
        Assert.assertEquals(GenericStatus.PAUSED_PENDING_INPUT, status);
        long currentTime = System.currentTimeMillis();
        TimingInfo timing = StatusAndTiming.computeChunkTiming(run, 0L, e.getNode("2"), e.getNode("5"), null);
        long runTime = currentTime - run.getStartTimeInMillis();
        Assert.assertEquals((double) (runTime), (double) (timing.getTotalDurationMillis()), 10.0); // Approx b/c depends on when currentTime gathered

        // Test the aborted builds are handled right
        run.doTerm();
        j.waitForCompletion(run);
        FlowExecution exec = run.getExecution();
        status = StatusAndTiming.computeChunkStatus(run, null, exec.getNode("2"), exec.getNode("6"), null);
        Assert.assertEquals(GenericStatus.ABORTED, status);
    }

    @Test
    public void busyStepTest() throws Exception {
        WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "InputJob");
        String sleep = "sh 'sleep 10000'\n";
        if (SystemUtils.IS_OS_WINDOWS) {
            sleep = "bat 'timeout /t 30'\n";
        }
        job.setDefinition(new CpsFlowDefinition("node {\n" + "    stage(\"parallelStage\"){\n"
                + "      parallel left : {\n" + "            echo \"running\"\n"
                + "            input message: 'Please input branch to test against' \n" + "        }, \n"
                + "        right : {\n" + sleep + //13
                "        }\n" + "    }\n" + "}"));
        QueueTaskFuture<WorkflowRun> buildTask = job.scheduleBuild2(0);
        WorkflowRun run = buildTask.getStartCondition().get();
        CpsFlowExecution e = (CpsFlowExecution) run.getExecutionPromise().get();
        while (run.getAction(InputAction.class) == null) {
            e.waitForSuspension();
        }
        e = (CpsFlowExecution) (run.getExecution());
        GenericStatus status = StatusAndTiming.computeChunkStatus(run, null, e.getNode("13"), e.getNode("13"),
                null);
        Assert.assertEquals(GenericStatus.IN_PROGRESS, status);

        status = StatusAndTiming.computeChunkStatus(run, null, e.getNode("12"), e.getNode("12"), null);
        Assert.assertEquals(GenericStatus.PAUSED_PENDING_INPUT, status);
    }

}