This is an automated email from the ASF dual-hosted git repository. kenhuuu pushed a commit to branch TINKERPOP-3200 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 8b09f7a449adfdb785601383e9fd209c4f104aac Author: Ken Hu <[email protected]> AuthorDate: Tue Oct 14 15:31:47 2025 -0700 TINKERPOP-3200 Made repeat() act as a global parent Co-authored-by: Cole-Greer <[email protected]> --- CHANGELOG.asciidoc | 1 + docs/src/dev/provider/gremlin-semantics.asciidoc | 5 +- docs/src/upgrade/release-3.8.x.asciidoc | 185 +++++++++++++++++++++ .../process/traversal/step/branch/RepeatStep.java | 43 +++-- .../gremlin/test/features/branch/Repeat.feature | 181 +++++++++++++++++++- .../gremlin/test/features/integrated/Paths.feature | 13 +- 6 files changed, 404 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index dde7953b08..e457b44e41 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -95,6 +95,7 @@ This release also includes changes from <<release-3-7-XXX, 3.7.XXX>>. * Removed the `@RemoteOnly` testing tag in Gherkin as lambda tests have all been moved to the Java test suite. * Updated gremlin-javascript to use GraphBinary as default instead of GraphSONv3 * Added the `asNumber()` step to perform number conversion. +* Changed `repeat()` to make `repeatTraversal` global rather than a mix of local and global. * Renamed many types in the grammar for consistent use of terms "Literal", "Argument", and "Varargs" * Changed `gremlin-net` so that System.Text.Json is only listed as an explicit dependency when it is not available from the framework. * Fixed translation of numeric literals for Go losing type definitions diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc b/docs/src/dev/provider/gremlin-semantics.asciidoc index dff6cda769..8df5684db6 100644 --- a/docs/src/dev/provider/gremlin-semantics.asciidoc +++ b/docs/src/dev/provider/gremlin-semantics.asciidoc @@ -1974,7 +1974,7 @@ link:https://tinkerpop.apache.org/docs/x.y.z/reference/#project-step[reference] [[repeat-step]] === repeat() -*Description:* Iteratively applies a traversal (the "loop body") to each incoming traverser until a stopping +*Description:* Iteratively applies a traversal (the "loop body") to all incoming traversers until a stopping condition is met. Optionally, it can emit traversers on each iteration according to an emit predicate. The repeat step supports loop naming and a loop counter via `loops()`. @@ -2015,6 +2015,9 @@ predicates are evaluated before the first iteration (pre) or after each iteratio `do/while` semantics respectively: - Pre-check / pre-emit: when the modulator appears before `repeat(...)`. - Post-check / post-emit: when the modulator appears after `repeat(...)`. +- Global traversal scope: The `repeatTraversal` is a global child. This means all traversers entering the repeat body +are processed together as a unified stream with global semantics. `Barrier` (`order()`, `sample()`, etc.) steps within +the repeat traversal operate across all traversers collectively rather than in isolation per traverser. - Loop counter semantics: - The loop counter for a given named or unnamed repeat is incremented once per completion of the loop body (i.e., after the body finishes), not before. Therefore, `loops()` reflects the number of completed iterations. diff --git a/docs/src/upgrade/release-3.8.x.asciidoc b/docs/src/upgrade/release-3.8.x.asciidoc index 65b5e6a71b..fd2bfed87f 100644 --- a/docs/src/upgrade/release-3.8.x.asciidoc +++ b/docs/src/upgrade/release-3.8.x.asciidoc @@ -220,6 +220,135 @@ gremlin> g.inject([Float.MAX_VALUE, Float.MAX_VALUE], [Double.MAX_VALUE, Double. See link:https://issues.apache.org/jira/browse/TINKERPOP-3115[TINKERPOP-3115] +==== repeat() Step Global Children Semantics Change + +The `repeat()` step has been updated to treat the repeat traversal as a global child in all cases. Previously, the +repeat traversal behaved as a hybrid between local and global semantics, which could lead to unexpected results in +certain scenarios. The repeat traversal started off as a local child but as traversers were added back per iteration, +it behaved more like a global child. + +With this change, the repeat traversal now consistently operates with global semantics, meaning that all traversers +are processed together rather than being processed per traverser. This provides more predictable behavior and aligns +with the semantics of other steps. + +[source,text] +---- +// In 3.7.x and earlier, the order would be local to the first traverser. +// Notice how the results are grouped by marko, then vadas, then lop +gremlin> g.withoutStrategies(RepeatUnrollStrategy).V(1, 2, 3). +......1> repeat(both().simplePath().order().by("name")).times(2).path().by("name") +==>[marko,lop,josh] +==>[marko,josh,lop] +==>[marko,lop,peter] +==>[marko,josh,ripple] +==>[vadas,marko,josh] +==>[vadas,marko,lop] +==>[lop,marko,josh] +==>[lop,josh,marko] +==>[lop,josh,ripple] +==>[lop,marko,vadas] + +// In 3.8.0, the aggregate now consistently uses global semantics +// The traversers are now ordered so the traversers from the final iteration are ordered first then by +// the traversers from previous iterations +gremlin> g.withoutStrategies(RepeatUnrollStrategy).V(1, 2, 3). +......1> repeat(both().simplePath().order().by("name")).times(2).path().by("name") +==>[marko,lop,josh] +==>[vadas,marko,josh] +==>[lop,marko,josh] +==>[marko,josh,lop] +==>[vadas,marko,lop] +==>[lop,josh,marko] +==>[marko,lop,peter] +==>[marko,josh,ripple] +==>[lop,josh,ripple] +==>[lop,marko,vadas] +---- + +This change may affect traversals that relied on the previous hybrid behavior, particularly those using side effects +or barrier steps within `repeat()`. Review any traversals using `repeat()` with steps like `aggregate()`, `store()`, +or other barrier steps to ensure they produce the expected results. Note that there is no way to exactly replicate the +old behavior anymore. The following examples show why: + +[source,text] +---- +// In 3.7.x +gremlin> g.V().repeat(both().order().by("name")).times(1).path() +==>[v[1],v[4]] +==>[v[1],v[3]] +==>[v[1],v[2]] +==>[v[2],v[1]] +==>[v[3],v[4]] +==>[v[3],v[1]] +==>[v[3],v[6]] +==>[v[4],v[3]] +==>[v[4],v[1]] +==>[v[4],v[5]] +==>[v[5],v[4]] +==>[v[6],v[3]] + +// In 3.8.0, if there is a single iteration then adding a local() can give the same result +gremlin> g.V().repeat(local(both().order().by("name"))).times(1).path() +==>[v[1],v[4]] +==>[v[1],v[3]] +==>[v[1],v[2]] +==>[v[2],v[1]] +==>[v[3],v[4]] +==>[v[3],v[1]] +==>[v[3],v[6]] +==>[v[4],v[3]] +==>[v[4],v[1]] +==>[v[4],v[5]] +==>[v[5],v[4]] +==>[v[6],v[3]] + + +// In 3.7.x +gremlin> g.V().repeat(local(both().simplePath().order().by("name"))).times(2).path() +==>[v[1],v[3],v[4]] +==>[v[1],v[4],v[3]] +==>[v[1],v[3],v[6]] +==>[v[1],v[4],v[5]] +==>[v[2],v[1],v[4]] +==>[v[2],v[1],v[3]] +==>[v[3],v[1],v[4]] +==>[v[3],v[4],v[1]] +==>[v[3],v[4],v[5]] +==>[v[3],v[1],v[2]] +==>[v[4],v[1],v[3]] +==>[v[4],v[3],v[1]] +==>[v[4],v[3],v[6]] +==>[v[4],v[1],v[2]] +==>[v[5],v[4],v[3]] +==>[v[5],v[4],v[1]] +==>[v[6],v[3],v[4]] +==>[v[6],v[3],v[1]] + +// In 3.8.0, if there are multiple iterations then the local() will affect each iteration which gives different results +// than in 3.7.x (shown above) +gremlin> g.V().repeat(local(both().simplePath().order().by("name"))).times(2).path() +==>[v[1],v[4],v[3]] +==>[v[1],v[4],v[5]] +==>[v[1],v[3],v[4]] +==>[v[1],v[3],v[6]] +==>[v[2],v[1],v[4]] +==>[v[2],v[1],v[3]] +==>[v[3],v[4],v[1]] +==>[v[3],v[4],v[5]] +==>[v[3],v[1],v[4]] +==>[v[3],v[1],v[2]] +==>[v[4],v[3],v[1]] +==>[v[4],v[3],v[6]] +==>[v[4],v[1],v[3]] +==>[v[4],v[1],v[2]] +==>[v[5],v[4],v[3]] +==>[v[5],v[4],v[1]] +==>[v[6],v[3],v[4]] +==>[v[6],v[3],v[1]] +---- + +See: link:https://issues.apache.org/jira/browse/TINKERPOP-3200[TINKERPOP-3200] + ==== Prefer OffsetDateTime The default implementation for date type in Gremlin is now changed from the `java.util.Date` to the more encompassing @@ -927,6 +1056,62 @@ The `ChooseStep` now provides a `ChooseSemantics` enum which helps indicate if t See: link:https://issues.apache.org/jira/browse/TINKERPOP-3178[TINKERPOP-3178] +===== repeat() Step Global Children Semantics Change + +The `RepeatStep` has been updated to consistently treat the repeat traversal as a global child rather than using +hybrid local/global semantics. This change affects how the repeat traversal processes traversers and interacts with +the parent traversal. + +Previously, `RepeatStep` would start with local semantics for the first iteration and then switch to global semantics +for the subsequent iterations, which created inconsistencies in how side effects and barriers behaved within the repeat +traversal. The biggest change will be to `Barrier` steps in the repeat traversal as they will now have access to all +the starting traversers. + +[source,text] +---- +// In 3.7.x and earlier, the order would be local to the first traverser. +// Notice how the results are grouped by marko, then vadas, then lop +gremlin> g.withoutStrategies(RepeatUnrollStrategy).V(1, 2, 3). +......1> repeat(both().simplePath().order().by("name")).times(2).path().by("name") +==>[marko,lop,josh] +==>[marko,josh,lop] +==>[marko,lop,peter] +==>[marko,josh,ripple] +==>[vadas,marko,josh] +==>[vadas,marko,lop] +==>[lop,marko,josh] +==>[lop,josh,marko] +==>[lop,josh,ripple] +==>[lop,marko,vadas] + +// In 3.8.0, the aggregate now consistently uses global semantics +// The traversers are now ordered so the traversers from the final iteration are ordered first then by +// the traversers from previous iterations +gremlin> g.withoutStrategies(RepeatUnrollStrategy).V(1, 2, 3). +......1> repeat(both().simplePath().order().by("name")).times(2).path().by("name") +==>[marko,lop,josh] +==>[vadas,marko,josh] +==>[lop,marko,josh] +==>[marko,josh,lop] +==>[vadas,marko,lop] +==>[lop,josh,marko] +==>[marko,lop,peter] +==>[marko,josh,ripple] +==>[lop,josh,ripple] +==>[lop,marko,vadas] +---- + +Providers implementing custom optimizations or strategies around `RepeatStep` should verify that their +implementations account for the repeat traversal being a global child. This particularly affects: + +- Strategies that analyze or transform repeat traversals +- Optimizations that depend on the scope semantics of child traversals + +The last point about optimizations may be particularly important for providers that have memory constraints as this +change may bring about higher memory usage due to more traversers needing to be held in memory. + +See: link:https://issues.apache.org/jira/browse/TINKERPOP-3200[TINKERPOP-3200] + ===== Prefer OffsetDateTime The default implementation for date type in Gremlin is now changed from the deprecated `java.util.Date` to the more diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatStep.java index 17b8a2ef4e..2abbbc87cc 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatStep.java @@ -21,9 +21,12 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.branch; import org.apache.tinkerpop.gremlin.process.traversal.Step; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.Barrier; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.apache.tinkerpop.gremlin.process.traversal.step.util.ComputerAwareStep; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; +import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException; +import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; @@ -43,6 +46,7 @@ public final class RepeatStep<S> extends ComputerAwareStep<S, S> implements Trav private Traversal.Admin<S, S> repeatTraversal = null; private Traversal.Admin<S, ?> untilTraversal = null; private Traversal.Admin<S, ?> emitTraversal = null; + private boolean first = true; private String loopName = null; public boolean untilFirst = false; public boolean emitFirst = false; @@ -206,25 +210,40 @@ public final class RepeatStep<S> extends ComputerAwareStep<S, S> implements Trav throw new IllegalStateException("The repeat()-traversal was not defined: " + this); while (true) { - if (this.repeatTraversal.getEndStep().hasNext()) { + if (!first && this.repeatTraversal.getEndStep().hasNext()) { return this.repeatTraversal.getEndStep(); } else { - final Traverser.Admin<S> start = this.starts.next(); - start.initialiseLoops(this.getId(), this.loopName); - if (doUntil(start, true)) { - start.resetLoops(); - return IteratorUtils.of(start); - } - this.repeatTraversal.addStart(start); - if (doEmit(start, true)) { - final Traverser.Admin<S> emitSplit = start.split(); - emitSplit.resetLoops(); - return IteratorUtils.of(emitSplit); + this.first = false; + if (!TraversalHelper.getStepsOfAssignableClassRecursively(Barrier.class, repeatTraversal).isEmpty()) { + // If the repeatTraversal has a Barrier then make sure that all starts are added to the + // repeatTraversal before it is iterated so that RepeatStep always has "global" children. + if (!this.starts.hasNext()) + throw FastNoSuchElementException.instance(); + while (this.starts.hasNext()) { + processTraverser(this.starts.next()); + } + } else { + return processTraverser(this.starts.next()); } } } } + private Iterator<Traverser.Admin<S>> processTraverser(final Traverser.Admin<S> start) { + start.initialiseLoops(this.getId(), this.loopName); + if (doUntil(start, true)) { + start.resetLoops(); + return IteratorUtils.of(start); + } + this.repeatTraversal.addStart(start); + if (doEmit(start, true)) { + final Traverser.Admin<S> emitSplit = start.split(); + emitSplit.resetLoops(); + return IteratorUtils.of(emitSplit); + } + return Collections.emptyIterator(); + } + @Override protected Iterator<Traverser.Admin<S>> computerAlgorithm() throws NoSuchElementException { if (null == this.repeatTraversal) diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature index 9fbdde3ef5..51834c060c 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature @@ -429,4 +429,183 @@ Feature: Step - repeat() When iterated to list Then the result should be unordered | result | - | marko | \ No newline at end of file + | marko | + + # Proper handling of empty results if repeat doesn't output traversers + Scenario: g_V_repeatXboth_hasXnot_productiveXX_timesX3X_constantX1X + Given the modern graph + And the traversal of + """ + g.V().repeat(__.both().has("not", "productive")).times(3).constant(1) + """ + When iterated to list + Then the result should be empty + + # Proper handling of empty results if no traverser goes into repeat + Scenario: g_V_hasXnot_productiveX_repeatXbothX_timesX3X_constantX1X + Given the modern graph + And the traversal of + """ + g.V().has("not", "productive").repeat(__.both()).times(3).constant(1) + """ + When iterated to list + Then the result should be empty + + # Each iteration of repeat traversal that ends in a barrier leads to BFS style processing + @InsertionOrderingRequired + Scenario: g_VX1_2_3X_repeatXboth_barrierX_emit_timesX2X_path + Given the modern graph + And using the parameter vid1 defined as "v[marko].id" + And using the parameter vid2 defined as "v[vadas].id" + And using the parameter vid3 defined as "v[lop].id" + And the traversal of + """ + g.V(vid1, vid2, vid3).repeat(__.both().barrier()).emit().times(2).path() + """ + When iterated to list + Then the result should be ordered + | result | + | p[v[marko],v[lop]] | + | p[v[marko],v[vadas]] | + | p[v[marko],v[josh]] | + | p[v[vadas],v[marko]] | + | p[v[lop],v[marko]] | + | p[v[lop],v[josh]] | + | p[v[lop],v[peter]] | + | p[v[marko],v[lop],v[marko]] | + | p[v[marko],v[lop],v[josh]] | + | p[v[marko],v[lop],v[peter]] | + | p[v[marko],v[vadas],v[marko]] | + | p[v[marko],v[josh],v[ripple]] | + | p[v[marko],v[josh],v[lop]] | + | p[v[marko],v[josh],v[marko]] | + | p[v[vadas],v[marko],v[lop]] | + | p[v[vadas],v[marko],v[vadas]] | + | p[v[vadas],v[marko],v[josh]] | + | p[v[lop],v[marko],v[lop]] | + | p[v[lop],v[marko],v[vadas]] | + | p[v[lop],v[marko],v[josh]] | + | p[v[lop],v[josh],v[ripple]] | + | p[v[lop],v[josh],v[lop]] | + | p[v[lop],v[josh],v[marko]] | + | p[v[lop],v[peter],v[lop]] | + + # Global children should be ordered by last loop first + @GraphComputerVerificationOrderingNotSupported + Scenario: g_V_order_byXname_descX_repeatXboth_simplePath_order_byXname_descXX_timesX2X_path + Given the modern graph + And the traversal of + """ + g.V().order().by("name", Order.desc).repeat(__.both().simplePath().order().by("name", Order.desc)).times(2).path() + """ + When iterated to list + Then the result should be ordered + | result | + | p[v[lop],v[marko],v[vadas]] | + | p[v[josh],v[marko],v[vadas]] | + | p[v[marko],v[josh],v[ripple]] | + | p[v[lop],v[josh],v[ripple]] | + | p[v[marko],v[lop],v[peter]] | + | p[v[josh],v[lop],v[peter]] | + | p[v[peter],v[lop],v[marko]] | + | p[v[josh],v[lop],v[marko]] | + | p[v[ripple],v[josh],v[marko]] | + | p[v[lop],v[josh],v[marko]] | + | p[v[vadas],v[marko],v[lop]] | + | p[v[josh],v[marko],v[lop]] | + | p[v[ripple],v[josh],v[lop]] | + | p[v[marko],v[josh],v[lop]] | + | p[v[vadas],v[marko],v[josh]] | + | p[v[lop],v[marko],v[josh]] | + | p[v[peter],v[lop],v[josh]] | + | p[v[marko],v[lop],v[josh]] | + + # Nested repeat should maintain globalness + @GraphComputerVerificationOrderingNotSupported + Scenario: g_V_repeatXboth_repeatXorder_byXnameXX_timesX1XX_timesX1X + Given the modern graph + And the traversal of + """ + g.V().repeat(__.both().repeat(__.order().by("name")).times(1)).times(1) + """ + When iterated to list + Then the result should be ordered + | result | + | v[josh] | + | v[josh] | + | v[josh] | + | v[lop] | + | v[lop] | + | v[lop] | + | v[marko] | + | v[marko] | + | v[marko] | + | v[peter] | + | v[ripple] | + | v[vadas] | + + # Nested local inside repeat should prevent global children from parent repeat + @GraphComputerVerificationStarGraphExceeded + Scenario: g_V_order_byXname_descX_repeatXlocalXout_order_byXnameXXX_timesX1X + Given the modern graph + And the traversal of + """ + g.V().order().by("name", Order.desc).repeat(__.local(__.out().order().by("name"))).times(1) + """ + When iterated to list + Then the result should be unordered + | result | + | v[lop] | + | v[josh] | + | v[lop] | + | v[vadas] | + | v[lop] | + | v[ripple] | + + # Local child traversal should be applied per loop + @GraphComputerVerificationStarGraphExceeded + Scenario: g_V_order_byXnameX_repeatXlocalXboth_simplePath_order_byXnameXXX_timesX2X_path + Given the modern graph + And the traversal of + """ + g.V().order().by("name").repeat(__.local(__.both().simplePath().order().by("name"))).times(2).path() + """ + When iterated to list + Then the result should be ordered + | result | + | p[v[josh],v[lop],v[marko]] | + | p[v[josh],v[lop],v[peter]] | + | p[v[josh],v[marko],v[lop]] | + | p[v[josh],v[marko],v[vadas]] | + | p[v[lop],v[josh],v[marko]] | + | p[v[lop],v[josh],v[ripple]] | + | p[v[lop],v[marko],v[josh]] | + | p[v[lop],v[marko],v[vadas]] | + | p[v[marko],v[josh],v[lop]] | + | p[v[marko],v[josh],v[ripple]] | + | p[v[marko],v[lop],v[josh]] | + | p[v[marko],v[lop],v[peter]] | + | p[v[peter],v[lop],v[josh]] | + | p[v[peter],v[lop],v[marko]] | + | p[v[ripple],v[josh],v[lop]] | + | p[v[ripple],v[josh],v[marko]] | + | p[v[vadas],v[marko],v[josh]] | + | p[v[vadas],v[marko],v[lop]] | + + # Branching step with global children should remain global in repeat traversal + @GraphComputerVerificationOrderingNotSupported + Scenario: g_V_repeatXunionXoutXknowsX_order_byXnameX_inXcreatedX_order_byXnameXXX_timesX1X + Given the modern graph + And the traversal of + """ + g.V().repeat(__.union(__.out("knows").order().by("name"), __.in("created").order().by("name"))).times(1) + """ + When iterated to list + Then the result should be ordered + | result | + | v[josh] | + | v[vadas] | + | v[josh] | + | v[josh] | + | v[marko] | + | v[peter] | diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/Paths.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/Paths.feature index 172754f81a..381ab656ee 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/Paths.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/Paths.feature @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -@StepClassIntegrated +@StepClassIntegrated @StepPaths Feature: Step - paths @GraphComputerVerificationReferenceOnly @InsertionOrderingRequired @@ -38,6 +38,7 @@ Feature: Step - paths by(__.select(Pop.all, "v")).as("t"). filter(__.select(Pop.all, "p").count(local).as("l"). select(Pop.last, "t").select(Pop.all, "p").dedup(Scope.local).count(Scope.local).where(P.eq("l"))). + where("src", P.neq("tgt")). select(Pop.last, "t"). not(__.select(Pop.all, "p").as("p").count(local).as("l"). select(Pop.all, "x").unfold().filter(select(keys).where(P.eq("t")).by(__.select("src", "tgt"))). @@ -63,18 +64,12 @@ Feature: Step - paths | l[peter,lop,josh]| | l[vadas,marko]| | l[ripple,josh]| - | l[marko]| - | l[josh]| - | l[ripple]| | l[josh,ripple]| | l[peter,lop]| | l[vadas,marko,josh]| | l[lop,josh,ripple]| | l[marko,josh]| | l[lop,marko,vadas]| - | l[lop]| - | l[peter]| - | l[vadas]| | l[marko,josh,ripple]| | l[marko,vadas]| | l[vadas,marko,lop]| @@ -103,11 +98,9 @@ Feature: Step - paths When iterated to list Then the result should be unordered | result | - | m[{"song":"TOMORROW IS A LONG TIME","artists":["Bob_Dylan"]}] | - | m[{"song":"JOHN BROWN","artists":["Bob_Dylan"]}] | + | m[{"song":"I WANT YOU","artists":["Bob_Dylan"]}] | | m[{"song":"BABY BLUE","artists":["Unknown"]}] | | m[{"song":"CANDYMAN","artists":["Garcia","Hunter"]}] | | m[{"song":"BIG RIVER","artists":["Weir","Johnny_Cash"]}] | | m[{"song":"TERRAPIN STATION","artists":["Garcia","Hunter"]}] | | m[{"song":"DRUMS","artists":["Grateful_Dead"]}] | -
