├── .github ├── CODEOWNERS ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── cd.yaml │ └── jenkins-security-scan.yml ├── .gitignore ├── .mvn ├── extensions.xml └── maven.config ├── CHANGELOG.md ├── Jenkinsfile ├── README.md ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── jenkinsci │ │ └── plugins │ │ └── workflow │ │ └── support │ │ ├── DefaultStepContext.java │ │ ├── PipelineIOUtils.java │ │ ├── actions │ │ ├── EnvironmentAction.java │ │ ├── LogActionImpl.java │ │ ├── LogStorageAction.java │ │ ├── PauseAction.java │ │ ├── WorkspaceActionImpl.java │ │ └── WorkspaceRunAction.java │ │ ├── concurrent │ │ ├── ChainingListenableFuture.java │ │ ├── DirectExecutor.java │ │ ├── Futures.java │ │ ├── ListFuture.java │ │ ├── MoreExecutors.java │ │ ├── Timeout.java │ │ ├── WithThreadName.java │ │ └── package-info.java │ │ ├── pickles │ │ ├── SecretPickle.java │ │ ├── SingleTypedPickleFactory.java │ │ ├── ThrowablePickle.java │ │ ├── TryRepeatedly.java │ │ ├── XStreamPickle.java │ │ ├── package-info.java │ │ └── serialization │ │ │ ├── DryCapsule.java │ │ │ ├── DryOwner.java │ │ │ ├── PickleResolver.java │ │ │ ├── RiverReader.java │ │ │ └── RiverWriter.java │ │ ├── steps │ │ ├── build │ │ │ └── RunWrapper.java │ │ └── input │ │ │ └── POSTHyperlinkNote.java │ │ ├── storage │ │ ├── BulkFlowNodeStorage.java │ │ ├── FlowNodeStorage.java │ │ └── SimpleXStreamFlowNodeStorage.java │ │ └── visualization │ │ └── table │ │ ├── ArgumentsColumn.java │ │ ├── ConsoleColumn.java │ │ ├── FlowGraphTable.java │ │ └── StatusColumn.java └── resources │ ├── META-INF │ └── hudson.remoting.ClassFilter │ ├── index.jelly │ └── org │ └── jenkinsci │ └── plugins │ └── workflow │ └── support │ ├── actions │ ├── LogActionImpl │ │ ├── index.jelly │ │ └── index.properties │ ├── LogStorageAction │ │ ├── index.jelly │ │ └── index.properties │ ├── Messages.properties │ ├── Messages_zh_CN.properties │ ├── WorkspaceActionImpl │ │ └── sidepanel.jelly │ └── WorkspaceRunAction │ │ ├── index.jelly │ │ ├── index.properties │ │ └── index_zh_CN.properties │ ├── steps │ ├── build │ │ └── RunWrapper │ │ │ └── help.html │ └── input │ │ └── POSTHyperlinkNote │ │ └── script.js │ └── visualization │ └── table │ ├── ArgumentsColumn │ ├── column.jelly │ └── columnHeader.jelly │ ├── ConsoleColumn │ └── column.jelly │ ├── FlowGraphTable │ └── ajax.jelly │ └── StatusColumn │ └── column.jelly └── test ├── java └── org │ └── jenkinsci │ └── plugins │ └── workflow │ ├── support │ ├── actions │ │ ├── LogActionImplTest.java │ │ └── PauseActionTest.java │ ├── concurrent │ │ └── TimeoutTest.java │ ├── pickles │ │ ├── ThrowablePickleTest.java │ │ └── serialization │ │ │ ├── EphemeralPickleResolverTest.java │ │ │ ├── PickleResolverTest.java │ │ │ ├── RiverWriterTest.java │ │ │ └── SerializationSecurityTest.java │ ├── steps │ │ └── build │ │ │ └── RunWrapperTest.java │ ├── storage │ │ ├── AbstractStorageTest.java │ │ ├── BulkFlowNodeStorageTest.java │ │ ├── BulkStorageTest.java │ │ ├── MockFlowExecution.java │ │ ├── SimpleXStreamStorageTest.java │ │ └── StorageTestUtils.java │ └── visualization │ │ └── table │ │ └── FlowGraphTableTest.java │ └── test │ └── steps │ ├── BlockSemaphoreStep.java │ ├── SemaphoreStep.java │ ├── SemaphoreStepTest.java │ ├── SynchronousResumeNotSupportedExceptionTest.java │ ├── input │ └── POSTHyperlinkNoteTest.java │ └── package-info.java └── resources └── org └── jenkinsci └── plugins └── workflow └── support ├── storage ├── BulkFlowNodeStorageTest │ └── actionDeserializationShouldBeRobust │ │ ├── jobs │ │ └── test0 │ │ │ ├── builds │ │ │ ├── 1 │ │ │ │ ├── build.xml │ │ │ │ ├── log │ │ │ │ ├── log-index │ │ │ │ └── workflow │ │ │ │ │ └── flowNodeStore.xml │ │ │ ├── legacyIds │ │ │ └── permalinks │ │ │ ├── config.xml │ │ │ └── nextBuildNumber │ │ └── org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml └── SimpleXStreamStorageTest │ └── actionDeserializationShouldBeRobust │ ├── jobs │ └── test0 │ │ ├── builds │ │ ├── 1 │ │ │ ├── build.xml │ │ │ ├── log │ │ │ ├── log-index │ │ │ ├── program.dat │ │ │ └── workflow │ │ │ │ ├── 2.xml │ │ │ │ ├── 3.xml │ │ │ │ ├── 4.xml │ │ │ │ └── 5.xml │ │ ├── legacyIds │ │ └── permalinks │ │ ├── config.xml │ │ └── nextBuildNumber │ └── org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml └── visualization └── table └── FlowGraphTableTest └── corruptedFlowGraph.zip /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/workflow-support-plugin-developers 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "maven" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins 2 | 3 | name: cd 4 | on: 5 | workflow_dispatch: 6 | check_run: 7 | types: 8 | - completed 9 | 10 | jobs: 11 | maven-cd: 12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 13 | secrets: 14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | work 3 | *.iml 4 | .idea 5 | /.classpath 6 | /.project 7 | /.settings/ 8 | # macOS 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.8 6 | 7 | 8 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s 4 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | buildPlugin(useContainerAgent: true, configurations: [ 2 | [platform: 'linux', jdk: 21], 3 | [platform: 'windows', jdk: 17], 4 | ]) 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pipeline: Supporting APIs Plugin 2 | 3 | ## Introduction 4 | 5 | This plugin provides APIs that are used by core Pipeline plugins for features such as persistence and step visualization. 6 | 7 | ## Version History 8 | 9 | See [the changelog](CHANGELOG.md). 10 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/PipelineIOUtils.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.support; 2 | 3 | import hudson.XmlFile; 4 | import hudson.util.XStream2; 5 | 6 | import edu.umd.cs.findbugs.annotations.NonNull; 7 | import java.io.BufferedOutputStream; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.OutputStream; 11 | import java.nio.file.Files; 12 | import java.nio.file.InvalidPathException; 13 | import java.nio.file.StandardOpenOption; 14 | 15 | /** 16 | * Utilities to assist with IO and in some cases improve performance specifically for pipeline. 17 | */ 18 | public class PipelineIOUtils { 19 | /** 20 | * Convenience method to transparently write data directly or atomicly using {@link hudson.util.AtomicFileWriter}. 21 | * @param toWrite Object to write to file 22 | * @param location File to write object to 23 | * @param xstream xstream to use for output 24 | * @param atomicWrite If true, do an atomic write, otherwise do a direct write to file. 25 | * @throws IOException 26 | */ 27 | public static void writeByXStream(@NonNull Object toWrite, @NonNull File location, @NonNull XStream2 xstream, boolean atomicWrite) throws IOException { 28 | if (atomicWrite) { 29 | XmlFile file = new XmlFile(xstream, location); 30 | file.write(toWrite); 31 | } else { 32 | try(OutputStream os = new BufferedOutputStream( 33 | Files.newOutputStream(location.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))){ 34 | xstream.toXMLUTF8(toWrite, os); // No atomic nonsense, just write and write and write! 35 | } catch (InvalidPathException ipe) { 36 | throw new IOException(ipe); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/actions/EnvironmentAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Jesse Glick. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.actions; 26 | 27 | import hudson.EnvVars; 28 | import hudson.model.Action; 29 | import hudson.model.Run; 30 | import hudson.model.TaskListener; 31 | import java.io.IOException; 32 | import java.util.Map; 33 | import org.jenkinsci.plugins.workflow.steps.EnvironmentExpander; 34 | import org.jenkinsci.plugins.workflow.support.DefaultStepContext; 35 | 36 | /** 37 | * A {@linkplain Run#addAction run action} which reports environment variables. 38 | * If present, will be used from {@link DefaultStepContext#get} on {@link EnvVars} 39 | * after amendment by {@link EnvironmentExpander#getEffectiveEnvironment}. 40 | */ 41 | public interface EnvironmentAction extends Action { 42 | 43 | /** 44 | * Gets the complete global environment for a build, including both {@link Run#getEnvironment(TaskListener)} and any {@link IncludingOverrides#getOverriddenEnvironment}. 45 | */ 46 | EnvVars getEnvironment() throws IOException, InterruptedException; 47 | 48 | /** 49 | * Optional extension interface that allows the overrides to be distinguished. 50 | */ 51 | interface IncludingOverrides extends EnvironmentAction { 52 | 53 | /** 54 | * Gets any environment variables set during this build that were not originally present. 55 | */ 56 | Map getOverriddenEnvironment(); 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/actions/LogStorageAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2016, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.actions; 26 | 27 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 28 | import hudson.console.AnnotatedLargeText; 29 | import hudson.model.TaskListener; 30 | import java.io.IOException; 31 | import edu.umd.cs.findbugs.annotations.CheckForNull; 32 | import edu.umd.cs.findbugs.annotations.NonNull; 33 | import org.apache.commons.jelly.XMLOutput; 34 | import org.jenkinsci.plugins.workflow.actions.FlowNodeAction; 35 | import org.jenkinsci.plugins.workflow.actions.LogAction; 36 | import org.jenkinsci.plugins.workflow.actions.PersistentAction; 37 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 38 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 39 | import org.jenkinsci.plugins.workflow.log.LogStorage; 40 | import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator; 41 | import org.kohsuke.accmod.Restricted; 42 | import org.kohsuke.accmod.restrictions.DoNotUse; 43 | import org.kohsuke.accmod.restrictions.NoExternalUse; 44 | 45 | /** 46 | * A marker for a node which had some log text using {@link LogStorage#nodeListener}. 47 | */ 48 | @Restricted(NoExternalUse.class) // for use from DefaultStepContext only 49 | public class LogStorageAction extends LogAction implements FlowNodeAction, PersistentAction { 50 | 51 | @SuppressFBWarnings(value="PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification="TODO clean up") 52 | public transient FlowNode node; 53 | 54 | private LogStorageAction(FlowNode node) { 55 | this.node = node; 56 | } 57 | 58 | @Override public void onLoad(FlowNode node) { 59 | this.node = node; 60 | } 61 | 62 | @Override public AnnotatedLargeText getLogText() { 63 | FlowExecutionOwner owner = node.getExecution().getOwner(); 64 | return LogStorage.of(owner).stepLog(node, !node.isActive()); 65 | } 66 | 67 | /** 68 | * Used from console.jelly to write annotated log to the given output. 69 | */ 70 | @Restricted(DoNotUse.class) // Jelly 71 | public void writeLogTo(long offset, XMLOutput out) throws IOException { 72 | // Similar to Run#writeWholeLogTo but terminates even if node.isActive(). Adapated from WorkflowRun.writeLogTo. 73 | long pos = offset; 74 | while (true) { 75 | long pos2 = getLogText().writeHtmlTo(pos, out.asWriter()); 76 | if (pos2 <= pos) { 77 | break; 78 | } 79 | pos = pos2; 80 | } 81 | } 82 | 83 | /** 84 | * Creates a sink to print output from a step. 85 | * Will use {@link LogActionImpl} if necessary. 86 | * @param node a node which wishes to print output 87 | * @param decorator an optional decorator to pass to {@link TaskListenerDecorator#apply} 88 | * @return a stream 89 | */ 90 | @SuppressWarnings("deprecation") // LogActionImpl here for backward compatibility 91 | public static @NonNull TaskListener listenerFor(@NonNull FlowNode node, @CheckForNull TaskListenerDecorator decorator) throws IOException, InterruptedException { 92 | FlowExecutionOwner owner = node.getExecution().getOwner(); 93 | if (LogActionImpl.isOld(owner) || node.getAction(LogActionImpl.class) != null) { 94 | return LogActionImpl.stream(node, decorator); 95 | } else { 96 | if (node.getAction(LogStorageAction.class) == null) { 97 | node.addAction(new LogStorageAction(node)); 98 | } 99 | return TaskListenerDecorator.apply(LogStorage.of(owner).nodeListener(node), owner, decorator); 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/actions/PauseAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.workflow.support.actions; 25 | 26 | import hudson.model.Action; 27 | import hudson.model.InvisibleAction; 28 | import org.jenkinsci.plugins.workflow.actions.PersistentAction; 29 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 30 | 31 | import java.io.IOException; 32 | import java.util.ArrayList; 33 | import java.util.List; 34 | import java.util.logging.Level; 35 | import java.util.logging.Logger; 36 | import edu.umd.cs.findbugs.annotations.CheckForNull; 37 | import edu.umd.cs.findbugs.annotations.NonNull; 38 | 39 | /** 40 | * Pause {@link FlowNode} Action. 41 | * Simply marks the node as being a node that causes the build to pause e.g. an Input node. 42 | * 43 | * @author tom.fennelly@gmail.com 44 | */ 45 | public class PauseAction extends InvisibleAction implements PersistentAction { 46 | 47 | private static final Logger LOGGER = Logger.getLogger(PauseAction.class.getName()); 48 | 49 | private String cause; 50 | private long startTime = System.currentTimeMillis(); 51 | private long endTime; 52 | 53 | public PauseAction(String cause) { 54 | this.cause = cause; 55 | } 56 | 57 | public String getCause() { 58 | return cause; 59 | } 60 | 61 | public void setCause(String cause) { 62 | this.cause = cause; 63 | } 64 | 65 | public long getStartTime() { 66 | return startTime; 67 | } 68 | 69 | public void setStartTime(long startTime) { 70 | this.startTime = startTime; 71 | } 72 | 73 | public long getEndTime() { 74 | return endTime; 75 | } 76 | 77 | public void setEndTime(long endTime) { 78 | this.endTime = endTime; 79 | } 80 | 81 | public boolean isPaused() { 82 | // The node is paused if the end time is not set on it. 83 | return (endTime == 0L); 84 | } 85 | 86 | /** 87 | * Get the pause duration for this flow node. 88 | * If the node is paused, the duration will be calculated against the current time. 89 | * 90 | * @return The pause duration in milliseconds. 91 | */ 92 | public long getPauseDuration() { 93 | if (isPaused()) { 94 | return (System.currentTimeMillis() - startTime); 95 | } else { 96 | return (endTime - startTime); 97 | } 98 | } 99 | 100 | public static @CheckForNull PauseAction getCurrentPause(@NonNull FlowNode node) { 101 | List pauseActions = getPauseActions(node); 102 | 103 | if (!pauseActions.isEmpty()) { 104 | return pauseActions.get(pauseActions.size() - 1); 105 | } 106 | 107 | return null; 108 | } 109 | 110 | public static void endCurrentPause(@NonNull FlowNode node) throws IOException { 111 | PauseAction currentPause = getCurrentPause(node); 112 | 113 | if (currentPause != null) { 114 | currentPause.setEndTime(System.currentTimeMillis()); 115 | node.save(); 116 | } else { 117 | LOGGER.log(Level.FINE, "‘endCurrentPause’ was called for a FlowNode (‘{0}’) that does not have an active pause. ‘endCurrentPause’ may have already been called.", node.getDisplayName()); 118 | } 119 | } 120 | 121 | /** 122 | * Simple helper method to test if the supplied node is a pause node. 123 | * @param node The node to test. 124 | * @return True if the node is pause node, otherwise false. 125 | */ 126 | public static boolean isPaused(@NonNull FlowNode node) { 127 | PauseAction currentPause = getCurrentPause(node); 128 | 129 | if (currentPause != null) { 130 | return currentPause.isPaused(); 131 | } 132 | 133 | return false; 134 | } 135 | 136 | /** 137 | * Get the {@link PauseAction} instances for the supplied node. 138 | * @param node The node to be searched. 139 | * @return The {@link PauseAction} instances for the supplied node. Returns an empty list if there are none. 140 | */ 141 | public static @NonNull List getPauseActions(@NonNull FlowNode node) { 142 | List pauseActions = new ArrayList<>(); 143 | List actions = node.getActions(); 144 | 145 | for (Action action : actions) { 146 | if (action instanceof PauseAction) { 147 | pauseActions.add((PauseAction) action); 148 | } 149 | } 150 | 151 | return pauseActions; 152 | } 153 | 154 | /** 155 | * get the aggregate pause duration of the supplied flow node. 156 | * @param node The node to calculate on. 157 | * @return The pause duration in milliseconds. 158 | */ 159 | public static long getPauseDuration(@NonNull FlowNode node) { 160 | List pauseActions = getPauseActions(node); 161 | long pauseDuration = 0L; 162 | 163 | for (PauseAction pauseAction : pauseActions) { 164 | pauseDuration += pauseAction.getPauseDuration(); 165 | } 166 | 167 | return pauseDuration; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/actions/WorkspaceActionImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Jesse Glick. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.actions; 26 | 27 | import hudson.FilePath; 28 | import hudson.model.DirectoryBrowserSupport; 29 | import hudson.model.Item; 30 | import hudson.model.Node; 31 | import hudson.model.Queue; 32 | import hudson.model.labels.LabelAtom; 33 | import hudson.security.AccessControlled; 34 | import java.io.FileNotFoundException; 35 | import java.io.IOException; 36 | import java.util.Set; 37 | import java.util.TreeSet; 38 | import jenkins.model.Jenkins; 39 | import org.jenkinsci.plugins.workflow.actions.FlowNodeAction; 40 | import org.jenkinsci.plugins.workflow.actions.WorkspaceAction; 41 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 42 | import org.jenkinsci.plugins.workflow.FilePathUtils; 43 | 44 | public final class WorkspaceActionImpl extends WorkspaceAction implements FlowNodeAction { 45 | 46 | private static final long serialVersionUID = 1; 47 | 48 | private final String node; 49 | private final String path; 50 | private final Set labels; 51 | private transient FlowNode parent; 52 | 53 | public WorkspaceActionImpl(FilePath workspace, FlowNode parent) { 54 | node = FilePathUtils.getNodeName(workspace); 55 | Jenkins j = Jenkins.getInstanceOrNull(); 56 | Node n = j == null ? null : node.isEmpty() ? j : j.getNode(node); 57 | labels = new TreeSet<>(); 58 | if (n != null) { 59 | labels.addAll(n.getAssignedLabels()); 60 | labels.remove(n.getSelfLabel()); 61 | } 62 | path = workspace.getRemote(); 63 | this.parent = parent; 64 | } 65 | 66 | @Override public String getNode() { 67 | return node; 68 | } 69 | 70 | @Override public String getPath() { 71 | return path; 72 | } 73 | 74 | @Override public Set getLabels() { 75 | return labels; 76 | } 77 | 78 | public FlowNode getParent() { 79 | return parent; 80 | } 81 | 82 | @Override public void onLoad(FlowNode parent) { 83 | this.parent = parent; 84 | } 85 | 86 | @Override public String getIconFileName() { 87 | return "folder.png"; 88 | } 89 | 90 | @Override public String getDisplayName() { 91 | return "Workspace"; 92 | } 93 | 94 | @Override public String getUrlName() { 95 | return "ws"; 96 | } 97 | 98 | // Analogous to AbstractProject.doWs. 99 | // TODO this trick fails when a file or dir is named parent/node/path/workspace/iconFileName/displayName/urlName; how to convince Stapler that this method should take precedence? 100 | public DirectoryBrowserSupport doDynamic() throws IOException { 101 | Queue.Executable executable = parent.getExecution().getOwner().getExecutable(); 102 | if (executable instanceof AccessControlled) { 103 | ((AccessControlled) executable).checkPermission(Item.WORKSPACE); 104 | } 105 | FilePath ws = getWorkspace(); 106 | if (ws == null) { 107 | throw new FileNotFoundException(); 108 | } 109 | return new DirectoryBrowserSupport(this, ws, "Workspace", "folder.png", true); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/actions/WorkspaceRunAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2018 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.actions; 26 | 27 | import hudson.Extension; 28 | import hudson.model.Action; 29 | import hudson.model.Item; 30 | import hudson.security.AccessControlled; 31 | import java.io.IOException; 32 | import java.util.ArrayList; 33 | import java.util.Collection; 34 | import java.util.Collections; 35 | import java.util.List; 36 | import java.util.logging.Level; 37 | import java.util.logging.Logger; 38 | import jenkins.model.TransientActionFactory; 39 | import org.jenkinsci.plugins.workflow.flow.FlowExecution; 40 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 41 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 42 | import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner; 43 | import org.kohsuke.accmod.Restricted; 44 | import org.kohsuke.accmod.restrictions.NoExternalUse; 45 | 46 | /** 47 | * Display of all {@link WorkspaceActionImpl}s for a build. 48 | */ 49 | @Restricted(NoExternalUse.class) 50 | public final class WorkspaceRunAction implements Action { 51 | 52 | private static final Logger LOGGER = Logger.getLogger(WorkspaceRunAction.class.getName()); 53 | 54 | private final FlowExecutionOwner.Executable build; 55 | public final FlowExecutionOwner owner; 56 | 57 | WorkspaceRunAction(FlowExecutionOwner.Executable build, FlowExecutionOwner owner) { 58 | this.build = build; 59 | this.owner = owner; 60 | } 61 | 62 | private boolean hasNoWorkspacePermission() { 63 | return (build instanceof AccessControlled && !((AccessControlled) build).hasPermission(Item.WORKSPACE)); 64 | } 65 | 66 | @Override public String getIconFileName() { 67 | return hasNoWorkspacePermission() ? null : "folder.png"; 68 | } 69 | 70 | @Override public String getDisplayName() { 71 | return hasNoWorkspacePermission() ? null : Messages.workspaces(); 72 | } 73 | 74 | @Override public String getUrlName() { 75 | return hasNoWorkspacePermission() ? null : "ws"; 76 | } 77 | 78 | public List getActions() { 79 | FlowExecution exec; 80 | try { 81 | exec = owner.get(); 82 | } catch (IOException x) { 83 | LOGGER.log(Level.WARNING, null, x); 84 | // Broken flow, cannot display anything. 85 | return Collections.emptyList(); 86 | } 87 | List r = new ArrayList<>(); 88 | for (FlowNode node : new DepthFirstScanner().allNodes(exec)) { 89 | r.addAll(node.getActions(WorkspaceActionImpl.class)); 90 | } 91 | Collections.reverse(r); 92 | return r; 93 | } 94 | 95 | @Extension public static final class Factory extends TransientActionFactory { 96 | 97 | @Override public Class type() { 98 | return FlowExecutionOwner.Executable.class; 99 | } 100 | 101 | @Override public Collection createFor(FlowExecutionOwner.Executable target) { 102 | FlowExecutionOwner owner = target.asFlowExecutionOwner(); 103 | if (owner == null) { 104 | return Collections.emptySet(); 105 | } 106 | return Collections.singleton(new WorkspaceRunAction(target, owner)); 107 | } 108 | 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/concurrent/DirectExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package org.jenkinsci.plugins.workflow.support.concurrent; 16 | 17 | import com.google.common.annotations.GwtCompatible; 18 | import org.kohsuke.accmod.Restricted; 19 | import org.kohsuke.accmod.restrictions.NoExternalUse; 20 | import java.util.concurrent.Executor; 21 | 22 | /** 23 | * An {@link Executor} that runs each task in the thread that invokes {@link Executor#execute 24 | * execute}. 25 | */ 26 | @GwtCompatible 27 | @Restricted(NoExternalUse.class) 28 | enum DirectExecutor implements Executor { 29 | INSTANCE; 30 | 31 | @Override 32 | public void execute(Runnable command) { 33 | command.run(); 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "MoreExecutors.directExecutor()"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/concurrent/MoreExecutors.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package org.jenkinsci.plugins.workflow.support.concurrent; 16 | 17 | import com.google.common.annotations.GwtCompatible; 18 | import org.kohsuke.accmod.Restricted; 19 | import org.kohsuke.accmod.restrictions.NoExternalUse; 20 | import java.util.concurrent.Executor; 21 | import java.util.concurrent.ExecutorService; 22 | 23 | /** 24 | * Factory and utility methods for {@link java.util.concurrent.Executor}, {@link ExecutorService}, 25 | * and {@link java.util.concurrent.ThreadFactory}. 26 | * 27 | * @author Eric Fellheimer 28 | * @author Kyle Littlefield 29 | * @author Justin Mahoney 30 | * @since 3.0 31 | */ 32 | @GwtCompatible(emulated = true) 33 | @Restricted(NoExternalUse.class) 34 | public final class MoreExecutors { 35 | private MoreExecutors() {} 36 | 37 | /** 38 | * Returns an {@link Executor} that runs each task in the thread that invokes {@link 39 | * Executor#execute execute}, as in {@code ThreadPoolExecutor.CallerRunsPolicy}. 40 | * 41 | *

This executor is appropriate for tasks that are lightweight and not deeply chained. 42 | * Inappropriate {@code directExecutor} usage can cause problems, and these problems can be 43 | * difficult to reproduce because they depend on timing. For example: 44 | * 45 | *

    46 | *
  • A call like {@code future.transform(function, directExecutor())} may execute the function 47 | * immediately in the thread that is calling {@code transform}. (This specific case happens 48 | * if the future is already completed.) If {@code transform} call was made from a UI thread 49 | * or other latency-sensitive thread, a heavyweight function can harm responsiveness. 50 | *
  • If the task will be executed later, consider which thread will trigger the execution -- 51 | * since that thread will execute the task inline. If the thread is a shared system thread 52 | * like an RPC network thread, a heavyweight task can stall progress of the whole system or 53 | * even deadlock it. 54 | *
  • If many tasks will be triggered by the same event, one heavyweight task may delay other 55 | * tasks -- even tasks that are not themselves {@code directExecutor} tasks. 56 | *
  • If many such tasks are chained together (such as with {@code 57 | * future.transform(...).transform(...).transform(...)....}), they may overflow the stack. 58 | * (In simple cases, callers can avoid this by registering all tasks with the same {@code 59 | * MoreExecutors#newSequentialExecutor} wrapper around {@code directExecutor()}. More 60 | * complex cases may require using thread pools or making deeper changes.) 61 | *
62 | * 63 | * Additionally, beware of executing tasks with {@code directExecutor} while holding a lock. Since 64 | * the task you submit to the executor (or any other arbitrary work the executor does) may do slow 65 | * work or acquire other locks, you risk deadlocks. 66 | * 67 | *

This instance is equivalent to: 68 | * 69 | *

{@code
70 |    * final class DirectExecutor implements Executor {
71 |    *   public void execute(Runnable r) {
72 |    *     r.run();
73 |    *   }
74 |    * }
75 |    * }
76 | * 77 | *

This should be preferred to {@code #newDirectExecutorService()} because implementing the 78 | * {@link ExecutorService} subinterface necessitates significant performance overhead. 79 | * 80 | * 81 | * @since 18.0 82 | */ 83 | public static Executor directExecutor() { 84 | return DirectExecutor.INSTANCE; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/concurrent/Timeout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.concurrent; 26 | 27 | import hudson.FilePath; 28 | import hudson.Util; 29 | import hudson.remoting.VirtualChannel; 30 | import hudson.util.ClassLoaderSanityThreadFactory; 31 | import hudson.util.DaemonThreadFactory; 32 | import hudson.util.NamingThreadFactory; 33 | import java.util.concurrent.Executors; 34 | import java.util.concurrent.ScheduledExecutorService; 35 | import java.util.concurrent.ScheduledFuture; 36 | import java.util.concurrent.TimeUnit; 37 | import java.util.logging.Level; 38 | import java.util.logging.Logger; 39 | 40 | /** 41 | * Allows operations to be limited in execution time. 42 | * For example, {@link VirtualChannel#call} or {@link FilePath#isDirectory} could otherwise hang forever. 43 | * Use in a {@code try}-with-resources block. 44 | */ 45 | public class Timeout implements AutoCloseable { 46 | 47 | private static final Logger LOGGER = Logger.getLogger(Timeout.class.getName()); 48 | 49 | private static final ScheduledExecutorService interruptions = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(new ClassLoaderSanityThreadFactory(new DaemonThreadFactory()), "Timeout.interruptions")); 50 | 51 | private final Thread thread; 52 | private volatile boolean completed; 53 | private volatile ScheduledFuture future; 54 | private long endTime; 55 | /* 56 | private final String originalName; 57 | */ 58 | 59 | private Timeout(long time, TimeUnit unit) { 60 | thread = Thread.currentThread(); 61 | LOGGER.log(Level.FINER, "Might interrupt {0} after {1} {2}", new Object[] {thread.getName(), time, unit}); 62 | /* see below: 63 | originalName = thread.getName(); 64 | thread.setName(String.format("%s (Timeout@%h: %s)", originalName, this, Util.getTimeSpanString(unit.toMillis(time)))); 65 | */ 66 | ping(time, unit); 67 | } 68 | 69 | @Override public void close() { 70 | completed = true; 71 | if (future != null) { 72 | future.cancel(true); 73 | } 74 | /* 75 | thread.setName(originalName); 76 | */ 77 | LOGGER.log(Level.FINER, "completed {0}", thread.getName()); 78 | } 79 | 80 | private void ping(final long time, final TimeUnit unit) { 81 | future = interruptions.schedule(() -> { 82 | if (completed) { 83 | LOGGER.log(Level.FINER, "{0} already finished, no need to interrupt", thread.getName()); 84 | return; 85 | } 86 | if (LOGGER.isLoggable(Level.FINE)) { 87 | Throwable t = new Throwable(); 88 | t.setStackTrace(thread.getStackTrace()); 89 | LOGGER.log(Level.FINE, "Interrupting " + thread.getName() + " after " + time + " " + unit, t); 90 | } 91 | thread.interrupt(); 92 | if (endTime == 0) { 93 | // First interruption. 94 | endTime = System.nanoTime(); 95 | } else { 96 | // Not dead yet? 97 | String unresponsiveness = Util.getTimeSpanString((System.nanoTime() - endTime) / 1_000_000); 98 | LOGGER.log(Level.INFO, "{0} unresponsive for {1}", new Object[] {thread.getName(), unresponsiveness}); 99 | /* TODO does not work; thread.getName() does not seem to return the current value when called from another thread, even w/ synchronized access, and running with -Xint 100 | thread.setName(thread.getName().replaceFirst(String.format("(Timeout@%h: )[^)]+", this), "$1unresponsive for " + unresponsiveness)); 101 | */ 102 | } 103 | ping(5, TimeUnit.SECONDS); 104 | }, time, unit); 105 | } 106 | 107 | public static Timeout limit(final long time, final TimeUnit unit) { 108 | return new Timeout(time, unit); 109 | } 110 | 111 | // TODO JENKINS-32986 offer a variant that will escalate to Thread.stop 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/concurrent/WithThreadName.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.concurrent; 26 | 27 | /** 28 | * Utility to temporarily append some information to the name of the current thread. 29 | * This is helpful for making thread dumps more readable and informative: 30 | * stack trace elements do not contain any information about object identity. 31 | */ 32 | public final class WithThreadName implements AutoCloseable { 33 | 34 | private final String original; 35 | 36 | /** 37 | * Sets the current thread’s name. 38 | * @param suffix text to append to the original name 39 | */ 40 | public WithThreadName(String suffix) { 41 | Thread t = Thread.currentThread(); 42 | original = t.getName(); 43 | t.setName(original + suffix); 44 | } 45 | 46 | /** 47 | * Restores the original name. 48 | */ 49 | @Override public void close() { 50 | Thread.currentThread().setName(original); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/concurrent/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copies of several beta APIs in Guava to protect ourselves from potential API changes in the future. 3 | * 4 | * TODO: write a tool that checks the use of Guava beta APIs. 5 | */ 6 | package org.jenkinsci.plugins.workflow.support.concurrent; -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/pickles/SecretPickle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2015 Jesse Glick. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.pickles; 26 | 27 | import com.google.common.util.concurrent.ListenableFuture; 28 | import hudson.Extension; 29 | import hudson.util.Secret; 30 | import org.jenkinsci.plugins.workflow.pickles.Pickle; 31 | import org.jenkinsci.plugins.workflow.support.concurrent.Futures; 32 | 33 | /** 34 | * {@link Pickle} of a {@link Secret} which stores the encrypted value. 35 | * Needed because {@link Secret} itself only provides an XStream converter, nothing for Java serialization, much less JBoss Marshalling. 36 | */ 37 | public class SecretPickle extends Pickle { 38 | 39 | private final String encryptedValue; 40 | 41 | private SecretPickle(Secret secret) { 42 | encryptedValue = secret.getEncryptedValue(); 43 | } 44 | 45 | @Override public ListenableFuture rehydrate() { 46 | return Futures.immediateFuture(Secret.fromString(encryptedValue)); 47 | } 48 | 49 | @Extension public static final class Factory extends SingleTypedPickleFactory { 50 | 51 | @Override protected Pickle pickle(Secret secret) { 52 | return new SecretPickle(secret); 53 | } 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/pickles/SingleTypedPickleFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.pickles; 26 | 27 | import org.jenkinsci.plugins.workflow.pickles.Pickle; 28 | import org.jenkinsci.plugins.workflow.pickles.PickleFactory; 29 | import hudson.Functions; 30 | 31 | import edu.umd.cs.findbugs.annotations.NonNull; 32 | 33 | /** 34 | * {@link PickleFactory} implementation for a common situation where only kind of ephemeral object is being pickled. 35 | * @param the ephemeral object type 36 | */ 37 | public abstract class SingleTypedPickleFactory extends PickleFactory { 38 | private final Class type; 39 | 40 | @SuppressWarnings("unchecked") 41 | protected SingleTypedPickleFactory() { 42 | type = Functions.getTypeParameter(getClass(), SingleTypedPickleFactory.class, 0); 43 | } 44 | 45 | protected abstract @NonNull Pickle pickle(@NonNull T object); 46 | 47 | @Override public final Pickle writeReplace(Object object) { 48 | if (type.isInstance(object)) { 49 | return pickle(type.cast(object)); 50 | } 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/pickles/ThrowablePickle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2018 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.pickles; 26 | 27 | import com.google.common.util.concurrent.ListenableFuture; 28 | import hudson.Extension; 29 | import hudson.Functions; 30 | import hudson.remoting.ProxyException; 31 | import java.io.IOException; 32 | import java.io.NotSerializableException; 33 | import java.io.OutputStream; 34 | import java.util.logging.Level; 35 | import java.util.logging.Logger; 36 | import org.apache.commons.io.output.NullOutputStream; 37 | import org.jboss.marshalling.Marshaller; 38 | import org.jboss.marshalling.Marshalling; 39 | import org.jboss.marshalling.MarshallingConfiguration; 40 | import org.jboss.marshalling.river.RiverMarshallerFactory; 41 | import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist; 42 | import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox; 43 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 44 | import org.jenkinsci.plugins.workflow.pickles.Pickle; 45 | import org.jenkinsci.plugins.workflow.pickles.PickleFactory; 46 | import org.jenkinsci.plugins.workflow.support.concurrent.Futures; 47 | import org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverWriter; 48 | import org.kohsuke.accmod.Restricted; 49 | import org.kohsuke.accmod.restrictions.NoExternalUse; 50 | 51 | /** 52 | * Ensures that exceptions are safely serializable. 53 | * Replaces anything problematic with {@link ProxyException}. 54 | * Mainly defends against {@link NotSerializableException}. 55 | */ 56 | @Restricted(NoExternalUse.class) 57 | public final class ThrowablePickle extends Pickle { 58 | 59 | private static final Logger LOGGER = Logger.getLogger(ThrowablePickle.class.getName()); 60 | private static final long serialVersionUID = 1; 61 | 62 | /** Stack trace of the original exception. */ 63 | private final ProxyException t; 64 | /** Class name of the original exception. */ 65 | private final String clazz; 66 | /** Stack trace of the problem serializing the original exception. */ 67 | private final String error; 68 | 69 | private ThrowablePickle(Throwable t, Exception x) { 70 | LOGGER.log(Level.FINE, "Sanitizing {0} due to {1}", new Object[] {t, x}); 71 | this.t = new ProxyException(t); 72 | clazz = t.getClass().getName(); 73 | error = Functions.printThrowable(x); 74 | } 75 | 76 | @Override public ListenableFuture rehydrate(FlowExecutionOwner owner) { 77 | try { 78 | owner.getListener().getLogger().println(error.trim()); 79 | owner.getListener().getLogger().println("Loading unserializable exception; result will no longer be assignable to class " + clazz); 80 | } catch (IOException x) { 81 | LOGGER.log(Level.WARNING, null, x); 82 | } 83 | return Futures.immediateFuture(t); 84 | } 85 | 86 | @Extension public static final class Factory extends PickleFactory { 87 | 88 | /** @see RiverWriter */ 89 | @Override public Pickle writeReplace(Object o) { 90 | if (o instanceof Throwable) { 91 | Throwable t = (Throwable) o; 92 | try (OutputStream ignore = new NullOutputStream(); 93 | // Could set an ObjectResolver to ignore _other_ pickles, but we do really expect an Exception to have fields of, say, FilePath. 94 | Marshaller marshaller = new RiverMarshallerFactory().createMarshaller(new MarshallingConfiguration())) { 95 | GroovySandbox.runInSandbox(() -> { 96 | marshaller.start(Marshalling.createByteOutput(ignore)); 97 | marshaller.writeObject(t); 98 | return null; 99 | }, Whitelist.all()); 100 | } catch (Exception x) { 101 | return new ThrowablePickle(t, x); 102 | } 103 | } 104 | return null; 105 | } 106 | 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/pickles/TryRepeatedly.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.pickles; 26 | 27 | import com.google.common.util.concurrent.AbstractFuture; 28 | import com.google.common.util.concurrent.ListenableFuture; 29 | import hudson.Functions; 30 | import hudson.console.ModelHyperlinkNote; 31 | import hudson.model.TaskListener; 32 | import java.io.IOException; 33 | import jenkins.util.Timer; 34 | 35 | import java.util.concurrent.ScheduledFuture; 36 | import java.util.concurrent.TimeUnit; 37 | import java.util.logging.Level; 38 | import java.util.logging.Logger; 39 | import edu.umd.cs.findbugs.annotations.CheckForNull; 40 | import edu.umd.cs.findbugs.annotations.NonNull; 41 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 42 | import org.jenkinsci.plugins.workflow.pickles.Pickle; 43 | 44 | /** 45 | * {@link ListenableFuture} that promises a value that needs to be periodically tried. 46 | * Specialized for use from {@link Pickle#rehydrate(FlowExecutionOwner)}. 47 | */ 48 | public abstract class TryRepeatedly extends AbstractFuture { 49 | 50 | private static final Logger LOGGER = Logger.getLogger(TryRepeatedly.class.getName()); 51 | 52 | private final int delay; 53 | private ScheduledFuture next; 54 | /** Number of {@link #tryLater} calls to run between logging attempts. */ 55 | private float backoff = 1; 56 | /** Amount by which {@link #backoff} gets multiplied, so we do not flood the log with endless messages. */ 57 | private static final float BACKOFF_EXPONENT = 1.42f; // >√2 58 | /** Reset to {@link #backoff} after each message, then decremented by on each call to {@link #tryLater}. */ 59 | private int retriesRemaining; 60 | 61 | protected TryRepeatedly(int delay) { 62 | this(delay, delay); 63 | } 64 | 65 | protected TryRepeatedly(int delay, int initialDelay) { 66 | this.delay = delay; 67 | tryLater(initialDelay); 68 | } 69 | 70 | /** 71 | * Override to supply the owner passed to {@link Pickle#rehydrate(FlowExecutionOwner)}. 72 | */ 73 | protected @NonNull FlowExecutionOwner getOwner() { 74 | return FlowExecutionOwner.dummyOwner(); 75 | } 76 | 77 | /** 78 | * Assuming {@link #getOwner} has been overridden, override to print a message to the build log explaining why the pickle is still unloadable. 79 | * Could use {@link ModelHyperlinkNote} etc. 80 | */ 81 | protected void printWaitingMessage(@NonNull TaskListener listener) { 82 | listener.getLogger().println("Still trying to load " + this); 83 | } 84 | 85 | private void tryLater(int currentDelay) { 86 | if (isCancelled()) { 87 | return; 88 | } 89 | 90 | next = Timer.get().schedule(new Runnable() { 91 | @Override 92 | public void run() { 93 | try { 94 | V v = tryResolve(); 95 | if (v == null) { 96 | if (retriesRemaining == 0) { 97 | try { 98 | TaskListener listener = getOwner().getListener(); 99 | try { 100 | printWaitingMessage(listener); 101 | } catch (Exception x) { 102 | Functions.printStackTrace(x, listener.getLogger()); 103 | } 104 | } catch (IOException x) { 105 | LOGGER.log(Level.WARNING, null, x); 106 | } 107 | backoff *= BACKOFF_EXPONENT; 108 | retriesRemaining = (int) backoff; 109 | } else { 110 | retriesRemaining--; 111 | } 112 | tryLater(delay); 113 | } else { 114 | set(v); 115 | } 116 | } catch (Throwable t) { 117 | setException(t); 118 | } 119 | } 120 | }, currentDelay, TimeUnit.SECONDS); 121 | } 122 | 123 | @Override 124 | public boolean cancel(boolean mayInterruptIfRunning) { 125 | if (next != null) { 126 | next.cancel(mayInterruptIfRunning); 127 | } 128 | LOGGER.log(Level.FINE, "Cancelling {0} in {1}", new Object[] {this, getOwner()}); 129 | return super.cancel(mayInterruptIfRunning); 130 | } 131 | 132 | /** 133 | * This method is called periodically to attempt to resolve the value that this future promises. 134 | * 135 | * @return 136 | * null to retry this at a later moment. 137 | * @throws Exception 138 | * Any exception thrown will cause the future to fail. 139 | */ 140 | protected abstract @CheckForNull V tryResolve() throws Exception; 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/pickles/XStreamPickle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2015 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.pickles; 26 | 27 | import com.google.common.util.concurrent.ListenableFuture; 28 | import hudson.model.Items; 29 | import java.io.Serializable; 30 | import org.jenkinsci.plugins.workflow.pickles.Pickle; 31 | import org.jenkinsci.plugins.workflow.support.concurrent.Futures; 32 | 33 | /** 34 | * A way of pickling Jenkins objects which have a well-defined XStream representation but are not {@link Serializable}. 35 | * Can also be used for objects which are {@link Serializable} but mistakenly so. 36 | * For any such type you wish to save, create a {@link SingleTypedPickleFactory} returning this pickle. 37 | *

Uses {@link Items#XSTREAM2} so suitable for things normally kept in job configuration. 38 | *

Note that the object ought to be self-contained and require no initialization, 39 | * so do not use this for anything with an {@code onLoad} or {@code setOwner} method, etc. 40 | */ 41 | public final class XStreamPickle extends Pickle { 42 | 43 | private final String xml; 44 | 45 | public XStreamPickle(Object o) { 46 | xml = Items.XSTREAM2.toXML(o); 47 | } 48 | 49 | @Override public ListenableFuture rehydrate() { 50 | return Futures.immediateFuture(Items.XSTREAM2.fromXML(xml)); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/pickles/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * Helper classes to assist engines to serialize various Jenkins domain objects. 27 | */ 28 | package org.jenkinsci.plugins.workflow.support.pickles; -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/DryCapsule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.pickles.serialization; 26 | 27 | import org.jenkinsci.plugins.workflow.pickles.Pickle; 28 | 29 | import java.io.Serializable; 30 | 31 | /** 32 | * Written into stream in place of an ephemeral value. 33 | * 34 | * The {@link Pickle} that represents how to restore them is written as a list into a separate stream, 35 | * and a capsule refers to them by index. 36 | * 37 | * {@link PickleResolver} replaces occurrences of this object by the actual value. 38 | * 39 | * @author Kohsuke Kawaguchi 40 | */ 41 | class DryCapsule implements Serializable { 42 | final int id; 43 | 44 | public DryCapsule(int id) { 45 | this.id = id; 46 | } 47 | 48 | private static final long serialVersionUID = 1L; 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/DryOwner.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.support.pickles.serialization; 2 | 3 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * Replaces {@link FlowExecutionOwner} in the persisted object graph. 9 | * 10 | *

11 | * This allows the program state to be restored with the "correct" {@link FlowExecutionOwner} 12 | * that's implicit by the context in which the read-back happens. 13 | * 14 | * @author Kohsuke Kawaguchi 15 | * @see RiverReader#owner 16 | * @see RiverWriter#owner 17 | */ 18 | public class DryOwner implements Serializable { 19 | 20 | private static final long serialVersionUID = 1L; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/PickleResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.pickles.serialization; 26 | 27 | import com.google.common.base.Function; 28 | import com.google.common.util.concurrent.Futures; 29 | import com.google.common.util.concurrent.ListenableFuture; 30 | import com.google.common.util.concurrent.MoreExecutors; 31 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 32 | import java.util.ArrayList; 33 | import java.util.Collection; 34 | import java.util.List; 35 | import java.util.concurrent.TimeUnit; 36 | import jenkins.util.SystemProperties; 37 | import jenkins.util.Timer; 38 | import org.jboss.marshalling.ObjectResolver; 39 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 40 | import org.jenkinsci.plugins.workflow.pickles.Pickle; 41 | import org.kohsuke.accmod.Restricted; 42 | import org.kohsuke.accmod.restrictions.NoExternalUse; 43 | 44 | /** 45 | * {@link ObjectResolver} that resolves {@link DryCapsule} to unpickled objects. 46 | * 47 | * @author Kohsuke Kawaguchi 48 | */ 49 | public class PickleResolver implements ObjectResolver { 50 | 51 | /** 52 | * Pickle resolution will fail automatically after this many seconds. 53 | *

This is intended to prevent Pipeline builds from hanging forever in unusual cases. 54 | */ 55 | @Restricted(NoExternalUse.class) 56 | @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Non-final for modification from script console") 57 | public static long RESOLUTION_TIMEOUT_SECONDS = SystemProperties.getLong(PickleResolver.class + ".RESOLUTION_TIMEOUT_SECONDS", TimeUnit.HOURS.toSeconds(1)); 58 | 59 | /** 60 | * Persisted forms of the stateful objects. 61 | */ 62 | private final List pickles; 63 | 64 | /** 65 | * Unpicked objects from {@link #pickles}, when they are all ready. 66 | */ 67 | private List values; 68 | 69 | private final FlowExecutionOwner owner; 70 | 71 | @Deprecated 72 | public PickleResolver(List pickles) { 73 | this(pickles, FlowExecutionOwner.dummyOwner()); 74 | } 75 | 76 | public PickleResolver(List pickles, FlowExecutionOwner owner) { 77 | this.pickles = pickles; 78 | this.owner = owner; 79 | } 80 | 81 | public Object get(int id) { 82 | return values.get(id); 83 | } 84 | 85 | @Deprecated 86 | public ListenableFuture rehydrate() { 87 | return rehydrate(new ArrayList<>()); 88 | } 89 | 90 | public ListenableFuture rehydrate(Collection> pickleFutures) { 91 | // if there's nothing to rehydrate, we are done 92 | if (pickles.isEmpty()) { 93 | return Futures.immediateFuture(this); 94 | } 95 | 96 | List> members = new ArrayList<>(); 97 | for (Pickle r : pickles) { 98 | // TODO log("rehydrating " + r); 99 | ListenableFuture future; 100 | try { 101 | future = Futures.withTimeout(r.rehydrate(owner), RESOLUTION_TIMEOUT_SECONDS, TimeUnit.SECONDS, Timer.get()); 102 | } catch (RuntimeException x) { 103 | future = Futures.immediateFailedFuture(x); 104 | } 105 | pickleFutures.add(future); 106 | members.add(Futures.transform(future, new Function() { 107 | @Override public Object apply(Object input) { 108 | // TODO log("rehydrated to " + input); 109 | return input; 110 | } 111 | }, MoreExecutors.directExecutor())); 112 | } 113 | 114 | ListenableFuture> all = Futures.allAsList(members); 115 | 116 | return Futures.transform(all,new Function<>() { 117 | @Override 118 | public PickleResolver apply(List input) { 119 | values = input; 120 | return PickleResolver.this; 121 | } 122 | }, MoreExecutors.directExecutor()); 123 | } 124 | 125 | @Override 126 | public Object readResolve(Object o) { 127 | if (o instanceof DryCapsule) { 128 | DryCapsule cap = (DryCapsule) o; 129 | return get(cap.id); 130 | } 131 | return o; 132 | } 133 | 134 | @Override 135 | public Object writeReplace(Object original) { 136 | // only meant to be used for deserialization 137 | throw new IllegalStateException(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/POSTHyperlinkNote.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Jesse Glick. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.steps.input; 26 | 27 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 28 | import hudson.Extension; 29 | import hudson.console.ConsoleAnnotationDescriptor; 30 | import hudson.console.HyperlinkNote; 31 | import java.io.IOException; 32 | import java.net.URLEncoder; 33 | import java.nio.charset.StandardCharsets; 34 | import java.util.Base64; 35 | import java.util.logging.Level; 36 | import java.util.logging.Logger; 37 | import jenkins.model.Jenkins; 38 | import org.kohsuke.stapler.Stapler; 39 | import org.kohsuke.stapler.StaplerRequest2; 40 | 41 | /** 42 | * Hyperlink which sends a POST request to the specified URL. 43 | */ 44 | public final class POSTHyperlinkNote extends HyperlinkNote { 45 | 46 | private static final Logger LOGGER = Logger.getLogger(POSTHyperlinkNote.class.getName()); 47 | 48 | @SuppressFBWarnings("HSM_HIDING_METHOD") 49 | public static String encodeTo(String url, String text) { 50 | try { 51 | return new POSTHyperlinkNote(url, text.length()).encode() + text; 52 | } catch (IOException e) { 53 | // impossible, but don't make this a fatal problem 54 | LOGGER.log(Level.WARNING, "Failed to serialize " + POSTHyperlinkNote.class, e); 55 | return text; 56 | } 57 | } 58 | 59 | private final String url; 60 | 61 | public POSTHyperlinkNote(String url, int length) { 62 | super("#", length); 63 | if (url.startsWith("/")) { 64 | StaplerRequest2 req = Stapler.getCurrentRequest2(); 65 | // When req is not null? 66 | if (req != null) { 67 | url = req.getContextPath() + url; 68 | } else { 69 | Jenkins j = Jenkins.getInstanceOrNull(); 70 | if (j != null) { 71 | String rootUrl = j.getRootUrl(); 72 | if (rootUrl != null) { 73 | url = rootUrl + url.substring(1); 74 | } else { 75 | // hope that / works, i.e., that there is no context path 76 | // TODO: Does not works when there is a content path, p.e. http://localhost:8080/jenkins 77 | // This message log should be an error. 78 | LOGGER.warning("You need to define the root URL of Jenkins"); 79 | } 80 | } 81 | } 82 | } 83 | this.url = url; 84 | } 85 | 86 | @Override protected String extraAttributes() { 87 | // TODO perhaps add hoverNotification 88 | return " data-encoded-url='" + encodeForJavascript(url) + "' class='post-hyperlink-note-button'"; 89 | } 90 | 91 | /** 92 | * Encode the String (using URLEncoding and then base64 encoding) so we can safely pass it to javascript where it can be decoded safely. 93 | * Javascript strings are UTF-16 and the endianness depends on the platform so we use URL encoding to ensure the String is all 7bit clean ascii and base64 encoding to fix passing any "unsafe" characters. 94 | */ 95 | private static String encodeForJavascript(String str) { 96 | // https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem 97 | String encode = URLEncoder.encode(str, StandardCharsets.UTF_8); 98 | return Base64.getUrlEncoder().encodeToString(encode.getBytes(StandardCharsets.UTF_8)); 99 | } 100 | 101 | // TODO why does there need to be a descriptor at all? 102 | @Extension public static final class DescriptorImpl extends ConsoleAnnotationDescriptor { 103 | @Override public String getDisplayName() { 104 | return "POST Hyperlinks"; 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/storage/FlowNodeStorage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.storage; 26 | 27 | import org.jenkinsci.plugins.workflow.flow.FlowExecution; 28 | import org.jenkinsci.plugins.workflow.graph.FlowActionStorage; 29 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 30 | 31 | import java.io.IOException; 32 | import edu.umd.cs.findbugs.annotations.CheckForNull; 33 | import edu.umd.cs.findbugs.annotations.NonNull; 34 | 35 | /** 36 | * Abstraction of various ways to persist {@link FlowNode}, for those {@link FlowExecution}s 37 | * who want to store them within Jenkins. 38 | * 39 | * A flow graph has a characteristic that it is additive. 40 | * 41 | * Flow nodes may be stored in memory or directly persisted to disk at any given moment, but invoking {@link #flush()} 42 | * should always guarantee that everything currently in memory is written. 43 | * @author Kohsuke Kawaguchi 44 | * @author Sam Van Oort 45 | */ 46 | public abstract class FlowNodeStorage implements FlowActionStorage { 47 | // Set up as "avoid" because an unset field will default to false when deserializing and not explicitly set. 48 | private transient boolean avoidAtomicWrite = false; 49 | 50 | /** If true, we use non-atomic write of XML files for this storage. See {@link hudson.util.AtomicFileWriter}. */ 51 | public boolean isAvoidAtomicWrite() { 52 | return avoidAtomicWrite; 53 | } 54 | 55 | /** Set whether we should avoid using atomic write for node files (ensures valid node data if write is interrupted) or not. 56 | */ 57 | public void setAvoidAtomicWrite(boolean avoidAtomicWrite){ 58 | this.avoidAtomicWrite = avoidAtomicWrite; 59 | } 60 | 61 | /** 62 | * @return null 63 | * If no node of the given ID has been persisted before. 64 | */ 65 | public abstract @CheckForNull FlowNode getNode(String id) throws IOException; 66 | 67 | /** Registers node in this storage, potentially persisting to disk. 68 | * {@link #flushNode(FlowNode)} will guarantee it is persisted. 69 | */ 70 | public abstract void storeNode(@NonNull FlowNode n) throws IOException; 71 | 72 | /** 73 | * Register the given node to the storage, potentially flushing to disk, 74 | * and optionally marking the node as deferring writes. 75 | *

This should be invoked with delayWritingAction=true until you have a fully configured node to write out. 76 | * 77 | * Generally {@link #autopersist(FlowNode)} should be automatically invoked before Step execution begins 78 | * unless the step is block-scoped (in which case the FlowNode will handle this). 79 | * 80 | * @param n Node to store 81 | * @param delayWritingActions If true, node will avoid persisting actions except on explicit flush or when you call 82 | * {@link #autopersist(FlowNode)}. 83 | * @throws IOException 84 | */ 85 | public void storeNode(@NonNull FlowNode n, boolean delayWritingActions) throws IOException { 86 | storeNode(n); // Default impl, override if you support delaying writes 87 | } 88 | 89 | /** 90 | * Flushes the node if needed, and if supported, marks it as needing to flush with EVERY write to the {@link FlowNode#actions}. 91 | */ 92 | public void autopersist(@NonNull FlowNode n) throws IOException { 93 | flushNode(n); 94 | } 95 | 96 | /** Persists node fully to disk, ensuring it is written out to storage. */ 97 | public void flushNode(@NonNull FlowNode n) throws IOException { 98 | // Only needs implementation if you're not guaranteeing persistence at all times 99 | } 100 | 101 | /** Invoke this to insure any unwritten {@link FlowNode} data is persisted to disk. 102 | * Should be invoked by {@link FlowExecution#notifyShutdown()} to ensure disk state is persisted. 103 | */ 104 | public void flush() throws IOException { 105 | // Only needs implementation if you're not already guaranteeing persistence at all times 106 | } 107 | 108 | /** Have we written everything to disk that we need to, or is there something waiting to be written by invoking {@link #flush()}? */ 109 | public boolean isPersistedFully() { 110 | return true; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/visualization/table/ArgumentsColumn.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.visualization.table; 26 | 27 | import hudson.Extension; 28 | import org.jenkinsci.plugins.workflow.actions.ArgumentsAction; 29 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 30 | import org.jenkinsci.plugins.workflow.visualization.table.FlowNodeViewColumn; 31 | import org.jenkinsci.plugins.workflow.visualization.table.FlowNodeViewColumnDescriptor; 32 | import org.kohsuke.accmod.Restricted; 33 | import org.kohsuke.accmod.restrictions.NoExternalUse; 34 | import org.kohsuke.stapler.DataBoundConstructor; 35 | 36 | @Restricted(NoExternalUse.class) 37 | public class ArgumentsColumn extends FlowNodeViewColumn { 38 | 39 | @DataBoundConstructor public ArgumentsColumn() {} 40 | 41 | public String get(FlowNode node) { 42 | return ArgumentsAction.getStepArgumentsAsString(node); 43 | } 44 | 45 | @Extension public static class DescriptorImpl extends FlowNodeViewColumnDescriptor { 46 | 47 | @Override public String getDisplayName() { 48 | return "Arguments"; 49 | } 50 | 51 | @SuppressWarnings("deprecation") 52 | @Override public FlowNodeViewColumn getDefaultInstance() { 53 | return new ArgumentsColumn(); 54 | } 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/visualization/table/ConsoleColumn.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.support.visualization.table; 2 | 3 | import hudson.Extension; 4 | import org.jenkinsci.plugins.workflow.visualization.table.FlowNodeViewColumn; 5 | import org.jenkinsci.plugins.workflow.visualization.table.FlowNodeViewColumnDescriptor; 6 | import org.kohsuke.stapler.DataBoundConstructor; 7 | 8 | /** 9 | * @author Kohsuke Kawaguchi 10 | */ 11 | public class ConsoleColumn extends FlowNodeViewColumn { 12 | @DataBoundConstructor 13 | public ConsoleColumn() { 14 | } 15 | 16 | @Override 17 | public String getColumnCaption() { 18 | return ""; // no caption needed because icon is clear enough 19 | } 20 | 21 | @Extension 22 | public static class DescriptorImpl extends FlowNodeViewColumnDescriptor { 23 | @Override 24 | public FlowNodeViewColumn getDefaultInstance() { 25 | return new ConsoleColumn(); 26 | } 27 | 28 | @Override 29 | public String getDisplayName() { 30 | return "Console Output"; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/workflow/support/visualization/table/StatusColumn.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.support.visualization.table; 2 | 3 | import hudson.Extension; 4 | import org.jenkinsci.plugins.workflow.visualization.table.FlowNodeViewColumn; 5 | import org.jenkinsci.plugins.workflow.visualization.table.FlowNodeViewColumnDescriptor; 6 | import org.kohsuke.stapler.DataBoundConstructor; 7 | 8 | /** 9 | * @author Kohsuke Kawaguchi 10 | */ 11 | public class StatusColumn extends FlowNodeViewColumn { 12 | @DataBoundConstructor 13 | public StatusColumn() { 14 | } 15 | 16 | @Extension 17 | public static class DescriptorImpl extends FlowNodeViewColumnDescriptor { 18 | @Override 19 | public FlowNodeViewColumn getDefaultInstance() { 20 | return new StatusColumn(); 21 | } 22 | 23 | @Override 24 | public String getDisplayName() { 25 | return "Status"; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/hudson.remoting.ClassFilter: -------------------------------------------------------------------------------- 1 | org.jboss.marshalling.TraceInformation$FieldInfo 2 | org.jboss.marshalling.TraceInformation$IncompleteObjectInfo 3 | org.jboss.marshalling.TraceInformation$ObjectInfo 4 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 |

27 | Common utility implementations to build Pipeline Plugin 28 |
29 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/actions/LogActionImpl/index.jelly: -------------------------------------------------------------------------------- 1 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ${%Console Output} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ${%skipSome(offset/1024,"?consoleFull")} 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 |           
58 | 59 |
60 | 61 | 62 | 63 | 64 |
65 |             
66 |             ${it.writeLogTo(offset,output)}
67 |           
68 |
69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/actions/LogActionImpl/index.properties: -------------------------------------------------------------------------------- 1 | skipSome=Skipping {0,number,integer} KB.. Full Log 2 | 3 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/actions/LogStorageAction/index.jelly: -------------------------------------------------------------------------------- 1 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ${%Console Output} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ${%skipSome(offset/1024,"?consoleFull")} 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 |           
58 | 59 |
60 | 61 | 62 | 63 | 64 |
65 |             
66 |             ${it.writeLogTo(offset,output)}
67 |           
68 |
69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/actions/LogStorageAction/index.properties: -------------------------------------------------------------------------------- 1 | skipSome=Skipping {0,number,integer} KB.. Full Log 2 | 3 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/actions/Messages.properties: -------------------------------------------------------------------------------- 1 | workspaces=Workspaces 2 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/actions/Messages_zh_CN.properties: -------------------------------------------------------------------------------- 1 | Workspaces=\u5DE5\u4F5C\u7A7A\u95F4 2 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/actions/WorkspaceActionImpl/sidepanel.jelly: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/actions/WorkspaceRunAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | ${h.checkPermission(it.owner.executable.parent, it.owner.executable.parent.WORKSPACE)} 30 | 31 | 32 |

${%Workspacesfor(it.owner.executable.fullDisplayName)}

33 |
    34 | 35 |
  • 36 | 37 | 38 | ${a.path} 39 | 40 | 41 | ${a.path} 42 | 43 | 44 | on 45 |
  • 46 |
    47 |
48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/actions/WorkspaceRunAction/index.properties: -------------------------------------------------------------------------------- 1 | Workspaces=Workspaces 2 | Workspacesfor=Workspaces for {0} 3 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/actions/WorkspaceRunAction/index_zh_CN.properties: -------------------------------------------------------------------------------- 1 | Workspaces=\u5DE5\u4F5C\u7A7A\u95F4 2 | Workspacesfor={0} \u7684\u5DE5\u4F5C\u7A7A\u95F4 3 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/steps/build/RunWrapper/help.html: -------------------------------------------------------------------------------- 1 |
2 |
getBuildCauses
Returns a JSON array of build causes for the current build
3 |
EXPERIMENTAL - MAY CHANGE getBuildCauses(String causeClass)
Takes a string representing the fully qualified Cause class and returns a JSON array of build causes filtered by that type for the current build, or an empty JSON array if no causes of the specified type apply to the current build
4 |
number
build number (integer)
5 |
result
typically SUCCESS, UNSTABLE, or FAILURE (may be null for an ongoing build)
6 |
currentResult
typically SUCCESS, UNSTABLE, or FAILURE. Will never be null.
7 |
resultIsBetterOrEqualTo(String)
Compares the current build result to the provided result string (SUCCESS, UNSTABLE, or FAILURE) and returns true if the current build result is better than or equal to the provided result.
8 |
resultIsWorseOrEqualTo(String)
Compares the current build result to the provided result string (SUCCESS, UNSTABLE, or FAILURE) and returns true if the current build result is worse than or equal to the provided result.
9 |
displayName
normally #123 but sometimes set to, e.g., an SCM commit identifier.
10 |
fullDisplayName
normally folder1 » folder2 » foo #123.
11 |
projectName
Name of the project of this build, such as foo.
12 |
fullProjectName
Full name of the project of this build, including folders such as folder1/folder2/foo.
13 |
description
additional information about the build
14 |
id
normally number as a string
15 |
externalizableId
identifier for this build from combining fullProjectName and number as fullProjectName#number
16 |
timeInMillis
time since the epoch when the build was scheduled
17 |
startTimeInMillis
time since the epoch when the build started running
18 |
duration
duration of the build in milliseconds
19 |
durationString
a human-readable representation of the build duration
20 |
previousBuild
previous build of the project, or null
21 |
previousBuildInProgress
previous build of the project that is currently building, or null
22 |
previousBuiltBuild
previous build of the project that has been built (may be currently building), or null
23 |
previousCompletedBuild
previous build of the project that has last finished building, or null
24 |
previousFailedBuild
previous build of the project that has last failed to build, or null
25 |
previousNotFailedBuild
previous build of the project that did not fail to build (eg. result is successful or unstable), or null
26 |
previousSuccessfulBuild
previous build of the project that has successfully built, or null
27 |
nextBuild
next build of the project, or null
28 |
absoluteUrl
URL of build index page
29 |
buildVariables
for a non-Pipeline downstream build, offers access to a map of defined build variables; for a Pipeline downstream build, any variables set globally on env at the time the build ends. Child Pipeline jobs can use this to report additional information to the parent job by setting variables in env. Note that build parameters are not shown in buildVariables.
30 |
changeSets
a list of changesets coming from distinct SCM checkouts; each has a kind and is a list of commits; each commit has a commitId, timestamp, msg, author, and affectedFiles each of which has an editType and path; the value will not generally be Serializable so you may only access it inside a method marked @NonCPS
31 |
upstreamBuilds
a list of upstream builds. These are the builds of the upstream projects whose artifacts feed into this build. 32 |
rawBuild
a hudson.model.Run with further APIs, only for trusted libraries or administrator-approved scripts outside the sandbox; the value will not be Serializable so you may only access it inside a method marked @NonCPS
33 |
keepLog
true if the log file for this build should be kept and not deleted. 34 |
35 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/steps/input/POSTHyperlinkNote/script.js: -------------------------------------------------------------------------------- 1 | Behaviour.specify("A.post-hyperlink-note-button", "POSTHyperLinkNote", 0, function (element) { 2 | element.addEventListener("click", (event) => { 3 | event.preventDefault(); 4 | const url = decodeURIComponent(atob(element.dataset.encodedUrl)); 5 | fetch(url, { 6 | method: "post", 7 | headers: crumb.wrap({}), 8 | }); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/visualization/table/ArgumentsColumn/column.jelly: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | ${column.get(node)} 29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/visualization/table/ArgumentsColumn/columnHeader.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 27 | 28 | ${column.columnCaption} 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/visualization/table/ConsoleColumn/column.jelly: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 39 |
40 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/visualization/table/FlowGraphTable/ajax.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 29 | 30 | 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 58 | 59 | 60 |
${%Step}
51 | 52 | ${row.displayName} - (${row.durationString} ${timeType}) 53 | 54 |
61 |
62 |
63 |
64 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/workflow/support/visualization/table/StatusColumn/column.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/actions/LogActionImplTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2016, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.actions; 26 | 27 | import hudson.EnvVars; 28 | import hudson.model.TaskListener; 29 | import java.util.Collections; 30 | import java.util.LinkedList; 31 | import java.util.Set; 32 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 33 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 34 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 35 | import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback; 36 | import org.jenkinsci.plugins.workflow.steps.Step; 37 | import org.jenkinsci.plugins.workflow.steps.StepContext; 38 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 39 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 40 | import org.junit.ClassRule; 41 | import org.junit.Test; 42 | import org.junit.Rule; 43 | import org.jvnet.hudson.test.BuildWatcher; 44 | import org.jvnet.hudson.test.JenkinsRule; 45 | import org.jvnet.hudson.test.TestExtension; 46 | import org.kohsuke.stapler.DataBoundConstructor; 47 | 48 | public class LogActionImplTest { 49 | 50 | @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); 51 | @Rule public JenkinsRule r = new JenkinsRule(); 52 | 53 | @Test public void logsAndBlocks() throws Exception { 54 | WorkflowJob p = r.createProject(WorkflowJob.class); 55 | p.setDefinition(new CpsFlowDefinition("parallel a: {chatty('LBBL') {echo(/atom step in A with $remaining commands to go/)}}, b: {chatty('BL') {echo(/atom step in B with $remaining commands to go/)}}", true)); 56 | WorkflowRun b = r.buildAndAssertSuccess(p); 57 | r.assertLogContains("logging from LBBL with 3 commands to go", b); 58 | r.assertLogContains("atom step in A with 2 commands to go", b); 59 | r.assertLogContains("atom step in A with 1 commands to go", b); 60 | r.assertLogContains("logging from LBBL with 0 commands to go", b); 61 | r.assertLogContains("atom step in B with 1 commands to go", b); 62 | r.assertLogContains("logging from BL with 0 commands to go", b); 63 | } 64 | public static class ChattyStep extends Step { 65 | public final String pattern; 66 | @DataBoundConstructor public ChattyStep(String pattern) {this.pattern = pattern;} 67 | @Override public StepExecution start(StepContext context) throws Exception { 68 | return new Execution(context, pattern); 69 | } 70 | @TestExtension("logsAndBlocks") public static class DescriptorImpl extends StepDescriptor { 71 | @Override public String getFunctionName() {return "chatty";} 72 | @Override public boolean takesImplicitBlockArgument() {return true;} 73 | @Override public Set> getRequiredContext() { 74 | return Collections.singleton(TaskListener.class); 75 | } 76 | } 77 | private static class Execution extends StepExecution { 78 | Execution(StepContext context, String pattern) { 79 | super(context); 80 | this.pattern = pattern; 81 | } 82 | final String pattern; 83 | LinkedList commands; // L ~ false to log, B ~ true to run block 84 | @Override public boolean start() throws Exception { 85 | commands = new LinkedList<>(); 86 | for (char c : pattern.toCharArray()) { 87 | if (c == 'L') { 88 | commands.add(false); 89 | } else { 90 | assert c == 'B'; 91 | commands.add(true); 92 | } 93 | } 94 | run(); 95 | return false; 96 | } 97 | private void run() throws Exception { 98 | StepContext context = getContext(); 99 | if (commands.isEmpty()) { 100 | context.onSuccess(null); 101 | } else if (commands.pop()) { 102 | context.newBodyInvoker().withCallback(new Callback()).withContext(new EnvVars("remaining", Integer.toString(commands.size()))).start(); 103 | } else { 104 | context.get(TaskListener.class).getLogger().println("logging from " + pattern + " with " + commands.size() + " commands to go"); 105 | run(); 106 | } 107 | } 108 | private final class Callback extends BodyExecutionCallback { // not using TailCall since run() sometimes calls onSuccess itself 109 | @Override public void onSuccess(StepContext context, Object result) { 110 | try { 111 | run(); 112 | } catch (Exception x) { 113 | context.onFailure(x); 114 | } 115 | } 116 | @Override public void onFailure(StepContext context, Throwable t) { 117 | context.onFailure(t); 118 | } 119 | } 120 | } 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/actions/PauseActionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.workflow.support.actions; 25 | 26 | import org.jenkinsci.plugins.workflow.flow.FlowExecution; 27 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 28 | import org.jenkinsci.plugins.workflow.support.actions.PauseAction; 29 | import org.junit.Assert; 30 | import org.junit.Test; 31 | import org.mockito.Mockito; 32 | 33 | /** 34 | * @author tom.fennelly@gmail.com 35 | */ 36 | public class PauseActionTest { 37 | 38 | @Test 39 | public void test() throws Exception { 40 | FlowExecution flowExecution = Mockito.mock(FlowExecution.class); 41 | FlowNode flowNode = new FlowNode(flowExecution, "1") { 42 | @Override 43 | protected String getTypeDisplayName() { 44 | return "flownode"; 45 | } 46 | }; 47 | 48 | Assert.assertFalse(PauseAction.isPaused(flowNode)); 49 | Assert.assertEquals(0L, PauseAction.getPauseDuration(flowNode)); 50 | 51 | flowNode.addAction(new PauseAction("P1")); 52 | PauseAction firstPause = PauseAction.getCurrentPause(flowNode); 53 | Assert.assertEquals("P1", firstPause.getCause()); 54 | Assert.assertTrue(PauseAction.isPaused(flowNode)); 55 | Thread.sleep(200); 56 | Assert.assertTrue(PauseAction.getPauseDuration(flowNode) > 100L); 57 | 58 | PauseAction.endCurrentPause(flowNode); 59 | Assert.assertFalse(PauseAction.isPaused(flowNode)); 60 | long firstPauseDuration = firstPause.getPauseDuration(); 61 | 62 | Thread.sleep(200); 63 | Assert.assertEquals(firstPauseDuration, PauseAction.getPauseDuration(flowNode)); 64 | 65 | flowNode.addAction(new PauseAction("P2")); 66 | PauseAction secondPause = PauseAction.getCurrentPause(flowNode); 67 | Assert.assertEquals("P2", secondPause.getCause()); 68 | Assert.assertTrue(PauseAction.isPaused(flowNode)); 69 | Thread.sleep(200); 70 | Assert.assertTrue(PauseAction.getPauseDuration(flowNode) > firstPauseDuration); 71 | 72 | PauseAction.endCurrentPause(flowNode); 73 | Assert.assertFalse(PauseAction.isPaused(flowNode)); 74 | long secondPauseDuration = secondPause.getPauseDuration(); 75 | 76 | Thread.sleep(200); 77 | Assert.assertEquals((firstPauseDuration + secondPauseDuration), PauseAction.getPauseDuration(flowNode)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/concurrent/TimeoutTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.concurrent; 26 | 27 | import java.util.Map; 28 | import java.util.TreeMap; 29 | import java.util.concurrent.Future; 30 | import java.util.concurrent.TimeUnit; 31 | import java.util.logging.Level; 32 | import java.util.stream.IntStream; 33 | import jenkins.util.Timer; 34 | import org.junit.Test; 35 | import static org.junit.Assert.*; 36 | import org.junit.Rule; 37 | import org.jvnet.hudson.test.LoggerRule; 38 | 39 | public class TimeoutTest { 40 | 41 | @Rule public LoggerRule logging = new LoggerRule().record(Timeout.class, Level.FINER); 42 | 43 | @Test public void passed() throws Exception { 44 | try (Timeout timeout = Timeout.limit(5, TimeUnit.SECONDS)) { 45 | Thread.sleep(1_000); 46 | } 47 | Thread.sleep(5_000); 48 | } 49 | 50 | @Test public void failed() throws Exception { 51 | try (Timeout timeout = Timeout.limit(5, TimeUnit.SECONDS)) { 52 | Thread.sleep(10_000); 53 | fail("should have timed out"); 54 | } catch (InterruptedException x) { 55 | // good 56 | } 57 | Thread.sleep(1_000); 58 | } 59 | 60 | @Test public void hung() throws Exception { 61 | /* see disabled code in Timeout: 62 | final AtomicBoolean stop = new AtomicBoolean(); 63 | Thread t = Thread.currentThread(); 64 | Timer.get().submit(() -> { 65 | while (!stop.get()) { 66 | System.err.println(t.getName()); 67 | try { 68 | Thread.sleep(1_000); 69 | } catch (InterruptedException x) { 70 | x.printStackTrace(); 71 | } 72 | } 73 | }); 74 | */ 75 | try (Timeout timeout = Timeout.limit(1, TimeUnit.SECONDS)) { 76 | for (int i = 0; i < 5; i++) { 77 | try /* (WithThreadName naming = new WithThreadName(" cycle #" + i)) */ { 78 | Thread.sleep(10_000); 79 | fail("should have timed out"); 80 | } catch (InterruptedException x) { 81 | // OK 82 | } 83 | } 84 | } 85 | Thread.sleep(6_000); 86 | /* 87 | stop.set(true); 88 | */ 89 | } 90 | 91 | @Test public void starvation() throws Exception { 92 | Map> hangers = new TreeMap<>(); 93 | IntStream.range(0, 15).forEachOrdered(i -> hangers.put(i, Timer.get().submit(() -> { 94 | try (Timeout timeout = Timeout.limit(5, TimeUnit.SECONDS)) { 95 | System.err.println("starting #" + i); 96 | Thread.sleep(Long.MAX_VALUE); 97 | fail("should have timed out"); 98 | } catch (InterruptedException x) { 99 | System.err.println("interrupted #" + i); 100 | } 101 | }))); 102 | for (Map.Entry> hanger : hangers.entrySet()) { 103 | System.err.println("joining #" + hanger.getKey()); 104 | hanger.getValue().get(30, TimeUnit.SECONDS); 105 | System.err.println("joined #" + hanger.getKey()); 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/pickles/ThrowablePickleTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2018 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.pickles; 26 | 27 | import hudson.remoting.ProxyException; 28 | import java.util.logging.Level; 29 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted; 30 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 31 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 32 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 33 | import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep; 34 | import org.junit.ClassRule; 35 | import org.junit.Test; 36 | import org.junit.Rule; 37 | import org.jvnet.hudson.test.BuildWatcher; 38 | import org.jvnet.hudson.test.Issue; 39 | import org.jvnet.hudson.test.LoggerRule; 40 | import org.jvnet.hudson.test.JenkinsSessionRule; 41 | 42 | public class ThrowablePickleTest { 43 | 44 | @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); 45 | @Rule public JenkinsSessionRule sessions = new JenkinsSessionRule(); 46 | @Rule public LoggerRule logging = new LoggerRule().record(ThrowablePickle.class, Level.FINE); 47 | 48 | @Issue("JENKINS-51390") 49 | @Test public void smokes() throws Throwable { 50 | String beName = BadException.class.getName(); 51 | sessions.then(r -> { 52 | WorkflowJob p = r.createProject(WorkflowJob.class, "p"); 53 | p.setDefinition(new CpsFlowDefinition("try {throw new " + beName + "()} catch (x) {semaphore 'wait'; echo(/caught a $x/)}", true)); 54 | WorkflowRun b = p.scheduleBuild2(0).waitForStart(); 55 | SemaphoreStep.waitForStart("wait/1", b); 56 | }); 57 | sessions.then(r -> { 58 | WorkflowRun b = r.jenkins.getItemByFullName("p", WorkflowJob.class).getBuildByNumber(1); 59 | SemaphoreStep.success("wait/1", null); 60 | r.assertBuildStatusSuccess(r.waitForCompletion(b)); 61 | r.assertLogContains("in field " + beName + ".notSerializable", b); 62 | r.assertLogContains("assignable to class " + beName, b); 63 | r.assertLogContains("caught a " + ProxyException.class.getName() + ": " + beName, b); 64 | }); 65 | } 66 | 67 | public static class BadException extends Exception { 68 | 69 | private final Object notSerializable = new Object(); 70 | 71 | @Whitelisted 72 | public BadException() {} 73 | 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/EphemeralPickleResolverTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.pickles.serialization; 26 | 27 | import org.jenkinsci.plugins.workflow.pickles.Pickle; 28 | import com.google.common.util.concurrent.ListenableFuture; 29 | import com.google.common.util.concurrent.SettableFuture; 30 | import org.junit.Assert; 31 | import org.junit.Test; 32 | 33 | import java.util.ArrayList; 34 | import java.util.NoSuchElementException; 35 | import java.util.concurrent.ExecutionException; 36 | import java.util.concurrent.Future; 37 | 38 | import static java.util.Arrays.*; 39 | 40 | /** 41 | * @author Kohsuke Kawaguchi 42 | */ 43 | public class EphemeralPickleResolverTest extends Assert { 44 | @Test 45 | public void resolveNothing() throws Exception { 46 | ListenableFuture f = new PickleResolver(new ArrayList<>()).rehydrate(); 47 | assertSuccessfulCompletion(f); 48 | } 49 | 50 | @Test 51 | public void resolveSomething() throws Exception { 52 | TestPickle v1 = new TestPickle(); 53 | TestPickle v2 = new TestPickle(); 54 | ListenableFuture f = new PickleResolver(asList(v1, v2)).rehydrate(); 55 | 56 | assertInProgress(f); 57 | v1.f.set(null); 58 | assertInProgress(f); 59 | v2.f.set(null); 60 | assertSuccessfulCompletion(f); 61 | } 62 | 63 | /** 64 | * If a resolution of a value fails, the whole thing should fail. 65 | */ 66 | @Test 67 | public void resolutionFails() throws Exception { 68 | TestPickle v1 = new TestPickle(); 69 | TestPickle v2 = new TestPickle(); 70 | ListenableFuture f = new PickleResolver(asList(v1, v2)).rehydrate(); 71 | 72 | assertInProgress(f); 73 | v1.f.set(null); 74 | assertInProgress(f); 75 | v2.f.setException(new NoSuchElementException()); 76 | 77 | assertTrue(f.isDone()); 78 | try { 79 | f.get(); 80 | fail("Expected a failure"); 81 | } catch (ExecutionException e) { 82 | assertTrue(e.getCause() instanceof NoSuchElementException); 83 | } 84 | } 85 | 86 | private void assertInProgress(Future f) { 87 | assertFalse(f.isDone()); 88 | assertFalse(f.isCancelled()); 89 | } 90 | 91 | private void assertSuccessfulCompletion(Future f) throws Exception { 92 | assertTrue(f.isDone()); 93 | f.get(); 94 | } 95 | 96 | class TestPickle extends Pickle { 97 | SettableFuture f = SettableFuture.create(); 98 | @Override 99 | public ListenableFuture rehydrate() { 100 | return f; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/PickleResolverTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.support.pickles.serialization; 2 | 3 | import com.google.common.util.concurrent.ListenableFuture; 4 | import hudson.model.Result; 5 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 6 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 7 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 8 | import org.jenkinsci.plugins.workflow.pickles.Pickle; 9 | import org.jenkinsci.plugins.workflow.support.pickles.SingleTypedPickleFactory; 10 | import org.jenkinsci.plugins.workflow.support.pickles.TryRepeatedly; 11 | import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep; 12 | import org.junit.ClassRule; 13 | import org.junit.Rule; 14 | import org.junit.Test; 15 | import org.jvnet.hudson.test.BuildWatcher; 16 | import org.jvnet.hudson.test.FlagRule; 17 | import org.jvnet.hudson.test.JenkinsSessionRule; 18 | import org.jvnet.hudson.test.TestExtension; 19 | 20 | public class PickleResolverTest { 21 | @ClassRule 22 | public static BuildWatcher watcher = new BuildWatcher(); 23 | 24 | @Rule 25 | public JenkinsSessionRule sessions = new JenkinsSessionRule(); 26 | 27 | @Rule 28 | public FlagRule resetPickleResolutionTimeout = new FlagRule<>(() -> PickleResolver.RESOLUTION_TIMEOUT_SECONDS, x -> PickleResolver.RESOLUTION_TIMEOUT_SECONDS = x); 29 | 30 | @Test 31 | public void timeout() throws Throwable { 32 | sessions.then(r -> { 33 | var p = r.jenkins.createProject(WorkflowJob.class, "stuckPickle"); 34 | p.setDefinition(new CpsFlowDefinition( 35 | "def x = new org.jenkinsci.plugins.workflow.support.pickles.serialization.PickleResolverTest.StuckPickle.Marker()\n" + 36 | "semaphore 'wait'\n" + 37 | "echo x.getClass().getName()", false)); 38 | var b = p.scheduleBuild2(0).waitForStart(); 39 | SemaphoreStep.waitForStart("wait/1", b); 40 | }); 41 | PickleResolver.RESOLUTION_TIMEOUT_SECONDS = 3; 42 | sessions.then(r -> { 43 | var p = r.jenkins.getItemByFullName("stuckPickle", WorkflowJob.class); 44 | var b = p.getBuildByNumber(1); 45 | r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b)); 46 | r.assertLogContains("Timed out: StuckPickle", b); 47 | }); 48 | } 49 | 50 | public static class StuckPickle extends Pickle { 51 | @Override 52 | public ListenableFuture rehydrate(FlowExecutionOwner owner) { 53 | return new TryRepeatedly(1) { 54 | @Override 55 | protected Marker tryResolve() { 56 | return null; 57 | } 58 | @Override protected FlowExecutionOwner getOwner() { 59 | return owner; 60 | } 61 | @Override public String toString() { 62 | return "StuckPickle for " + owner; 63 | } 64 | }; 65 | } 66 | 67 | public static class Marker {} 68 | 69 | @TestExtension("timeout") 70 | public static final class Factory extends SingleTypedPickleFactory { 71 | @Override protected Pickle pickle(Marker object) { 72 | return new StuckPickle(); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/pickles/serialization/RiverWriterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.pickles.serialization; 26 | 27 | import hudson.Functions; 28 | import java.io.File; 29 | import java.io.NotSerializableException; 30 | import java.io.Serializable; 31 | import java.util.ArrayList; 32 | import java.util.Collections; 33 | import java.util.logging.Level; 34 | import static org.hamcrest.Matchers.containsString; 35 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 36 | import org.junit.Test; 37 | import static org.hamcrest.MatcherAssert.assertThat; 38 | import static org.junit.Assert.assertEquals; 39 | import static org.junit.Assert.fail; 40 | import org.junit.Rule; 41 | import org.junit.rules.TemporaryFolder; 42 | import org.jvnet.hudson.test.Issue; 43 | import org.jvnet.hudson.test.LoggerRule; 44 | 45 | public class RiverWriterTest { 46 | 47 | @Rule public TemporaryFolder tmp = new TemporaryFolder(); 48 | @Rule public LoggerRule logging = new LoggerRule().recordPackage(RiverWriter.class, Level.FINE); 49 | 50 | @Test public void trivial() throws Exception { 51 | File f = tmp.newFile(); 52 | FlowExecutionOwner owner = FlowExecutionOwner.dummyOwner(); 53 | try (RiverWriter w = new RiverWriter(f, owner, Collections.emptySet())) { 54 | w.writeObject(Collections.singletonList("hello world")); 55 | } 56 | Object o; 57 | try (RiverReader r = new RiverReader(f, RiverWriterTest.class.getClassLoader(), owner)) { 58 | o = r.restorePickles(new ArrayList<>()).get().readObject(); 59 | } 60 | assertEquals(Collections.singletonList("hello world"), o); 61 | } 62 | 63 | @Issue("JENKINS-26137") 64 | @Test public void errors() throws Exception { 65 | File f = tmp.newFile(); 66 | FlowExecutionOwner owner = FlowExecutionOwner.dummyOwner(); 67 | try (RiverWriter w = new RiverWriter(f, owner, Collections.emptySet())) { 68 | w.writeObject(Collections.singletonList(new NotActuallySerializable())); 69 | fail(); 70 | } catch (NotSerializableException x) { 71 | /* 72 | original: 73 | Caused by: an exception which occurred: 74 | in field bad 75 | in object java.util.Collections$SingletonList@7791a8b4 76 | now: 77 | Caused by: an exception which occurred: 78 | in field org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverReaderTest$NotActuallySerializable.bad 79 | in object org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverReaderTest$NotActuallySerializable@6b09bb57 80 | in object java.util.Collections$SingletonList@6b09bb76 81 | */ 82 | assertThat(Functions.printThrowable(x), containsString(NotActuallySerializable.class.getName() + ".bad")); 83 | } 84 | } 85 | private static class NotActuallySerializable implements Serializable { 86 | String good = "OK"; 87 | Object bad = new Object(); 88 | } 89 | 90 | // TODO pickle resolution 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/storage/AbstractStorageTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.support.storage; 2 | 3 | import hudson.XmlFile; 4 | import hudson.model.Action; 5 | import org.jenkinsci.plugins.workflow.actions.BodyInvocationAction; 6 | import org.jenkinsci.plugins.workflow.actions.LabelAction; 7 | import org.jenkinsci.plugins.workflow.graph.AtomNode; 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.ClassRule; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.jvnet.hudson.test.BuildWatcher; 14 | import org.jvnet.hudson.test.JenkinsRule; 15 | 16 | import java.io.File; 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | import java.util.List; 20 | 21 | /** 22 | * Base test for the storage implementations 23 | * @author Sam Van Oort 24 | */ 25 | public abstract class AbstractStorageTest { 26 | @ClassRule 27 | public static BuildWatcher buildWatcher = new BuildWatcher(); 28 | 29 | @Rule 30 | public JenkinsRule j = new JenkinsRule(); 31 | 32 | File storageDir; 33 | XmlFile file; 34 | 35 | @Before 36 | public void setup() { 37 | File dir = new File(j.jenkins.getRootDir(), "storageTest"); 38 | dir.delete(); 39 | storageDir = new File(dir, "storage"); 40 | file = new XmlFile(new File(dir, "execution.xml")); 41 | } 42 | 43 | @After 44 | public void teardown() { 45 | File dir = new File(j.jenkins.getRootDir(), "storageTest"); 46 | dir.delete(); 47 | } 48 | 49 | // Implement me for the implementation we're testing 50 | public abstract FlowNodeStorage instantiateStorage(MockFlowExecution exec, File storageDirectory); 51 | 52 | /** Tests that basic nodes read and write correctly */ 53 | @Test 54 | public void verifyBasicPersist() throws Exception { 55 | MockFlowExecution mock = new MockFlowExecution(); 56 | FlowNodeStorage storage = instantiateStorage(mock, storageDir); 57 | mock.setStorage(storage); 58 | assert storage.isPersistedFully(); 59 | 60 | // Just any old node 61 | AtomNode simple = new StorageTestUtils.SimpleAtomNode(mock, "simple"); 62 | mock.saveActions(simple, Collections.EMPTY_LIST); 63 | 64 | // Node with actions added after storing 65 | AtomNode notQuiteAsSimple = new StorageTestUtils.SimpleAtomNode(mock, "actionAddedAfterStore", simple); 66 | storage.storeNode(notQuiteAsSimple); 67 | notQuiteAsSimple.addAction(new LabelAction("displayLabel")); 68 | 69 | // Node saved with explicit actions set 70 | StorageTestUtils.SimpleAtomNode directlySaveActions = new StorageTestUtils.SimpleAtomNode(mock, "explictSaveActions", notQuiteAsSimple); 71 | List acts = new ArrayList<>(); 72 | acts.add(new LabelAction("yetAnotherLabel")); 73 | acts.add(new BodyInvocationAction()); 74 | storage.saveActions(directlySaveActions, acts); 75 | directlySaveActions.setActions(acts); // Doesn't trigger writethrough to persistence, needed for consistency. 76 | 77 | // Deferred save 78 | AtomNode deferredSave = new StorageTestUtils.SimpleAtomNode(mock, "deferredSave", notQuiteAsSimple); 79 | storage.storeNode(deferredSave, true); 80 | deferredSave.addAction(new LabelAction("I was deferred but should still be written")); 81 | assert !storage.isPersistedFully(); 82 | 83 | storage.flush(); 84 | assert storage.isPersistedFully(); 85 | 86 | // Now we try to read it back 87 | MockFlowExecution mock2 = new MockFlowExecution(); 88 | FlowNodeStorage storageAfterRead = instantiateStorage(mock2, storageDir); 89 | assert storage.isPersistedFully(); 90 | 91 | StorageTestUtils.assertNodesMatch(simple, storageAfterRead.getNode(simple.getId())); 92 | StorageTestUtils.assertNodesMatch(notQuiteAsSimple, storageAfterRead.getNode(notQuiteAsSimple.getId())); 93 | StorageTestUtils.assertNodesMatch(directlySaveActions, storageAfterRead.getNode(directlySaveActions.getId())); 94 | StorageTestUtils.assertNodesMatch(deferredSave, storageAfterRead.getNode(deferredSave.getId())); 95 | 96 | // Ensures we can still read the correct node from deferred version 97 | StorageTestUtils.assertNodesMatch(storage.getNode(deferredSave.getId()), storage.getNode(deferredSave.getId())); 98 | } 99 | 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2024 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.support.storage; 26 | 27 | import hudson.model.Result; 28 | import hudson.util.RobustReflectionConverter; 29 | import java.io.File; 30 | import java.util.ArrayList; 31 | import java.util.logging.Level; 32 | import java.util.stream.Collectors; 33 | import java.util.stream.IntStream; 34 | import javax.xml.parsers.DocumentBuilderFactory; 35 | import org.jenkinsci.plugins.workflow.actions.LabelAction; 36 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 37 | import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode; 38 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 39 | import org.junit.Rule; 40 | import org.junit.Test; 41 | import org.jvnet.hudson.test.JenkinsRule; 42 | import org.jvnet.hudson.test.LoggerRule; 43 | import org.jvnet.hudson.test.recipes.LocalData; 44 | import org.w3c.dom.Element; 45 | import org.w3c.dom.Node; 46 | import static org.hamcrest.MatcherAssert.assertThat; 47 | import static org.hamcrest.Matchers.equalTo; 48 | import static org.hamcrest.Matchers.is; 49 | import static org.hamcrest.Matchers.not; 50 | import static org.hamcrest.Matchers.nullValue; 51 | 52 | public final class BulkFlowNodeStorageTest { 53 | 54 | @Rule public JenkinsRule r = new JenkinsRule(); 55 | @Rule public LoggerRule logger = new LoggerRule().record(RobustReflectionConverter.class, Level.FINE).capture(50); 56 | 57 | @Test public void orderOfEntries() throws Exception { 58 | var p = r.createProject(WorkflowJob.class, "p"); 59 | p.setDefinition(new CpsFlowDefinition("for (int i = 1; i <= 40; i++) {echo(/step #$i/)}", true)); 60 | var b = r.buildAndAssertSuccess(p); 61 | var entryList = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File(b.getRootDir(), "workflow-completed/flowNodeStore.xml")).getDocumentElement().getChildNodes(); 62 | var ids = new ArrayList(); 63 | for (int i = 0; i < entryList.getLength(); i++) { 64 | var entry = entryList.item(i); 65 | if (entry.getNodeType() == Node.ELEMENT_NODE) { 66 | ids.add(((Element) entry).getElementsByTagName("*").item(0).getTextContent()); 67 | } 68 | } 69 | assertThat(ids, is(IntStream.rangeClosed(2, 43).mapToObj(Integer::toString).collect(Collectors.toList()))); 70 | } 71 | 72 | @LocalData 73 | @Test public void actionDeserializationShouldBeRobust() throws Exception { 74 | /* 75 | var p = r.createProject(WorkflowJob.class); 76 | p.addProperty(new DurabilityHintJobProperty(FlowDurabilityHint.PERFORMANCE_OPTIMIZED)); 77 | p.setDefinition(new CpsFlowDefinition( 78 | "stage('test') {\n" + 79 | " sleep 120\n" + 80 | "}\n", true)); 81 | var b = p.scheduleBuild2(0).waitForStart(); 82 | Thread.sleep(5*1000); 83 | ((CpsFlowExecution) b.getExecution()).getStorage().flush(); 84 | */ 85 | var p = r.jenkins.getItemByFullName("test0", WorkflowJob.class); 86 | var b = p.getLastBuild(); 87 | // Build is unresumable because the local data was created with PERFORMANCE_OPTIMIZED without a clean shutdown. 88 | r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b)); 89 | // Existing flow nodes should still be preserved though. 90 | var stageBodyStartNode = (StepStartNode) b.getExecution().getNode("4"); 91 | assertThat(stageBodyStartNode, not(nullValue())); 92 | var label = stageBodyStartNode.getPersistentAction(LabelAction.class); 93 | assertThat(label.getDisplayName(), equalTo("test")); 94 | } 95 | // Used to create @LocalData for above test: 96 | /* 97 | public static class MyAction extends InvisibleAction implements PersistentAction { 98 | private final String value = "42"; 99 | } 100 | @TestExtension("actionDeserializationShouldBeRobust") 101 | public static class MyActionAdder implements GraphListener.Synchronous { 102 | @Override 103 | public void onNewHead(FlowNode node) { 104 | node.addAction(new MyAction()); 105 | } 106 | } 107 | */ 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/storage/BulkStorageTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.support.storage; 2 | 3 | import org.jenkinsci.plugins.workflow.actions.BodyInvocationAction; 4 | import org.jenkinsci.plugins.workflow.actions.LabelAction; 5 | import org.jenkinsci.plugins.workflow.graph.AtomNode; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | import java.io.File; 10 | 11 | /** 12 | * Actually attempts to test the storage engine 13 | */ 14 | public class BulkStorageTest extends AbstractStorageTest { 15 | 16 | @Override 17 | public FlowNodeStorage instantiateStorage(MockFlowExecution exec, File storageDirectory) { 18 | return new BulkFlowNodeStorage(exec, storageDir); 19 | } 20 | 21 | /** Tests the bulk-flushing behavior works as advertised. */ 22 | @Test 23 | public void testDeferWriteAndFlush() throws Exception { 24 | MockFlowExecution mock = new MockFlowExecution(); 25 | FlowNodeStorage storage = instantiateStorage(mock, storageDir); 26 | mock.setStorage(storage); 27 | file.write(mock); 28 | 29 | // Non-deferred write 30 | AtomNode directlyStored = new StorageTestUtils.SimpleAtomNode(mock, "directlyStored"); 31 | directlyStored.addAction(new LabelAction("directStored")); 32 | storage.storeNode(directlyStored, false); 33 | assert storage.isPersistedFully(); 34 | // If we added the action after, it wouldn't write out, because lump storage only writes on flush 35 | 36 | // Read and confirm the non-deferred one wrote 37 | MockFlowExecution mock2 = new MockFlowExecution(); 38 | FlowNodeStorage storageAfterRead = instantiateStorage(mock2, storageDir); 39 | mock2.setStorage(storageAfterRead); 40 | StorageTestUtils.assertNodesMatch(directlyStored, storageAfterRead.getNode(directlyStored.getId())); 41 | 42 | // Node with actions added after storing, and deferred write 43 | AtomNode deferredWriteNode = new StorageTestUtils.SimpleAtomNode(mock, "deferredWrite"); 44 | storage.storeNode(deferredWriteNode, true); 45 | assert !storage.isPersistedFully(); 46 | deferredWriteNode.addAction(new LabelAction("displayLabel")); 47 | 48 | // Read back and confirm the deferred write didn't flush to disk 49 | storageAfterRead = instantiateStorage(mock2, storageDir); 50 | assert storageAfterRead.isPersistedFully(); 51 | mock2.setStorage(storageAfterRead); 52 | Assert.assertNull(storageAfterRead.getNode(deferredWriteNode.getId())); 53 | StorageTestUtils.assertNodesMatch(directlyStored, storageAfterRead.getNode(directlyStored.getId())); // Make sure we didn't corrupt old node either 54 | 55 | // Flush the deferred one and confirm it's on disk now 56 | storage.flushNode(deferredWriteNode); 57 | assert storage.isPersistedFully(); 58 | storageAfterRead = instantiateStorage(mock2, storageDir); 59 | mock2.setStorage(storageAfterRead); 60 | StorageTestUtils.assertNodesMatch(deferredWriteNode, storageAfterRead.getNode(deferredWriteNode.getId())); 61 | 62 | // Add an action and re-read to confirm that it doesn't autopersist still 63 | deferredWriteNode.addAction(new BodyInvocationAction()); 64 | assert !storage.isPersistedFully(); 65 | storageAfterRead = instantiateStorage(mock2, storageDir); 66 | mock2.setStorage(storageAfterRead); 67 | Assert.assertEquals(1, storageAfterRead.getNode(deferredWriteNode.getId()).getActions().size()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/storage/MockFlowExecution.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.support.storage; 2 | 3 | import hudson.model.Action; 4 | import hudson.model.Result; 5 | import hudson.security.ACL; 6 | import jenkins.model.CauseOfInterruption; 7 | import org.acegisecurity.Authentication; 8 | import org.jenkinsci.plugins.workflow.flow.FlowExecution; 9 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 10 | import org.jenkinsci.plugins.workflow.flow.GraphListener; 11 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 12 | 13 | import edu.umd.cs.findbugs.annotations.CheckForNull; 14 | import edu.umd.cs.findbugs.annotations.NonNull; 15 | import java.io.IOException; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | /** 20 | * Mock {@link org.jenkinsci.plugins.workflow.flow.FlowExecution} for testing storage 21 | * @author Sam Van Oort 22 | */ 23 | class MockFlowExecution extends FlowExecution { 24 | 25 | List heads = new ArrayList<>(); 26 | transient FlowNodeStorage storage; 27 | 28 | MockFlowExecution(@NonNull FlowNodeStorage storage) { 29 | this.storage = storage; 30 | } 31 | 32 | MockFlowExecution() { 33 | 34 | } 35 | 36 | @Override 37 | public void start() throws IOException { 38 | 39 | } 40 | 41 | @Override 42 | public FlowExecutionOwner getOwner() { 43 | return null; 44 | } 45 | 46 | @Override 47 | public List getCurrentHeads() { 48 | return heads; 49 | } 50 | 51 | public MockFlowExecution setCurrentHeads(List heads) { 52 | this.heads = heads; 53 | return this; 54 | } 55 | 56 | @Override 57 | public boolean isCurrentHead(FlowNode n) { 58 | return this.heads.contains(n); 59 | } 60 | 61 | @Override 62 | public void interrupt(Result r, CauseOfInterruption... causes) throws IOException, InterruptedException { 63 | 64 | } 65 | 66 | @Override 67 | public void addListener(GraphListener listener) { 68 | 69 | } 70 | 71 | @Override 72 | public void onLoad() { 73 | 74 | } 75 | 76 | public FlowNodeStorage getStorage() { 77 | return storage; 78 | } 79 | 80 | public MockFlowExecution setStorage(@NonNull FlowNodeStorage store) { 81 | this.storage = store; 82 | return this; 83 | } 84 | 85 | @CheckForNull 86 | @Override 87 | public FlowNode getNode(String id) throws IOException { 88 | return getStorage().getNode(id); 89 | } 90 | 91 | @NonNull 92 | @Override 93 | public Authentication getAuthentication() { 94 | return ACL.SYSTEM; 95 | } 96 | 97 | @Override 98 | public List loadActions(FlowNode node) throws IOException { 99 | return getStorage().loadActions(node); 100 | } 101 | 102 | @Override 103 | public void saveActions(FlowNode node, List actions) throws IOException { 104 | getStorage().saveActions(node, actions); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.support.storage; 2 | 3 | 4 | import hudson.model.Result; 5 | import hudson.util.RobustReflectionConverter; 6 | import java.io.File; 7 | import java.util.logging.Level; 8 | import org.jenkinsci.plugins.workflow.actions.BodyInvocationAction; 9 | import org.jenkinsci.plugins.workflow.actions.LabelAction; 10 | import org.jenkinsci.plugins.workflow.actions.TimingAction; 11 | import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode; 12 | import org.jenkinsci.plugins.workflow.graph.AtomNode; 13 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 14 | import org.junit.Assert; 15 | import org.junit.Rule; 16 | import org.junit.Test; 17 | import org.jvnet.hudson.test.LoggerRule; 18 | import org.jvnet.hudson.test.recipes.LocalData; 19 | import static org.hamcrest.MatcherAssert.assertThat; 20 | import static org.hamcrest.Matchers.equalTo; 21 | import static org.hamcrest.Matchers.not; 22 | import static org.hamcrest.Matchers.nullValue; 23 | 24 | /** 25 | * Tries to test the storage engine 26 | */ 27 | public class SimpleXStreamStorageTest extends AbstractStorageTest { 28 | 29 | @Rule public LoggerRule logger = new LoggerRule().record(RobustReflectionConverter.class, Level.FINE).capture(50); 30 | 31 | @Override 32 | public FlowNodeStorage instantiateStorage(MockFlowExecution exec, File storageDirectory) { 33 | return new SimpleXStreamFlowNodeStorage(exec, storageDirectory); 34 | } 35 | 36 | /** Verify that when nodes are explicitly flushed they do write to disk. */ 37 | @Test 38 | public void testDeferWriteAndFlush() throws Exception { 39 | MockFlowExecution mock = new MockFlowExecution(); 40 | FlowNodeStorage storage = instantiateStorage(mock, storageDir); 41 | mock.setStorage(storage); 42 | file.write(mock); 43 | 44 | // Non-deferred write 45 | AtomNode directlyStored = new StorageTestUtils.SimpleAtomNode(mock, "directlyStored"); 46 | storage.storeNode(directlyStored, false); 47 | assert storage.isPersistedFully(); 48 | directlyStored.addAction(new LabelAction("directStored")); 49 | 50 | // Node with actions added after storing, and deferred write 51 | AtomNode deferredWriteNode = new StorageTestUtils.SimpleAtomNode(mock, "deferredWrite"); 52 | storage.storeNode(deferredWriteNode, true); 53 | deferredWriteNode.addAction(new LabelAction("displayLabel")); 54 | assert !storage.isPersistedFully(); 55 | 56 | // Read and confirm the non-deferred one wrote, and the deferred one didn't 57 | MockFlowExecution mock2 = new MockFlowExecution(); 58 | FlowNodeStorage storageAfterRead = instantiateStorage(mock2, storageDir); 59 | assert storageAfterRead.isPersistedFully(); 60 | 61 | mock2.setStorage(storageAfterRead); 62 | StorageTestUtils.assertNodesMatch(directlyStored, storageAfterRead.getNode(directlyStored.getId())); 63 | Assert.assertNull(storageAfterRead.getNode(deferredWriteNode.getId())); 64 | 65 | 66 | // Flush the deferred one and confirm it's on disk now 67 | storage.flushNode(deferredWriteNode); 68 | assert storage.isPersistedFully(); 69 | storageAfterRead = instantiateStorage(mock2, storageDir); 70 | mock2.setStorage(storageAfterRead); 71 | StorageTestUtils.assertNodesMatch(deferredWriteNode, storageAfterRead.getNode(deferredWriteNode.getId())); 72 | 73 | // Add an action and re-read to confirm that it doesn't autopersist still 74 | deferredWriteNode.addAction(new BodyInvocationAction()); 75 | assert !storage.isPersistedFully(); 76 | storageAfterRead = instantiateStorage(mock2, storageDir); 77 | mock2.setStorage(storageAfterRead); 78 | Assert.assertEquals(1, storageAfterRead.getNode(deferredWriteNode.getId()).getActions().size()); 79 | 80 | // Mark node for autopersist and confirm it actually does now by adding a new action 81 | storage.autopersist(deferredWriteNode); 82 | assert storage.isPersistedFully(); 83 | deferredWriteNode.addAction(new TimingAction()); 84 | storageAfterRead = instantiateStorage(mock2, storageDir); 85 | mock2.setStorage(storageAfterRead); 86 | StorageTestUtils.assertNodesMatch(deferredWriteNode, storageAfterRead.getNode(deferredWriteNode.getId())); 87 | } 88 | 89 | @LocalData 90 | @Test public void actionDeserializationShouldBeRobust() throws Exception { 91 | /* 92 | var p = j.createProject(WorkflowJob.class); 93 | p.addProperty(new DurabilityHintJobProperty(FlowDurabilityHint.MAX_SURVIVABILITY)); 94 | p.setDefinition(new CpsFlowDefinition( 95 | "stage('test') {\n" + 96 | " sleep 120\n" + 97 | "}\n", true)); 98 | var b = p.scheduleBuild2(0).waitForStart(); 99 | Thread.sleep(5*1000); 100 | ((CpsFlowExecution) b.getExecution()).getStorage().flush(); 101 | */ 102 | var p = j.jenkins.getItemByFullName("test0", WorkflowJob.class); 103 | var b = p.getLastBuild(); 104 | j.assertBuildStatus(Result.SUCCESS, j.waitForCompletion(b)); 105 | var stageBodyStartNode = (StepStartNode) b.getExecution().getNode("4"); 106 | assertThat(stageBodyStartNode, not(nullValue())); 107 | var label = stageBodyStartNode.getPersistentAction(LabelAction.class); 108 | assertThat(label.getDisplayName(), equalTo("test")); 109 | } 110 | // Used to create @LocalData for above test: 111 | /* 112 | public static class MyAction extends InvisibleAction implements PersistentAction { 113 | private final String value = "42"; 114 | } 115 | @TestExtension("actionDeserializationShouldBeRobust") 116 | public static class MyActionAdder implements GraphListener.Synchronous { 117 | @Override 118 | public void onNewHead(FlowNode node) { 119 | node.addAction(new MyAction()); 120 | } 121 | } 122 | */ 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/storage/StorageTestUtils.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.support.storage; 2 | 3 | import hudson.model.Action; 4 | import org.jenkinsci.plugins.workflow.flow.FlowExecution; 5 | import org.jenkinsci.plugins.workflow.graph.AtomNode; 6 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 7 | import org.junit.Assert; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Utilities for verifying storage 13 | * @author Sam Van Oort 14 | */ 15 | class StorageTestUtils { 16 | 17 | /** Test that the basic fields and the actions for nodes match */ 18 | public static void assertNodesMatch(FlowNode expected, FlowNode actual) throws Exception { 19 | Assert.assertEquals(expected.getId(), actual.getId()); 20 | Assert.assertEquals(expected.getClass().toString(), actual.getClass().toString()); 21 | Assert.assertArrayEquals(expected.getParentIds().toArray(), actual.getParentIds().toArray()); 22 | 23 | List expectedActionList = expected.getActions(); 24 | List actualActionList = actual.getActions(); 25 | 26 | Assert.assertEquals(expectedActionList.size(), actualActionList.size()); 27 | for (int i=0; i actions) { 52 | super.setActions(actions); 53 | } 54 | 55 | SimpleAtomNode(FlowExecution exec, String id, FlowNode... parents) { 56 | super(exec, id, parents); 57 | } 58 | 59 | SimpleAtomNode(FlowExecution exec, String id) { 60 | super(exec, id, new FlowNode[]{}); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/support/visualization/table/FlowGraphTableTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2020 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.workflow.support.visualization.table; 25 | 26 | import org.apache.commons.lang.StringUtils; 27 | import static org.hamcrest.MatcherAssert.assertThat; 28 | import static org.hamcrest.Matchers.arrayContaining; 29 | import static org.hamcrest.Matchers.containsString; 30 | import static org.hamcrest.Matchers.hasSize; 31 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 32 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 33 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 34 | import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep; 35 | import static org.junit.Assert.fail; 36 | import org.junit.Rule; 37 | import org.junit.Test; 38 | import org.jvnet.hudson.test.Issue; 39 | import org.jvnet.hudson.test.JenkinsRule; 40 | import org.jvnet.hudson.test.recipes.LocalData; 41 | 42 | public class FlowGraphTableTest { 43 | 44 | @Rule 45 | public JenkinsRule r = new JenkinsRule(); 46 | 47 | @Test 48 | public void smokes() throws Exception { 49 | WorkflowJob p = r.createProject(WorkflowJob.class); 50 | p.setDefinition(new CpsFlowDefinition( 51 | "echo('Hello, world!')\n" + 52 | "timeout(time: 1, unit: 'MINUTES') {\n" + 53 | " echo('Hello again, world!')\n" + 54 | "}\n" + 55 | "echo('Goodbye, world!')\n", true)); 56 | WorkflowRun b = r.buildAndAssertSuccess(p); 57 | FlowGraphTable t = new FlowGraphTable(b.getExecution()); 58 | t.build(); 59 | // Flow start, timeout step and body start, 3 echo steps. 60 | assertThat(t.getRows(), hasSize(6)); 61 | } 62 | 63 | @Test 64 | public void parallelSmokes() throws Exception { 65 | WorkflowJob p = r.createProject(WorkflowJob.class); 66 | p.setDefinition(new CpsFlowDefinition( 67 | "echo('Hello, world!')\n" + 68 | "parallel(one: {\n" + 69 | " timeout(time: 1, unit: 'MINUTES') {\n" + 70 | " echo('Hello, branch one!')\n" + 71 | " }\n" + 72 | "}, two: {\n" + 73 | " timeout(time: 1, unit: 'MINUTES') {\n" + 74 | " echo('Hello, branch two!')\n" + 75 | " }\n" + 76 | "})\n" + 77 | "echo('Goodbye, world!')\n", true)); 78 | WorkflowRun b = r.buildAndAssertSuccess(p); 79 | FlowGraphTable t = new FlowGraphTable(b.getExecution()); 80 | t.build(); 81 | // Flow start, parallel step start, 2 parallel branch starts, 2 timeout step starts, 2 timeout body starts, 4 echo steps. 82 | assertThat(t.getRows(), hasSize(12)); 83 | } 84 | 85 | @Issue("JENKINS-62545") 86 | @LocalData // There is no known way to reproduce the issue from scratch, so we use a fake build with redacted flow node XML files from a real build that had the problem. 87 | @Test 88 | public void corruptedFlowGraph() throws Exception { 89 | WorkflowJob p = r.jenkins.getItemByFullName("test0", WorkflowJob.class); 90 | WorkflowRun b = p.getBuildByNumber(1); 91 | FlowGraphTable t = new FlowGraphTable(b.getExecution()); 92 | try { 93 | t.build(); 94 | fail("Table construction should fail"); 95 | } catch (IllegalStateException e) { 96 | assertThat(e.getMessage(), containsString("twice when finding siblings of StepStartNode[id=199")); 97 | } 98 | } 99 | 100 | @Test 101 | public void rowDisplayName() throws Exception { 102 | WorkflowJob p = r.createProject(WorkflowJob.class); 103 | p.setDefinition(new CpsFlowDefinition( 104 | "stage('start') {\n" + 105 | " echo 'some message'\n" + 106 | " def i = 0; retry(3) {\n" + 107 | " if (++i < 3) error 'oops'\n" + 108 | " node {\n" + 109 | " isUnix()\n" + 110 | " }\n" + 111 | " }\n" + 112 | "}\n" + 113 | "stage('main') {\n" + 114 | " parallel quick: {\n" + 115 | " echo 'done'\n" + 116 | " }, slow: {\n" + 117 | " semaphore 'wait'\n" + 118 | " }\n" + 119 | "}", true)); 120 | WorkflowRun b = p.scheduleBuild2(0).waitForStart(); 121 | SemaphoreStep.waitForStart("wait/1", b); 122 | FlowGraphTable t = new FlowGraphTable(b.getExecution()); 123 | t.build(); 124 | SemaphoreStep.success("wait/1", null); 125 | r.waitForCompletion(b); 126 | assertThat(t.getRows().stream().map(r -> StringUtils.repeat(" ", r.getTreeDepth()) + r.getDisplayName()).toArray(String[]::new), arrayContaining( 127 | "Start of Pipeline", 128 | " stage", 129 | " stage block (start)", 130 | " echo", 131 | " retry", 132 | " retry block", 133 | " error", 134 | " retry block", 135 | " error", 136 | " retry block", 137 | " node", 138 | " node block", 139 | " isUnix", 140 | " stage", 141 | " stage block (main)", 142 | " parallel", 143 | " parallel block (Branch: quick)", 144 | " echo", 145 | " parallel block (Branch: slow)", 146 | " semaphore")); 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/test/steps/BlockSemaphoreStep.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.workflow.test.steps; 26 | 27 | import com.google.common.util.concurrent.FutureCallback; 28 | import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback; 29 | import org.jenkinsci.plugins.workflow.steps.Step; 30 | import org.jenkinsci.plugins.workflow.steps.StepContext; 31 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 32 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 33 | 34 | import java.util.Map; 35 | import java.util.Set; 36 | 37 | /** 38 | * Block step that can be externally controlled. 39 | */ 40 | public final class BlockSemaphoreStep extends Step { 41 | 42 | public enum State { 43 | /** {@link #start} has not yet been called. */ 44 | INIT, 45 | /** {@link #start} has been called, but the block has not started. */ 46 | STARTED, 47 | /** {@link StepContext#newBodyInvoker} has been called, so the block has started. */ 48 | BLOCK_STARTED, 49 | /** {@link FutureCallback} from {@link StepContext#newBodyInvoker} has been notified, so the block has ended. */ 50 | BLOCK_ENDED, 51 | /** {@link FutureCallback} from {@link Step} has been notified, so the whole step has ended. */ 52 | DONE, 53 | /** Aborted through {@link StepExecution#stop(Throwable)}. */ 54 | STOPPED 55 | } 56 | 57 | private State state = State.INIT; 58 | private StepContext context; 59 | private Object blockReturnValue; 60 | private Throwable blockFailure; 61 | 62 | private void moveFrom(State startingState) { 63 | assert state == startingState : state; 64 | state = State.values()[state.ordinal() + 1]; 65 | } 66 | 67 | public State getState() { 68 | return state; 69 | } 70 | 71 | @Override public StepExecution start(final StepContext context) throws Exception { 72 | this.context = context; 73 | return new StepExecution(context) { 74 | @Override 75 | public boolean start() throws Exception { 76 | moveFrom(State.INIT); 77 | return false; 78 | } 79 | 80 | @Override 81 | public void stop(Throwable cause) throws Exception { 82 | state = State.STOPPED; // force the state change regardless of the current state 83 | super.stop(cause); 84 | } 85 | }; 86 | } 87 | 88 | public void startBlock(Object... contextOverrides) { 89 | moveFrom(State.STARTED); 90 | context.newBodyInvoker().withContext(contextOverrides).withCallback(new Callback()).start(); 91 | } 92 | 93 | private class Callback extends BodyExecutionCallback { 94 | @Override public void onSuccess(StepContext context, Object returnValue) { 95 | blockReturnValue = returnValue; 96 | blockDone(); 97 | } 98 | @Override public void onFailure(StepContext context, Throwable t) { 99 | blockFailure = t; 100 | blockDone(); 101 | } 102 | } 103 | 104 | private synchronized void blockDone() { 105 | moveFrom(State.BLOCK_STARTED); 106 | notifyAll(); 107 | } 108 | 109 | public synchronized Object waitForBlock() throws Throwable { 110 | while (state == State.BLOCK_STARTED) { 111 | wait(); 112 | } 113 | assert state == State.BLOCK_ENDED : state; 114 | if (blockFailure != null) { 115 | throw blockFailure; 116 | } else { 117 | return blockReturnValue; 118 | } 119 | } 120 | 121 | public void finishSuccess(Object returnValue) { 122 | moveFrom(State.BLOCK_ENDED); 123 | context.onSuccess(returnValue); 124 | } 125 | 126 | public void finishFailure(Throwable t) { 127 | moveFrom(State.BLOCK_ENDED); 128 | context.onFailure(t); 129 | } 130 | 131 | @Override public StepDescriptor getDescriptor() { 132 | return new DescriptorImpl(); 133 | } 134 | 135 | /* not an @Extension */ private static final class DescriptorImpl extends StepDescriptor { 136 | 137 | @Override public Set> getRequiredContext() { 138 | throw new UnsupportedOperationException(); 139 | } 140 | 141 | @Override public String getFunctionName() { 142 | throw new UnsupportedOperationException(); 143 | } 144 | 145 | @Override public Step newInstance(Map arguments) { 146 | throw new UnsupportedOperationException(); 147 | } 148 | 149 | @Override public Map defineArguments(Step step) throws UnsupportedOperationException { 150 | throw new UnsupportedOperationException(); 151 | } 152 | 153 | @Override public String getDisplayName() { 154 | return "Test block step"; 155 | } 156 | 157 | @Override public boolean takesImplicitBlockArgument() { 158 | return true; 159 | } 160 | 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/test/steps/SemaphoreStepTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.test.steps; 2 | 3 | import hudson.model.Result; 4 | import hudson.model.queue.QueueTaskFuture; 5 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 6 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 7 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.jvnet.hudson.test.JenkinsRule; 11 | 12 | import static org.junit.Assert.assertNotNull; 13 | 14 | public class SemaphoreStepTest { 15 | @Rule 16 | public JenkinsRule j = new JenkinsRule(); 17 | 18 | @Test 19 | public void hardKill() throws Exception { 20 | WorkflowJob p1 = j.jenkins.createProject(WorkflowJob.class, "p"); 21 | p1.setDefinition(new CpsFlowDefinition("echo 'locked!'; semaphore 'wait'")); 22 | QueueTaskFuture future = p1.scheduleBuild2(0); 23 | assertNotNull(future); 24 | WorkflowRun b = future.waitForStart(); 25 | SemaphoreStep.waitForStart("wait/1", b); 26 | 27 | b.doKill(); 28 | j.waitForMessage("Hard kill!", b); 29 | j.assertBuildStatus(Result.ABORTED, j.waitForCompletion(b)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/test/steps/SynchronousResumeNotSupportedExceptionTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.test.steps; 2 | 3 | import java.util.Set; 4 | 5 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 6 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 7 | import org.jenkinsci.plugins.workflow.steps.Step; 8 | import org.jenkinsci.plugins.workflow.steps.StepContext; 9 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 10 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 11 | import org.jenkinsci.plugins.workflow.steps.StepExecutions; 12 | import org.junit.ClassRule; 13 | import org.junit.Rule; 14 | import org.junit.Test; 15 | import org.jvnet.hudson.test.BuildWatcher; 16 | import org.jvnet.hudson.test.JenkinsSessionRule; 17 | import org.jvnet.hudson.test.TestExtension; 18 | import org.kohsuke.stapler.DataBoundConstructor; 19 | 20 | import hudson.model.TaskListener; 21 | 22 | import static org.hamcrest.MatcherAssert.assertThat; 23 | import static org.hamcrest.Matchers.allOf; 24 | import static org.hamcrest.Matchers.containsString; 25 | 26 | public class SynchronousResumeNotSupportedExceptionTest { 27 | 28 | @ClassRule 29 | public static BuildWatcher bw = new BuildWatcher(); 30 | 31 | @Rule 32 | public JenkinsSessionRule rjr = new JenkinsSessionRule(); 33 | 34 | @Test 35 | public void test_SynchronousResumeNotSupportedException_ShouldShowFailedStep_AndSuggestRetry() throws Throwable { 36 | rjr.then(j -> { 37 | WorkflowJob p = j.createProject(WorkflowJob.class, "p"); 38 | p.setDefinition(new CpsFlowDefinition("simulatedSleep 20", true)); 39 | var run = p.scheduleBuild2(0).waitForStart(); 40 | j.waitForMessage("Going to sleep for", run); 41 | }); 42 | rjr.then(j -> { 43 | var b = j.jenkins.getItemByFullName("p", WorkflowJob.class).getBuildByNumber(1); 44 | j.waitForCompletion(b); 45 | assertThat(b.getLog(), allOf( 46 | containsString("nonresumable"), 47 | containsString("retry"), 48 | containsString("simulatedSleep"), 49 | containsString("retries"))); 50 | }); 51 | } 52 | 53 | @SuppressWarnings("unused") 54 | public static class SimulatedSleepStep extends Step { 55 | 56 | private final int sleepSeconds; 57 | 58 | @DataBoundConstructor 59 | public SimulatedSleepStep(int sleepSeconds) { 60 | this.sleepSeconds = sleepSeconds; 61 | } 62 | 63 | @Override 64 | public StepExecution start(StepContext context) throws Exception { 65 | return StepExecutions.synchronousNonBlockingVoid(context, c -> { 66 | var buildLogger = context.get(TaskListener.class).getLogger(); 67 | buildLogger.println("Simulated sleep step started"); 68 | buildLogger.println("Going to sleep for " + sleepSeconds + " seconds"); 69 | Thread.sleep(sleepSeconds * 1000L); 70 | buildLogger.println("Simulated sleep step completed"); 71 | }); 72 | } 73 | 74 | @TestExtension 75 | public static final class DescriptorImpl extends StepDescriptor { 76 | 77 | @Override 78 | public Set> getRequiredContext() { 79 | return Set.of(TaskListener.class); 80 | } 81 | 82 | @Override 83 | public String getFunctionName() { 84 | return "simulatedSleep"; 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/test/steps/input/POSTHyperlinkNoteTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.workflow.test.steps.input; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.allOf; 5 | import static org.hamcrest.Matchers.hasProperty; 6 | import static org.hamcrest.Matchers.is; 7 | import static org.hamcrest.Matchers.notNullValue; 8 | import java.net.URL; 9 | import java.util.Collections; 10 | import java.util.Map; 11 | import java.util.Set; 12 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 13 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 14 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 15 | import org.jenkinsci.plugins.workflow.steps.Step; 16 | import org.jenkinsci.plugins.workflow.steps.StepContext; 17 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 18 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 19 | import org.jenkinsci.plugins.workflow.steps.SynchronousStepExecution; 20 | import org.jenkinsci.plugins.workflow.support.steps.input.POSTHyperlinkNote; 21 | import org.junit.Ignore; 22 | import org.junit.Rule; 23 | import org.junit.Test; 24 | import org.jvnet.hudson.test.Issue; 25 | import org.jvnet.hudson.test.JenkinsRule; 26 | import org.jvnet.hudson.test.JenkinsRule.WebClient; 27 | import org.jvnet.hudson.test.TestExtension; 28 | import org.kohsuke.stapler.DataBoundConstructor; 29 | import org.htmlunit.HttpMethod; 30 | import org.htmlunit.MockWebConnection; 31 | import org.htmlunit.Page; 32 | import org.htmlunit.WebRequest; 33 | import org.htmlunit.html.HtmlAnchor; 34 | import org.htmlunit.html.HtmlPage; 35 | import edu.umd.cs.findbugs.annotations.NonNull; 36 | import hudson.model.ParametersAction; 37 | import hudson.model.ParametersDefinitionProperty; 38 | import hudson.model.Result; 39 | import hudson.model.StringParameterDefinition; 40 | import hudson.model.StringParameterValue; 41 | import hudson.model.TaskListener; 42 | import hudson.model.queue.QueueTaskFuture; 43 | import jenkins.model.Jenkins; 44 | 45 | public class POSTHyperlinkNoteTest { 46 | 47 | @Rule 48 | public JenkinsRule jr = new JenkinsRule(); 49 | 50 | @Test 51 | @Issue("SECURITY-2881") 52 | public void urlsAreSafeFromJavascriptInjection() throws Exception { 53 | testSanitization("whatever/'+alert(1)+'"); 54 | } 55 | 56 | @Test 57 | @Ignore("webclient does not support unicode URLS and this is passed as /jenkins/whatever/%F0%9F%99%88%F0%9F%99%89%F0%9F%99%8A%F0%9F%98%80%E2%98%BA") 58 | public void testPassingMultiByteCharacters() throws Exception { 59 | // this is actually illegal in HTML4 but common -> https://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars 60 | // browsers infer the URL from the charset and then encode the escaped characters... 61 | testSanitization("whatever/🙈🙉🙊😀☺"); 62 | } 63 | 64 | @Test 65 | public void testPassingSingleByte() throws Exception { 66 | testSanitization("whatever/something?withparameter=baa"); 67 | } 68 | 69 | void testSanitization(String fragment) throws Exception { 70 | WorkflowJob project = jr.createProject(WorkflowJob.class); 71 | project.setDefinition(new CpsFlowDefinition("security2881(params.TEST_URL)\n", true)); 72 | project.addProperty(new ParametersDefinitionProperty(new StringParameterDefinition("TEST_URL", "WHOOPS"))); 73 | 74 | QueueTaskFuture scheduleBuild = project.scheduleBuild2(0, new ParametersAction(new StringParameterValue("TEST_URL", fragment))); 75 | WorkflowRun run = jr.assertBuildStatus(Result.SUCCESS, scheduleBuild); 76 | WebClient wc = jr.createWebClient(); 77 | 78 | HtmlPage page = wc.getPage(run, "console"); 79 | HtmlAnchor anchor = page.getAnchorByText("SECURITY-2881"); 80 | assertThat(anchor, notNullValue()); 81 | MockWebConnection mwc = new MockWebConnection(); 82 | mwc.setDefaultResponse("Hello"); 83 | wc.setWebConnection(mwc); 84 | System.out.println(anchor); 85 | Page p = anchor.click(); 86 | 87 | // the click executes an ajax request - and so we need to wait until that has completed 88 | // ideally we would pass zero here as we have already clicked the javascript should have 89 | // started executing - but this is not always the case 90 | wc.waitForBackgroundJavaScriptStartingBefore(500); 91 | 92 | // check we have an interaction at the correct place and its not a javascript issue. 93 | WebRequest request = mwc.getLastWebRequest(); 94 | assertThat(request, notNullValue()); 95 | assertThat(request.getHttpMethod(), is(HttpMethod.POST)); 96 | URL url = request.getUrl(); 97 | System.out.println(url.toExternalForm()); 98 | assertThat(url, allOf(hasProperty("host", is(new URL(jr.jenkins.getConfiguredRootUrl()).getHost())), 99 | hasProperty("file", is(jr.contextPath + '/' + fragment)))); 100 | } 101 | 102 | public static class Security2881ConsoleStep extends Step { 103 | 104 | private final String urlFragment; 105 | 106 | @DataBoundConstructor 107 | public Security2881ConsoleStep(String urlFragment) { 108 | this.urlFragment = urlFragment; 109 | } 110 | 111 | @Override 112 | public StepExecution start(StepContext context) throws Exception { 113 | return new Security2881ConsoleStepExecution(context, urlFragment); 114 | } 115 | 116 | @TestExtension 117 | public static final class DescriptorImpl extends StepDescriptor { 118 | 119 | @Override public String getFunctionName() { 120 | return "security2881"; 121 | } 122 | 123 | @NonNull 124 | @Override public String getDisplayName() { 125 | return "Security2881"; 126 | } 127 | 128 | @Override public Set> getRequiredContext() { 129 | return Collections.singleton(TaskListener.class); 130 | } 131 | 132 | @Override public String argumentsToString(@NonNull Map namedArgs) { 133 | return null; 134 | } 135 | } 136 | 137 | public static class Security2881ConsoleStepExecution extends SynchronousStepExecution { 138 | 139 | private final String urlFragment; 140 | 141 | protected Security2881ConsoleStepExecution(StepContext context, String urlFragment) { 142 | super(context); 143 | this.urlFragment = urlFragment; 144 | } 145 | 146 | @Override 147 | protected Void run() throws Exception { 148 | TaskListener taskListener = getContext().get(TaskListener.class); 149 | // use the same URL for CORS. 150 | taskListener.getLogger().print(POSTHyperlinkNote.encodeTo(Jenkins.get().getConfiguredRootUrl() + urlFragment, "SECURITY-2881")); 151 | return null; 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/workflow/test/steps/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013-2014, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * Steps which are handy for verifying flow jobs and implementations of flow engines. 27 | */ 28 | package org.jenkinsci.plugins.workflow.test.steps; 29 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 1714512454761 6 | 1714512454774 7 | 0 8 | UTF-8 9 | false 10 | 11 | SUCCESS 12 | 16 | 17 | PERFORMANCE_OPTIMIZED 18 | 19 | 20 | flowNode 21 | 3116376 22 | 23 | 24 | classLoad 25 | 125882830 26 | 27 | 28 | run 29 | 80228333 30 | 31 | 32 | parse 33 | 213222375 34 | 35 | 36 | 37 | true 38 | 5 39 | 1:5 40 | 2 41 | false 42 | false 43 | 44 | false 45 | 46 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/log: -------------------------------------------------------------------------------- 1 | Started 2 | ha:////4Er24oWfBBiEN/Wa7id5o9M/o5Uqubl8YTcDNNanS4ZDAAAAoh+LCAAAAAAAAP9tjTEOwjAQBM8BClpKHuFItIiK1krDC0x8GCfWnbEdkooX8TX+gCESFVvtrLSa5wtWKcKBo5UdUu8otU4GP9jS5Mixv3geZcdn2TIl9igbHBs2eJyx4YwwR1SwULBGaj0nRzbDRnX6rmuvydanHMu2V1A5c4MHCFXMWcf8hSnC9jqYxPTz/BXAFEIGsfuclm8zQVqFvQAAAA==[Pipeline] Start of Pipeline 3 | ha:////4P/BX1JLzbsCrvx6yk49claR+ti0ovHPJGy4iulK196/AAAApR+LCAAAAAAAAP9tjTEOwjAUQ3+KOrAycohUghExsUZZOEFIQkgb/d8mKe3EibgadyBQiQlLlmxL1nu+oE4RjhQdby12HpP2vA+jK4lPFLtroIm3dOGaMFGwXNpJkrGnpUrKFhaxClYC1hZ1oOTRZdiIVt1VExS65pxj2Q4CKm8GeAAThZxVzN8yR9jeRpMIf5y/AJj7DGxXvP/86jduZBmjwAAAAA==[Pipeline] stage 4 | ha:////4JdiHwYzioWlo2ROxl2Zlo74y53NN96hzyE6iT+69Eh0AAAApR+LCAAAAAAAAP9tjTEOwjAUQ3+KOrAycoh0gA0xsUZZOEFIQkgb/d8mKe3EibgadyBQiQlLlmxL1nu+oE4RjhQdby12HpP2vA+jK4lPFLtroIm3dOGaMFGwXNpJkrGnpUrKFhaxClYC1hZ1oOTRZdiIVt1VExS65pxj2Q4CKm8GeAAThZxVzN8yR9jeRpMIf5y/AJj7DGxXvP/86jfoP95RwAAAAA==[Pipeline] { (test) 5 | ha:////4IbbNcCvGzKmOkFNSNkP4IRFXNaMMv4VDyWQd8mj5yT1AAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQTbaGWsbAmNJ0AWEZb8zwLrbuWJvJp3kLiJlZNMMm+a93rDOic4UbLcG+wdZu14DKOti0+U+lugiXu6ck2YKRguzSSpM+cFJRUDS1gDKwEbgzpQdmgLbIVXD9UGhba9lFS/o4DGdQM8gYlqLiqVL8wJdvexy4Q/z18BzLEA29ce4gdpL1fxvAAAAA==[Pipeline] sleep 6 | Sleeping for 2 min 0 sec 7 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/log-index: -------------------------------------------------------------------------------- 1 | 1231 5 2 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/workflow/flowNodeStore.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2 5 | 6 | 7 | 8 | 2 9 | 10 | 11 | 12 | 42 13 | 14 | 15 | 1714512455074 16 | 17 | 18 | 19 | 20 | 21 | 3 22 | 23 | 24 | 25 | 2 26 | 27 | 3 28 | org.jenkinsci.plugins.workflow.support.steps.StageStep 29 | 30 | 31 | 32 | 33 | 34 | 35 | name 36 | test 37 | 38 | 39 | 40 | true 41 | 42 | 43 | 1714512455144 44 | 45 | 46 | 42 47 | 48 | 49 | 50 | 51 | 52 | 4 53 | 54 | 55 | 56 | 3 57 | 58 | 4 59 | org.jenkinsci.plugins.workflow.support.steps.StageStep 60 | 61 | 62 | 63 | 64 | test 65 | 66 | 67 | 1714512455148 68 | 69 | 70 | 42 71 | 72 | 73 | 74 | 75 | 76 | 5 77 | 78 | 79 | 80 | 4 81 | 82 | 5 83 | org.jenkinsci.plugins.workflow.steps.SleepStep 84 | 85 | 86 | 87 | 88 | 89 | time 90 | 120 91 | 92 | 93 | 94 | true 95 | 96 | 97 | 1714512455155 98 | 99 | 100 | 42 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/legacyIds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/workflow-support-plugin/8b05d5d6f2a55416fa9614e011f0ab6cb85be71d/src/test/resources/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/legacyIds -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/permalinks: -------------------------------------------------------------------------------- 1 | lastSuccessfulBuild -1 2 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest/actionDeserializationShouldBeRobust/jobs/test0/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 5 | 6 | PERFORMANCE_OPTIMIZED 7 | 8 | 9 | 10 | 14 | true 15 | 16 | 17 | false 18 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest/actionDeserializationShouldBeRobust/jobs/test0/nextBuildNumber: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/BulkFlowNodeStorageTest/actionDeserializationShouldBeRobust/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test0 5 | 1 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 1714512387063 6 | 1714512387076 7 | 0 8 | UTF-8 9 | false 10 | 11 | SUCCESS 12 | 16 | 17 | MAX_SURVIVABILITY 18 | 19 | 20 | flowNode 21 | 79993754 22 | 23 | 24 | classLoad 25 | 146027039 26 | 27 | 28 | run 29 | 213084959 30 | 31 | 32 | parse 33 | 233240875 34 | 35 | 36 | saveProgram 37 | 58160417 38 | 39 | 40 | 41 | true 42 | 5 43 | 1:5 44 | 2 45 | false 46 | false 47 | 48 | false 49 | 50 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/log: -------------------------------------------------------------------------------- 1 | Started 2 | ha:////4G2Cb8Ql3KodDh/7I/MViMTdb0Ok+iChPXznMFKXf5nnAAAAoh+LCAAAAAAAAP9tjTEOwjAQBM8BClpKHuFItIiK1krDC0x8GCfWnbEdkooX8TX+gCESFVvtrLSa5wtWKcKBo5UdUu8otU4GP9jS5Mixv3geZcdn2TIl9igbHBs2eJyx4YwwR1SwULBGaj0nRzbDRnX6rmuvydanHMu2V1A5c4MHCFXMWcf8hSnC9jqYxPTz/BXAFEIGsfuclm8zQVqFvQAAAA==[Pipeline] Start of Pipeline 3 | ha:////4L/Qb4v+2UjVyAYs+x5KO+lNMbBqmeW70tIwLHQF+Pl/AAAApR+LCAAAAAAAAP9tjTEOwjAUQ3+KOrAycohUghExsUZZOEFIQkgb/d8mKe3EibgadyBQiQlLlmxL1nu+oE4RjhQdby12HpP2vA+jK4lPFLtroIm3dOGaMFGwXNpJkrGnpUrKFhaxClYC1hZ1oOTRZdiIVt1VExS65pxj2Q4CKm8GeAAThZxVzN8yR9jeRpMIf5y/AJj7DGxXvP/86jduZBmjwAAAAA==[Pipeline] stage 4 | ha:////4PUYO2bP3JVlngD9/Z26U1Kp8eR503A4GpCiKJSd4gG5AAAApR+LCAAAAAAAAP9tjTEOwjAUQ3+KOrAycoh0gA0xsUZZOEFIQkgb/d8mKe3EibgadyBQiQlLlmxL1nu+oE4RjhQdby12HpP2vA+jK4lPFLtroIm3dOGaMFGwXNpJkrGnpUrKFhaxClYC1hZ1oOTRZdiIVt1VExS65pxj2Q4CKm8GeAAThZxVzN8yR9jeRpMIf5y/AJj7DGxXvP/86jfoP95RwAAAAA==[Pipeline] { (test) 5 | ha:////4KStXP4gq7RkmGlqJhQHWcAXOAjXKQoejbxWDwwkJuwBAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQTbaGWsbAmNJ0AWEZb8zwLrbuWJvJp3kLiJlZNMMm+a93rDOic4UbLcG+wdZu14DKOti0+U+lugiXu6ck2YKRguzSSpM+cFJRUDS1gDKwEbgzpQdmgLbIVXD9UGhba9lFS/o4DGdQM8gYlqLiqVL8wJdvexy4Q/z18BzLEA29ce4gdpL1fxvAAAAA==[Pipeline] sleep 6 | Sleeping for 2 min 0 sec 7 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/log-index: -------------------------------------------------------------------------------- 1 | 1231 5 2 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/program.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/workflow-support-plugin/8b05d5d6f2a55416fa9614e011f0ab6cb85be71d/src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/program.dat -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/workflow/2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 2 6 | 7 | 8 | 9 | 42 10 | 11 | 12 | 1714512387423 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/workflow/3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 2 6 | 7 | 3 8 | org.jenkinsci.plugins.workflow.support.steps.StageStep 9 | 10 | 11 | 12 | 13 | 14 | 15 | name 16 | test 17 | 18 | 19 | 20 | true 21 | 22 | 23 | 1714512387527 24 | 25 | 26 | 42 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/workflow/4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 3 6 | 7 | 4 8 | org.jenkinsci.plugins.workflow.support.steps.StageStep 9 | 10 | 11 | 12 | 13 | test 14 | 15 | 16 | 1714512387546 17 | 18 | 19 | 42 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/1/workflow/5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 4 6 | 7 | 5 8 | org.jenkinsci.plugins.workflow.steps.SleepStep 9 | 10 | 11 | 12 | 13 | 14 | time 15 | 120 16 | 17 | 18 | 19 | true 20 | 21 | 22 | 1714512387626 23 | 24 | 25 | 42 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/legacyIds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/workflow-support-plugin/8b05d5d6f2a55416fa9614e011f0ab6cb85be71d/src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/legacyIds -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/builds/permalinks: -------------------------------------------------------------------------------- 1 | lastSuccessfulBuild -1 2 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 5 | 6 | MAX_SURVIVABILITY 7 | 8 | 9 | 10 | 14 | true 15 | 16 | 17 | false 18 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/jobs/test0/nextBuildNumber: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/storage/SimpleXStreamStorageTest/actionDeserializationShouldBeRobust/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test0 5 | 1 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/workflow/support/visualization/table/FlowGraphTableTest/corruptedFlowGraph.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/workflow-support-plugin/8b05d5d6f2a55416fa9614e011f0ab6cb85be71d/src/test/resources/org/jenkinsci/plugins/workflow/support/visualization/table/FlowGraphTableTest/corruptedFlowGraph.zip --------------------------------------------------------------------------------