Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
5f996d5
Initial implementation of predicate-based flow scanner and an increme…
svanoort Apr 14, 2016
ddb608d
Pacify FindBugs
svanoort Apr 14, 2016
2b51497
Tests for the unrefactored FlowScanner
svanoort Apr 18, 2016
bb0864e
WIP on pulling out generalized flowscanning algorithms
svanoort Apr 18, 2016
f4de174
Flesh out/fix most of the flow scanner implementations
svanoort Apr 19, 2016
d414723
Finish tests for FlowScanner algorithms and fix remaining edge cases
svanoort Apr 19, 2016
4c8faa4
Add visitor method to FlowScanner for collecting stats while walking …
svanoort Apr 19, 2016
eaf4769
Fix FindBugs complaints
svanoort Apr 19, 2016
0ed42f8
Flesh out incremental analysis cache skeleton
svanoort Apr 19, 2016
7f4d488
Move FlowScanner method to search by StepDescriptor into test only to…
svanoort Apr 19, 2016
deb585b
Rename findAllMatches to filter
svanoort Apr 25, 2016
72e49df
Refactor flow graph incremental analysis, add stub of a test until I …
svanoort Apr 25, 2016
eb48552
Fix imports in incremental flow analysis
svanoort Apr 25, 2016
e640fd3
Refactor the incremental flow analysis to allow testing by running in…
svanoort Apr 26, 2016
7c1e6df
Remove the incremental flow analysis so we can push it into a separat…
svanoort Apr 26, 2016
6dfbb2d
Start a ForkFlowScanner (WIP), add lots of Javadoc/comments, rename B…
svanoort Apr 27, 2016
ae9581b
Syntactic sugar and comments on FlowScanner
svanoort Apr 27, 2016
1331ddd
FlowScanner: switch to simpler iterator use internally, add filterato…
svanoort Apr 30, 2016
a307266
More AbstractFlowScanner helpers, add iterator on block starts, add t…
svanoort May 2, 2016
211f297
FlowScanning: ForkScanner passes in basic case, next up is testing ne…
svanoort May 2, 2016
c714502
Add standard gitignore items
svanoort May 3, 2016
94857ac
Test ForkScanner blacklisting with parallel branches & fix issues
svanoort May 3, 2016
94845a1
Refactor FlowScanner & affiliated classes to be prepare for review
svanoort May 3, 2016
f4ebc6e
Massive refactor: give graph analysis its own package, and split the …
svanoort May 3, 2016
8e8ddfb
Rename graphanalysis test package
svanoort May 3, 2016
b90e878
Optimize the DepthFirstWalker by non tracking non-BlockStart nodes
svanoort May 3, 2016
b44f402
Save the WIP for ForkScanner with multiple heads
svanoort May 17, 2016
366e080
Add explanation header to AbstractFlowScanner
svanoort May 17, 2016
233107f
Clean up from initial review: add and fix javadocs, refactor some fie…
svanoort May 17, 2016
2bf2829
Small changes from review, and @NonNull annotations on FlowNode
svanoort May 17, 2016
733ab4a
More changes from review
svanoort May 17, 2016
7c31794
Refactor and fixes from last refactor
svanoort May 18, 2016
a1ceb70
Finish stripping down the ForkScanner and the core least-common-ancestor
svanoort May 18, 2016
11be993
Somewhat better behaved version of ForkScanner, just need to fix blac…
svanoort May 18, 2016
b1f0976
Fix blacklisting, remove unused fields of ForkScanner objects
svanoort May 19, 2016
e598a91
Add suggested review API, add a missing NotNull, explain the null han…
svanoort May 24, 2016
7173c9c
LinearFlowScanner test is deterministic
svanoort May 24, 2016
2707059
Add megatest for FlowScanner abstract functionality
svanoort May 25, 2016
21c6623
Harden the flowscanner tests for ordering, clean up
svanoort May 25, 2016
13ec682
Refactor forkscanner, create dedicated tests, fix splitting
svanoort May 25, 2016
4ee5cec
WIP for ForkScanner - tests and 1 fix for leastCommonAncestor
svanoort May 25, 2016
6e28d68
Fix the remaining issues with LeastCommonAncestor core & add comments
svanoort May 31, 2016
0cddee1
Fix unused imports
svanoort May 31, 2016
09a5d21
Fix remaining bugs in ForkScanner, add test, and clean up docs/commme…
svanoort May 31, 2016
5a77748
Tidy up and HTML format the graph analysis javadocs
svanoort May 31, 2016
8bb6e6a
More javadocs + package annotation for graphanalysis
svanoort May 31, 2016
10e2eb4
For graph analysis add a FlowChunk object to use in analysis APIs
svanoort Jun 7, 2016
389543b
For flow scanning, add an in-memory representation of timed blocks
svanoort Jun 8, 2016
bd426b3
Add a parallel flow chunk for duration computation
svanoort Jun 8, 2016
2efcba4
Define interface for chunk storage
svanoort Jun 9, 2016
8ebbdc9
Save simple block visitor
svanoort Jun 10, 2016
f97a08f
Basic forkscanner block API
svanoort Jun 10, 2016
13ef687
Add status handling and memory storage engine to graph analysis
svanoort Jun 10, 2016
094cb36
Carve out advanced visitor methods
svanoort Jun 10, 2016
2116580
Advanced visitor for carving up flows (skeletal impl) and parallel ha…
svanoort Jun 10, 2016
698d0d3
Chunk and block storage types
svanoort Jul 24, 2016
b8ac909
Merge branch 'master' into block-scanning-APIs
svanoort Jul 24, 2016
64ec619
Customize SNAPSHOT version so we can distinguish this from master sna…
svanoort Jul 24, 2016
9efad20
Remove all the parts not needed for raw block scanning
svanoort Aug 5, 2016
1338b64
Formalize the block visitor API with parallels and branches yet again
svanoort Aug 5, 2016
6c4b8e3
Harden ChunkVisitor and start a FlowChunker
svanoort Aug 5, 2016
0237e96
Forkscanner now tracks state that shows what kind of nodes we have (a…
svanoort Aug 8, 2016
abdf296
When splitting chunks you need to know previous node for inclusive vs…
svanoort Aug 10, 2016
a955451
Add some peeking methods to ForkScanner and also a few small access/f…
svanoort Aug 10, 2016
6eb3705
Finish the internal SimpleChunkVisitor iteration logic in ForkScanner…
svanoort Aug 10, 2016
ac2609d
Remove the useless peek methods
svanoort Aug 10, 2016
e8e6203
Fix test failure
svanoort Aug 10, 2016
b5c2b9d
More small fixes
svanoort Aug 10, 2016
866e21d
Test visitor and tests for visitor iteration in ForkScanner
svanoort Aug 11, 2016
cd6e3dd
Soften requirements for MemoryFlowChunk
svanoort Aug 11, 2016
84656e5
Remove unused StandardSimpleChunkVisitor for now
svanoort Aug 11, 2016
55dc020
Add pause timing to MemoryFlowChunk
svanoort Aug 12, 2016
4af21f5
Fixes and test fixes for simple visitor parallels
svanoort Aug 12, 2016
1627155
Add standard visitor and convenience methods for forkscanner/simpleCh…
svanoort Aug 12, 2016
e1988cd
Cleanup for reviews
svanoort Aug 12, 2016
606de64
Merge branch 'master' into generic-flow-analyzer
svanoort Aug 12, 2016
c9f2444
Merge branch 'generic-flow-analyzer' into block-scanning-APIs
svanoort Aug 12, 2016
a53b13e
Remove dangling file
svanoort Aug 12, 2016
aa18ee5
Oh yeah I meant to change the versioning
svanoort Aug 12, 2016
f22aba6
Better reset the pause time for StandardChunkVisitor
svanoort Aug 12, 2016
9880dc8
Rename finder and add the stagefinder that I removed earlier
svanoort Aug 12, 2016
700d009
Remove unused BlockVisitor from early prototypes
svanoort Aug 12, 2016
586979c
Fix minor oopsy
svanoort Aug 12, 2016
7bb581c
Remove StageChunkFinder because BlockScopedStages need some additiona…
svanoort Aug 15, 2016
7b9faca
Cleanup author annotation misformatting for javadoc
svanoort Aug 17, 2016
ae47cc2
Make all paragraph tags in javadocs not self-closing to placate javadocs
svanoort Aug 17, 2016
d21d67a
Placate more javadocs complaints
svanoort Aug 17, 2016
1678b7d
Fix more javadocs complaints and silence doclint nonsense
svanoort Aug 17, 2016
2030360
More exhaustive tests for SimpleVisitor and fix a pair of swapped arg…
svanoort Aug 19, 2016
6b6eba7
Address review comments from @oleg-nenashev
svanoort Aug 19, 2016
33e079e
Fix an off-by-one case in ForkScanner with parallels and add more ext…
svanoort Aug 21, 2016
56e42eb
Restrive access to some of ForkScanner internals
svanoort Aug 21, 2016
94e9169
Remove unused totalBranches field in ForkScanner
svanoort Aug 21, 2016
6cd4215
Address review comments from @oleg-nenashev
svanoort Aug 23, 2016
9d05aa6
Apply a few small review changes
svanoort Aug 23, 2016
d332d8c
Re-enable linting just to see how much pain there is
svanoort Aug 23, 2016
9067f00
Javadocs update
svanoort Aug 24, 2016
8ed03ee
Fix more JavaDocs formatting due to DocLint being a jerk.
svanoort Aug 24, 2016
80b7ad4
Fix yet more doclint whining
svanoort Aug 24, 2016
cf4d5ac
OMG doclint really
svanoort Aug 24, 2016
4f1575c
Address review comments, mostly javadocs
svanoort Aug 24, 2016
0bc9ffe
Merge branch 'block-scanning-APIs' into generic-flow-analyzer
svanoort Aug 24, 2016
4cc9206
Javadoc error.
jglick Aug 22, 2016
2bcfd8b
Solved a javadoc hiccup
svanoort Aug 24, 2016
34483c3
Remove accidentally commit merge files
svanoort Aug 24, 2016
78e8d2c
Revert special versioning in prep for release
svanoort Aug 24, 2016
8abdf34
Fix a missing DepthFirstScanner reset of field
svanoort Aug 24, 2016
3d3bf57
Fix and thoroughly test against a bug with ForkScanner failing when h…
svanoort Aug 25, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -38,7 +39,6 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;

/**
* Core APIs and base logic for FlowScanners that extract information from a pipeline execution.
Expand Down Expand Up @@ -78,7 +78,7 @@
* <ul>
* <li>Implement a {@link FlowNodeVisitor} that collects metrics from each FlowNode visited, and call visitAll to extract the data.</li>
* <li>Find all flownodes of a given type (ex: stages), using {@link #filteredNodes(Collection, Collection, Predicate)}</li>
* <li>Find the first node with an Error before a specific node</li>
* <li>Find the first node with an {@link org.jenkinsci.plugins.workflow.actions.ErrorAction} before a specific node</li>
* <li>Scan through all nodes *just* within a block
* <ul>
* <li>Use the {@link org.jenkinsci.plugins.workflow.graph.BlockEndNode} as the head</li>
Expand All @@ -88,6 +88,7 @@
*
* @author <samvanoort@gmail.com>Sam Van Oort</samvanoort@gmail.com>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong HTML tags?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oleg-nenashev ? Likely fixed in #10 anyway.

*/
@NotThreadSafe
public abstract class AbstractFlowScanner implements Iterable <FlowNode>, Filterator<FlowNode> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, this is simultaneously an Iterable and an Iterator? Confusing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, it's weird: these map most closely to (reusable via reinitialization) Iterators, but implementing Iterable allows you to use them in for-each looks, which is a nice piece of syntactic sugar to assist users.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I would just make it only Iterable, get rid of reset, and create separate objects for the Iterator. Less confusing, no risk of accidentally resetting something still in use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree: the result of doing that would be even more confusing and would render the FlowScanners non-reusable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

render the FlowScanners non-reusable

Not if the Iterator were also abstract.

I still think conflating Iterable with Iterator is a confusing and unnecessary design decision.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐜 cf. now-hidden discussion about the inadvisability of making an Iterable be its own Iterator


protected FlowNode myCurrent;
Expand Down Expand Up @@ -235,15 +236,16 @@ public Filterator<FlowNode> filter(@Nonnull Predicate<FlowNode> filterCondition)
* Find the first FlowNode within the iteration order matching a given condition
* Includes null-checking on arguments to allow directly calling with unchecked inputs (simplifies use).
* @param heads Head nodes to start walking from
* @param endNodes
* @param blackListNodes Nodes that are never visited, search stops here (bound is exclusive).
* If you want to create an inclusive bound, just use a node's parents.
* @param matchCondition Predicate to match when we've successfully found a given node type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐜 document that the input argument to apply may not be null

* @return First matching node, or null if no matches found
*/
@CheckForNull
public FlowNode findFirstMatch(@CheckForNull Collection<FlowNode> heads,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CheckForNull

@CheckForNull Collection<FlowNode> endNodes,
@CheckForNull Collection<FlowNode> blackListNodes,
Predicate<FlowNode> matchCondition) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nonnull

if (!setup(heads, endNodes)) {
if (!setup(heads, blackListNodes)) {
return null;
}

Expand Down Expand Up @@ -283,7 +285,7 @@ public FlowNode findFirstMatch(@CheckForNull FlowExecution exec, @Nonnull Predic
* Includes null-checking on arguments to allow directly calling with unchecked inputs (simplifies use).
* @param heads Nodes to start iterating backward from by visiting their parents.
* @param blackList Nodes we may not visit or walk beyond.
* @param matchCondition Predicate that must be met for nodes to be included in output.
* @param matchCondition Predicate that must be met for nodes to be included in output. Input is always non-null.
* @return List of flownodes matching the predicate.
*/
@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.jenkinsci.plugins.workflow.graph.FlowNode;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.HashSet;
Expand All @@ -42,6 +43,7 @@
* <p/> The behavior is analogous to {@link org.jenkinsci.plugins.workflow.graph.FlowGraphWalker} but faster.
* @author <samvanoort@gmail.com>Sam Van Oort</samvanoort@gmail.com>
*/
@NotThreadSafe
public class DepthFirstScanner extends AbstractFlowScanner {

protected ArrayDeque<FlowNode> queue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐜 generally classes like this should be final and their fields private; if and when there is ever a need to subclass this (delegating seems more plausible), it is unlikely that having subclasses mess around with these fields directly would be wise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Actually it better still to make the whole class package-private, and just offer a factory method.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree now, I think. In practice the benefits of being able to reuse the scanners across multiple runs probably will not be realized.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jglick To be clear: factory methods could have been a smart refactor, and easy enough to do if this review and feedback had been provided on a reasonable timeframe. Unfortunately that timeframe has long passed, and it is too late to go down that path.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
import com.google.common.base.Predicate;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.Iterator;

/** Filters an iterator against a match predicate by wrapping an iterator
* @author <samvanoort@gmail.com>Sam Van Oort</samvanoort@gmail.com>
*/
@NotThreadSafe
class FilteratorImpl<T> implements Filterator<T> {
private boolean hasNext = false;
private T nextVal = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,10 @@
import org.jenkinsci.plugins.workflow.actions.LogAction;
import org.jenkinsci.plugins.workflow.actions.StageAction;
import org.jenkinsci.plugins.workflow.actions.WorkspaceAction;
import org.jenkinsci.plugins.workflow.graph.BlockEndNode;
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;

import javax.annotation.Nonnull;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
* Library of common functionality when analyzing/walking flow graphs
Expand All @@ -57,7 +52,7 @@ private FlowScanningUtils() {}
* @return Predicate that will match when FlowNode has the action given
*/
@Nonnull
public static Predicate<FlowNode> nodeHasActionPredicate(@Nonnull final Class<? extends Action> actionClass) {
public static Predicate<FlowNode> hasActionPredicate(@Nonnull final Class<? extends Action> actionClass) {
return new Predicate<FlowNode>() {
@Override
public boolean apply(FlowNode input) {
Expand All @@ -67,11 +62,6 @@ public boolean apply(FlowNode input) {
}

// Default predicates, which may be used for common conditions
public static final Predicate<FlowNode> MATCH_HAS_LABEL = nodeHasActionPredicate(LabelAction.class);
public static final Predicate<FlowNode> MATCH_IS_STAGE = nodeHasActionPredicate(StageAction.class);
public static final Predicate<FlowNode> MATCH_HAS_WORKSPACE = nodeHasActionPredicate(WorkspaceAction.class);
public static final Predicate<FlowNode> MATCH_HAS_ERROR = nodeHasActionPredicate(ErrorAction.class);
public static final Predicate<FlowNode> MATCH_HAS_LOG = nodeHasActionPredicate(LogAction.class);
public static final Predicate<FlowNode> MATCH_BLOCK_START = (Predicate)Predicates.instanceOf(BlockStartNode.class);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -61,6 +62,7 @@
*
* @author <samvanoort@gmail.com>Sam Van Oort</samvanoort@gmail.com>
*/
@NotThreadSafe
public class ForkScanner extends AbstractFlowScanner {

// Last element in stack is end of myCurrent parallel start, first is myCurrent start
Expand All @@ -82,7 +84,8 @@ protected void reset() {
myNext = null;
}

/** If true, we are walking from the flow end node and have a complete view of the flow */
/** If true, we are walking from the flow end node and have a complete view of the flow
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐜 add period after summary sentence

* Needed because there are implications when not walking from a finished flow (blocks without a {@link BlockEndNode})*/
public boolean isWalkingFromFinish() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isWalkingFromEnd since we have EndNodes instead of FinishNodes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are implications when you're dealing with an unfinished flow (incomplete blocks, for example, potentially unclosed parallels).

return walkingFromFinish;
}
Expand Down Expand Up @@ -264,7 +267,7 @@ ArrayDeque<ParallelBlockStart> leastCommonAncestor(@Nonnull Set<FlowNode> heads)

while (itIterator.hasNext()) {
Filterator<FlowNode> blockStartIterator = itIterator.next();
FlowPiece myPiece = pieceIterator.next();
FlowPiece myPiece = pieceIterator.next(); //Safe because we always remove/add with both iterators at once

// Welp we hit the end of a branch
if (!blockStartIterator.hasNext()) {
Expand Down Expand Up @@ -316,7 +319,6 @@ ArrayDeque<ParallelBlockStart> leastCommonAncestor(@Nonnull Set<FlowNode> heads)
@Override
protected void setHeads(@Nonnull Collection<FlowNode> heads) {
if (heads.size() > 1) {
//throw new IllegalArgumentException("ForkedFlowScanner can't handle multiple head nodes yet");
parallelBlockStartStack = leastCommonAncestor(new LinkedHashSet<FlowNode>(heads));
currentParallelStart = parallelBlockStartStack.pop();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And what if we have an empty parallel block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think should be okay, but adding a testcase.

Copy link
Member

@oleg-nenashev oleg-nenashev Aug 24, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I see leastCommonAncestor() may return empty deque if heads consist of a single Fork node without following items. In such case there will be runtime exception.

@svanoort Could you please verify since the code is not so easy for reading?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oleg-nenashev Verified this happens via test. leastCommonAncestor loses its mind here completely -- could have sworn we had a test for it, but now we do and a fix in a few min.

currentParallelStartNode = currentParallelStart.forkStart;
Expand Down Expand Up @@ -415,8 +417,8 @@ protected FlowNode next(@Nonnull FlowNode current, @Nonnull Collection<FlowNode>

// First we look at the parents of the current node if present
List<FlowNode> parents = current.getParents();
if (parents == null || parents.size() == 0) {
// welp done with this node, guess we consult the queue?
if (parents.isEmpty()) {
// welp, we're done with this node, guess we consult the queue?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} else if (parents.size() == 1) {
FlowNode p = parents.get(0);
if (p == currentParallelStartNode) {
Expand All @@ -436,7 +438,7 @@ protected FlowNode next(@Nonnull FlowNode current, @Nonnull Collection<FlowNode>
return possibleOutput;
}
} else {
throw new IllegalStateException("Found a FlowNode with multiple parents that isn't the end of a block! "+ this.myCurrent.toString());
throw new IllegalStateException("Found a FlowNode with multiple parents that isn't the end of a block! "+ this.myCurrent);
}

if (currentParallelStart != null && currentParallelStart.unvisited.size() > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.Collection;
import java.util.List;

Expand All @@ -49,11 +50,12 @@
* <ul>
* <li>Finding out the executor workspace used to run a FlowNode</li>
* <li>Finding the start of the parallel block enclosing the current node</li>
* <li>Locating the label applying to a given FlowNode (if any)</li>
* <li>Locating the label applying to a given FlowNode (if any) if using labelled blocks</li>
* </ul>
*
* @author <samvanoort@gmail.com>Sam Van Oort</samvanoort@gmail.com>
*/
@NotThreadSafe
public class LinearBlockHoppingScanner extends LinearScanner {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.jenkinsci.plugins.workflow.graph.FlowNode;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
Expand All @@ -40,8 +41,10 @@
* <p/>Use case: we don't care about parallel branches or know they don't exist, we just want to walk through the top-level blocks.
*
* <p/>This is the fastest & simplest way to walk a flow, because you only care about a single node at a time.
* Nuance: where there are multiple parent nodes (in a parallel block), and one is blacklisted, we'll find the first non-blacklisted one.
* @author <samvanoort@gmail.com>Sam Van Oort</samvanoort@gmail.com>
*/
@NotThreadSafe
public class LinearScanner extends AbstractFlowScanner {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

package org.jenkinsci.plugins.workflow.graphanalysis;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
Expand All @@ -43,6 +45,7 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

// Slightly dirty but it removes a ton of FlowTestUtils.* class qualifiers
Expand Down Expand Up @@ -131,6 +134,7 @@ public void setUp() throws Exception {
"}\n" +
"steps['2'] = {\n" +
" echo '2a'\n" +
" echo '2b'\n" +
" def nested = [:]\n" +
" nested['2-1'] = {\n" +
" echo 'do 2-1'\n" +
Expand All @@ -139,7 +143,6 @@ public void setUp() throws Exception {
" sleep 1\n" +
" echo '2 section 2'\n" +
" }\n" +
" echo '2b'\n" +
" parallel nested\n" +
"}\n" +
"parallel steps\n" +
Expand Down Expand Up @@ -280,6 +283,20 @@ public void testFlowSegmentSplit() throws Exception {
Assert.assertEquals(sideBranch, nodeMap.get(exec.getNode("7")));
}

@Test
public void testEmptyParallel() throws Exception {
WorkflowJob job = r.jenkins.createProject(WorkflowJob.class, "EmptyParallel");
job.setDefinition(new CpsFlowDefinition(
"parallel 'empty1': {}, 'empty2':{} \n" +
"echo 'done' "
));
WorkflowRun b = r.assertBuildStatusSuccess(job.scheduleBuild2(0));
ForkScanner scan = new ForkScanner();

List<FlowNode> outputs = scan.filteredNodes(b.getExecution().getCurrentHeads(), (Predicate) Predicates.alwaysTrue());
Assert.assertEquals(9, outputs.size());
}

/** Reference the flow graphs in {@link #SIMPLE_PARALLEL_RUN} and {@link #NESTED_PARALLEL_RUN} */
@Test
public void testLeastCommonAncestor() throws Exception {
Expand Down