From 4230c97f83f33adc87901b535c535d29def7cfe3 Mon Sep 17 00:00:00 2001 From: kenhuuu <106191785+kenhuuu@users.noreply.github.com> Date: Mon, 16 Oct 2023 15:08:39 -0700 Subject: [PATCH] Add support for testing sets in results for feature tests. (#2279) --- .../Gherkin/CommonSteps.cs | 12 +++++++ .../Gherkin/Gremlin.cs | 1 + .../driver/cucumber/cucumberSteps_test.go | 33 +++++++++++++++++-- gremlin-go/driver/cucumber/gremlin.go | 1 + .../test/cucumber/feature-steps.js | 1 + .../test/cucumber/gremlin.js | 1 + .../src/main/python/radish/gremlin.py | 1 + .../gremlin/features/StepDefinition.java | 7 ++-- .../test/features/filter/Dedup.feature | 17 ++++++++++ 9 files changed, 68 insertions(+), 6 deletions(-) diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs index 6da7935bd50..29e6a1bea4d 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs @@ -284,6 +284,18 @@ public void AssertResult(string characterizedAs, DataTable table = null) } Assert.True(expectedArrayContainsResultDictionary); } + else if (resultItem is HashSet resultItemSet) + { + var expectedArrayContainsResultAsSet = false; + foreach (var expectedItem in expectedArray) + { + if (expectedItem is not HashSet expectedItemSet) continue; + if (!expectedItemSet.SetEquals(resultItemSet)) continue; + expectedArrayContainsResultAsSet = true; + break; + } + Assert.True(expectedArrayContainsResultAsSet); + } else if (resultItem is double resultItemDouble && expectedArray.Select(e => e.GetType()).Any(t => t == typeof(decimal))) { diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index 12a44358dce..54548866b9c 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -137,6 +137,7 @@ private static IDictionary, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").Out("created").As("b").In("created").As("c").CyclicPath().From("a").To("b").Path()}}, {"g_injectX0X_V_both_coalesceXhasXname_markoX_both_constantX0XX_cyclicPath_path", new List, ITraversal>> {(g,p) =>g.Inject(0).V().Both().Coalesce(__.Has("name","marko").Both(),__.Constant(0)).CyclicPath().Path()}}, {"g_V_out_in_valuesXnameX_fold_dedupXlocalX_unfold", new List, ITraversal>> {(g,p) =>g.V().Out().In().Values("name").Fold().Dedup(Scope.Local).Unfold()}}, + {"g_V_out_in_valuesXnameX_fold_dedupXlocalX", new List, ITraversal>> {(g,p) =>g.V().Out().Map(__.In().Values("name").Fold().Dedup(Scope.Local))}}, {"g_V_out_asXxX_in_asXyX_selectXx_yX_byXnameX_fold_dedupXlocal_x_yX_unfold", new List, ITraversal>> {(g,p) =>g.V().Out().As("x").In().As("y").Select("x","y").By("name").Fold().Dedup(Scope.Local,"x","y").Unfold()}}, {"g_V_both_dedup_name", new List, ITraversal>> {(g,p) =>g.V().Both().Dedup().Values("name")}}, {"g_V_both_hasXlabel_softwareX_dedup_byXlangX_name", new List, ITraversal>> {(g,p) =>g.V().Both().Has(T.Label,"software").Dedup().By("lang").Values("name")}}, diff --git a/gremlin-go/driver/cucumber/cucumberSteps_test.go b/gremlin-go/driver/cucumber/cucumberSteps_test.go index 73855c31d57..00653ea0893 100644 --- a/gremlin-go/driver/cucumber/cucumberSteps_test.go +++ b/gremlin-go/driver/cucumber/cucumberSteps_test.go @@ -24,15 +24,17 @@ import ( "encoding/json" "errors" "fmt" - "github.com/apache/tinkerpop/gremlin-go/v3/driver" - "github.com/cucumber/godog" "math" "reflect" "regexp" + "sort" "strconv" "strings" "sync" "testing" + + gremlingo "github.com/apache/tinkerpop/gremlin-go/v3/driver" + "github.com/cucumber/godog" ) type tinkerPopGraph struct { @@ -652,6 +654,23 @@ func compareListEqualsWithoutOrder(expected []interface{}, actual []interface{}) break } } + } else if actualSet, ok := a.(*gremlingo.SimpleSet); ok { + // Set is a special case here because there is no TypeOf().Kind() for sets. + actualStringArray := makeSortedStringArrayFromSet(actualSet) + + for i := len(expectedCopy) - 1; i >= 0; i-- { + curExpected := expectedCopy[i] + expectedSet, ok := curExpected.(*gremlingo.SimpleSet) + if ok { + expectedStringArray := makeSortedStringArrayFromSet(expectedSet) + + if reflect.DeepEqual(actualStringArray, expectedStringArray) { + expectedCopy = append(expectedCopy[:i], expectedCopy[i+1:]...) + found = true + break + } + } + } } else { switch reflect.TypeOf(a).Kind() { case reflect.Array, reflect.Slice: @@ -761,6 +780,16 @@ func compareListEqualsWithOf(expected []interface{}, actual []interface{}) bool return true } +func makeSortedStringArrayFromSet(set *gremlingo.SimpleSet) []string { + var sortedStrings []string + for _, element := range set.ToSlice() { + sortedStrings = append(sortedStrings, fmt.Sprintf("%v", element)) + } + sort.Sort(sort.StringSlice(sortedStrings)) + + return sortedStrings +} + func (tg *tinkerPopGraph) theTraversalOf(arg1 *godog.DocString) error { traversal, err := GetTraversal(tg.scenario.Name, tg.g, tg.parameters) if err != nil { diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index 33aac6730e8..e3b4c6aba76 100644 --- a/gremlin-go/driver/cucumber/gremlin.go +++ b/gremlin-go/driver/cucumber/gremlin.go @@ -108,6 +108,7 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_VX1X_asXaX_outXcreatedX_asXbX_inXcreatedX_asXcX_cyclicPath_fromXaX_toXbX_path": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).As("a").Out("created").As("b").In("created").As("c").CyclicPath().From("a").To("b").Path()}}, "g_injectX0X_V_both_coalesceXhasXname_markoX_both_constantX0XX_cyclicPath_path": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(0).V().Both().Coalesce(gremlingo.T__.Has("name", "marko").Both(), gremlingo.T__.Constant(0)).CyclicPath().Path()}}, "g_V_out_in_valuesXnameX_fold_dedupXlocalX_unfold": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().In().Values("name").Fold().Dedup(gremlingo.Scope.Local).Unfold()}}, + "g_V_out_in_valuesXnameX_fold_dedupXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().Map(gremlingo.T__.In().Values("name").Fold().Dedup(gremlingo.Scope.Local))}}, "g_V_out_asXxX_in_asXyX_selectXx_yX_byXnameX_fold_dedupXlocal_x_yX_unfold": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().As("x").In().As("y").Select("x", "y").By("name").Fold().Dedup(gremlingo.Scope.Local, "x", "y").Unfold()}}, "g_V_both_dedup_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Both().Dedup().Values("name")}}, "g_V_both_hasXlabel_softwareX_dedup_byXlangX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Both().Has(gremlingo.T.Label, "software").Dedup().By("lang").Values("name")}}, diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js index 8ce00869ad8..9e25f187fe9 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js @@ -72,6 +72,7 @@ const ignoredScenarios = { // An associative array containing the scenario name as key, for example: 'g_withSideEffectXa_setX_V_both_name_storeXaX_capXaX': new IgnoreError(ignoreReason.setNotSupported), 'g_withSideEffectXa_setX_V_both_name_aggregateXlocal_aX_capXaX': new IgnoreError(ignoreReason.setNotSupported), + 'g_V_out_in_valuesXnameX_fold_dedupXlocalX': new IgnoreError(ignoreReason.setNotSupported), 'g_withStrategiesXProductiveByStrategyX_V_groupCount_byXageX': new IgnoreError(ignoreReason.nullKeysInMapNotSupportedWell), 'g_V_shortestPath_edgesIncluded': new IgnoreError(ignoreReason.needsFurtherInvestigation), 'g_V_shortestPath_edgesIncluded_edgesXoutEX': new IgnoreError(ignoreReason.needsFurtherInvestigation), diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js index 262429e3d09..43c96bad3a5 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js @@ -127,6 +127,7 @@ const gremlins = { g_VX1X_asXaX_outXcreatedX_asXbX_inXcreatedX_asXcX_cyclicPath_fromXaX_toXbX_path: [function({g, vid1}) { return g.V(vid1).as("a").out("created").as("b").in_("created").as("c").cyclicPath().from_("a").to("b").path() }], g_injectX0X_V_both_coalesceXhasXname_markoX_both_constantX0XX_cyclicPath_path: [function({g}) { return g.inject(0).V().both().coalesce(__.has("name","marko").both(),__.constant(0)).cyclicPath().path() }], g_V_out_in_valuesXnameX_fold_dedupXlocalX_unfold: [function({g}) { return g.V().out().in_().values("name").fold().dedup(Scope.local).unfold() }], + g_V_out_in_valuesXnameX_fold_dedupXlocalX: [function({g}) { return g.V().out().map(__.in_().values("name").fold().dedup(Scope.local)) }], g_V_out_asXxX_in_asXyX_selectXx_yX_byXnameX_fold_dedupXlocal_x_yX_unfold: [function({g}) { return g.V().out().as("x").in_().as("y").select("x","y").by("name").fold().dedup(Scope.local,"x","y").unfold() }], g_V_both_dedup_name: [function({g}) { return g.V().both().dedup().values("name") }], g_V_both_hasXlabel_softwareX_dedup_byXlangX_name: [function({g}) { return g.V().both().has(T.label,"software").dedup().by("lang").values("name") }], diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py index d833c06cab2..6f4ca7db75a 100644 --- a/gremlin-python/src/main/python/radish/gremlin.py +++ b/gremlin-python/src/main/python/radish/gremlin.py @@ -109,6 +109,7 @@ 'g_VX1X_asXaX_outXcreatedX_asXbX_inXcreatedX_asXcX_cyclicPath_fromXaX_toXbX_path': [(lambda g, vid1=None:g.V(vid1).as_('a').out('created').as_('b').in_('created').as_('c').cyclicPath().from_('a').to('b').path())], 'g_injectX0X_V_both_coalesceXhasXname_markoX_both_constantX0XX_cyclicPath_path': [(lambda g:g.inject(0).V().both().coalesce(__.has('name','marko').both(),__.constant(0)).cyclicPath().path())], 'g_V_out_in_valuesXnameX_fold_dedupXlocalX_unfold': [(lambda g:g.V().out().in_().name.fold().dedup(Scope.local).unfold())], + 'g_V_out_in_valuesXnameX_fold_dedupXlocalX': [(lambda g:g.V().out().map(__.in_().name.fold().dedup(Scope.local)))], 'g_V_out_asXxX_in_asXyX_selectXx_yX_byXnameX_fold_dedupXlocal_x_yX_unfold': [(lambda g:g.V().out().as_('x').in_().as_('y').select('x','y').by('name').fold().dedup(Scope.local,'x','y').unfold())], 'g_V_both_dedup_name': [(lambda g:g.V().both().dedup().name)], 'g_V_both_hasXlabel_softwareX_dedup_byXlangX_name': [(lambda g:g.V().both().has(T.label,'software').dedup().by('lang').name)], diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java index 5d8c8cdd9b5..c1d726c6423 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java @@ -221,14 +221,13 @@ public final class StepDefinition { add(Pair.with(Pattern.compile("D\\[(.*)\\]"), Direction::valueOf)); add(Pair.with(Pattern.compile("M\\[(.*)\\]"), Merge::valueOf)); - add(Pair.with(Pattern.compile("c\\[(.*)\\]"), s -> { - throw new AssumptionViolatedException("This test uses a lambda as a parameter which is not supported by gremlin-language"); - })); + add(Pair.with(Pattern.compile("c\\[(.*)\\]"), s -> Collections.emptySet())); add(Pair.with(Pattern.compile("s\\[\\]"), s -> { throw new AssumptionViolatedException("This test uses a empty Set as a parameter which is not supported by gremlin-language"); })); add(Pair.with(Pattern.compile("s\\[(.*)\\]"), s -> { - throw new AssumptionViolatedException("This test uses a Set as a parameter which is not supported by gremlin-language"); + final String[] items = s.split(","); + return Stream.of(items).map(String::trim).map(x -> convertToObject(x)).collect(Collectors.toSet()); })); add(Pair.with(Pattern.compile("(null)"), s -> null)); diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Dedup.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Dedup.feature index 6c950d7faf5..bae90ba8587 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Dedup.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Dedup.feature @@ -31,6 +31,23 @@ Feature: Step - dedup() | josh | | peter | + @GraphComputerVerificationStarGraphExceeded + Scenario: g_V_out_in_valuesXnameX_fold_dedupXlocalX + Given the modern graph + And the traversal of + """ + g.V().out().map(__.in().values("name").fold().dedup(Scope.local)) + """ + When iterated to list + Then the result should be unordered + | result | + | s[peter,marko,josh] | + | s[marko,peter,josh] | + | s[josh,marko,peter] | + | s[marko] | + | s[josh] | + | s[marko] | + Scenario: g_V_out_asXxX_in_asXyX_selectXx_yX_byXnameX_fold_dedupXlocal_x_yX_unfold Given the modern graph And the traversal of