This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


The following commit(s) were added to refs/heads/master by this push:
     new 42d9adb  Added error message assertion in gherkin
42d9adb is described below

commit 42d9adb5cc29bf994bdba32038d5e06eeb29080f
Author: Stephen Mallette <stepm...@amazon.com>
AuthorDate: Tue Mar 29 09:15:19 2022 -0400

    Added error message assertion in gherkin
    
    Having the option to assert the error message made it possible to add more 
tests to the Gherkin suite and remove more from the standard JVM suite. Added 
ReadOnlyStrategy tests to Gherkin and added tags for strategies. CTR
---
 docs/src/dev/developer/for-committers.asciidoc     |   6 ++
 gremlin-dotnet/build/generate.groovy               |   1 +
 .../Gherkin/CommonSteps.cs                         |  24 +++++
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |   8 ++
 gremlin-javascript/build/generate.groovy           |   2 +-
 .../gremlin-javascript/package-lock.json           |  17 ++++
 .../javascript/gremlin-javascript/package.json     |   1 +
 .../test/cucumber/feature-steps.js                 |  17 +++-
 .../gremlin-javascript/test/cucumber/gremlin.js    |   9 +-
 .../src/main/python/radish/feature_steps.py        |  73 +++++++++------
 gremlin-python/src/main/python/radish/gremlin.py   |   7 ++
 gremlin-test/features/filter/Coin.feature          |   2 +-
 gremlin-test/features/filter/Dedup.feature         |   1 +
 gremlin-test/features/filter/Sample.feature        |   3 +
 gremlin-test/features/filter/Where.feature         |   2 +-
 .../features/integrated/ReadOnlyStrategy.feature   | 100 +++++++++++++++++++++
 gremlin-test/features/map/Max.feature              |   4 +
 gremlin-test/features/map/Mean.feature             |   4 +
 gremlin-test/features/map/Min.feature              |   4 +
 gremlin-test/features/map/Order.feature            |   1 +
 gremlin-test/features/map/Path.feature             |   2 +-
 gremlin-test/features/map/Project.feature          |   1 +
 gremlin-test/features/map/Select.feature           |   2 +-
 gremlin-test/features/map/Sum.feature              |   4 +
 gremlin-test/features/sideEffect/Aggregate.feature |   4 +-
 gremlin-test/features/sideEffect/Fail.feature      |   2 +-
 gremlin-test/features/sideEffect/Group.feature     |   1 +
 .../features/sideEffect/GroupCount.feature         |   1 +
 .../tinkerpop/gremlin/features/StepDefinition.java |  26 ++++++
 .../process/ProcessLimitedComputerSuite.java       |   2 -
 .../process/ProcessLimitedStandardSuite.java       |   6 --
 31 files changed, 292 insertions(+), 45 deletions(-)

diff --git a/docs/src/dev/developer/for-committers.asciidoc 
b/docs/src/dev/developer/for-committers.asciidoc
index fdca4e8..d442e30 100644
--- a/docs/src/dev/developer/for-committers.asciidoc
+++ b/docs/src/dev/developer/for-committers.asciidoc
@@ -452,6 +452,8 @@ The "Then" options handle the assertion of the result. 
There are several options
 in it
 * "the result should be empty" - no results
 * "the traversal will raise an error" - an exception is thrown as a result of 
traversal iteration
+* "the traversal will raise an error with message _comparison_ text of 
_message_" - an exception is thrown as a result
+of traversal iteration where "_comparison_" may be one of "containing", 
"starting", or "ending".
 * "the result should be ordered" - the exact results and should appear in the 
order presented
 * "the result should be unordered" - the exact results but can appear any order
 * "the result should be of" - results can be any of the specified values and 
in any order (use when guarantees
@@ -535,6 +537,10 @@ best example of this sort of test would be one that uses 
the remote `Lambda` syn
 * `@UserSuppliedVertexIds` - The scenario relies on the vertex IDs specified 
in the dataset used by the scenario.
 * `@UserSuppliedEdgeIds` - The scenario relies on the edge IDs specified in 
the dataset used by the scenario.
 * `@UserSuppliedVertexPropertyIds` - The scenario relies on the vertex 
property IDs specified in the dataset used by the scenario.
+* `@With*` - The scenario uses some `with()` based configuration like 
strategies:
+** `@WithProductiveByStrategy`
+** `@WithReadOnlyStrategy`
+** `@WithSeedStrategy`
 
 Tag filters can be applied to Intellij at execution time by adding a system 
properties of
 `-Dcucumber.filter.tags=<step-filter>`.
diff --git a/gremlin-dotnet/build/generate.groovy 
b/gremlin-dotnet/build/generate.groovy
index 7b3a620..6360c3a 100644
--- a/gremlin-dotnet/build/generate.groovy
+++ b/gremlin-dotnet/build/generate.groovy
@@ -96,6 +96,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
                      'using Gremlin.Net.Structure;\n' +
                      'using Gremlin.Net.Process.Traversal;\n' +
                      'using 
Gremlin.Net.Process.Traversal.Strategy.Optimization;\n' +
+                     'using 
Gremlin.Net.Process.Traversal.Strategy.Verification;\n' +
                      'using 
Gremlin.Net.Process.Traversal.Strategy.Decoration;\n')
     writer.writeLine('namespace Gremlin.Net.IntegrationTest.Gherkin\n' +
             '{\n' +
diff --git 
a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
index 4a73b11..fc6bceb 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
@@ -219,6 +219,30 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
             _error = null;
         }
 
+        [Then("the traversal will raise an error with message (\\w+) text of 
\"(.*)\"")]
+        public void TraversalWillRaiseErrorWithMessage(string comparison, 
string expectedMessage)
+        {
+            Assert.NotNull(_error);
+
+            switch (comparison) {
+                case "containing":
+                    Assert.Equal(true, 
_error.Message.Contains(expectedMessage));
+                    break;
+                case "starting":
+                    Assert.Equal(true, 
_error.Message.StartsWith(expectedMessage));
+                    break;
+                case "ending":
+                    Assert.Equal(true, 
_error.Message.EndsWith(expectedMessage));
+                    break;
+                default:
+                    throw new NotSupportedException(
+                            "Unknown comparison of " + comparison + " - must 
be one of: containing, starting or ending");
+            }
+
+            // consume the error now that it has been asserted
+            _error = null;
+        }
+
         [Then("the result should be (\\w+)")]
         public void AssertResult(string characterizedAs, DataTable table = 
null)
         {
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index cbb0a26..acbe204 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -33,6 +33,7 @@ using System.Collections.Generic;
 using Gremlin.Net.Structure;
 using Gremlin.Net.Process.Traversal;
 using Gremlin.Net.Process.Traversal.Strategy.Optimization;
+using Gremlin.Net.Process.Traversal.Strategy.Verification;
 using Gremlin.Net.Process.Traversal.Strategy.Decoration;
 
 namespace Gremlin.Net.IntegrationTest.Gherkin
@@ -343,6 +344,13 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_V_coworker_with_midV", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) 
=>g.V().HasLabel("person").Filter(__.OutE("created")).As("p1").V().HasLabel("person").Where(P.Neq("p1")).Filter(__.OutE("created")).As("p2").Map<object>(__.Out("created").Where(__.In("created").As("p1")).Values<object>("name").Fold()).Group<object,object>().By(__.Select<object>("p1").By("name")).By(__.Group<object,object>().By(__.Select<object>("p2").By("name")).
 [...]
                {"g_V_playlist_paths", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) 
=>g.V().Has("name","Bob_Dylan").In("sungBy").Order().By("name").As("a").Repeat(__.Out().Order().By("name").SimplePath().From("a")).Until(__.Out("writtenBy").Has("name","Johnny_Cash")).Limit<object>(1).As("b").Repeat(__.Out().Order().By("name").As("c").SimplePath().From("b").To("c")).Until(__.Out("sungBy").Has("name","Grateful_Dead")).Limit<object>(1).Path().From("a")
 [...]
                {"g_V_shortestpath", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) 
=>g.V().As("v").Both().As("v").Project<object>("src","tgt","p").By(__.Select<object>(Pop.First,"v")).By(__.Select<object>(Pop.Last,"v")).By(__.Select<object>(Pop.All,"v")).As("triple").Group("x").By(__.Select<object>("src","tgt")).By(__.Select<object>("p").Fold()).Select<object>("tgt").Barrier().Repeat(__.Both().As("v").Project<object>("src","tgt","p").By(__.Select<obj
 [...]
+               {"g_withStrategiesXReadOnlyStrategyX_V", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.WithStrategies(new ReadOnlyStrategy()).V()}}, 
+               {"g_withStrategiesXReadOnlyStrategyX_V_outXknowsX_name", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.WithStrategies(new 
ReadOnlyStrategy()).V().Out("knows").Values<object>("name")}}, 
+               {"g_withStrategiesXReadOnlyStrategyX_addVXpersonX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.WithStrategies(new ReadOnlyStrategy()).AddV("person")}}, 
+               
{"g_withStrategiesXReadOnlyStrategyX_addVXpersonX_fromXVX1XX_toXVX2XX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.WithStrategies(new 
ReadOnlyStrategy()).AddE("link").From(__.V(1)).To(__.V(2))}}, 
+               
{"g_withStrategiesXReadOnlyStrategyX_V_addVXpersonX_fromXVX1XX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.WithStrategies(new 
ReadOnlyStrategy()).V().AddE("link").From(__.V(1))}}, 
+               {"g_withStrategiesXReadOnlyStrategyX_V_propertyXname_joshX", 
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.WithStrategies(new 
ReadOnlyStrategy()).V().Property("name","josh")}}, 
+               {"g_withStrategiesXReadOnlyStrategyX_E_propertyXweight_0X", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.WithStrategies(new ReadOnlyStrategy()).E().Property("weight",0)}}, 
                {"g_V_classic_recommendation", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.V().Has("name","DARK 
STAR").As("a").Out("followedBy").Aggregate("stash").In("followedBy").Where(P.Neq("a").And(P.Without(new
 List<object> 
{"stash"}))).GroupCount<object>().Unfold<object>().Project<object>("x","y","z").By(__.Select<object>(Column.Keys).Values<object>("name")).By(__.Select<object>(Column.Keys).Values<object>("performances")).By(__.Select<ob
 [...]
                {"g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.AddV("person").Property("name","marko").Property("age",29).As("marko").AddV("person").Property("name","vadas").Property("age",27).As("vadas").AddV("software").Property("name","lop").Property("lang","java").As("lop").AddV("person").Property("name","josh").Property("age",32).As("josh").AddV("software").Property("name","ripple").Property
 [...]
                
{"g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_2X", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.AddV("person").Property("name","marko").Property("age",29).As("marko").AddV("person").Property("name","vadas").Property("age",27).As("vadas").AddV("software").Property("name","lop").Property("lang","java").As("lop").AddV("person").Property("name","josh").Property("age",32).As("josh").AddV("software").Property("name"
 [...]
diff --git a/gremlin-javascript/build/generate.groovy 
b/gremlin-javascript/build/generate.groovy
index 540e7c3..6eaaf94 100644
--- a/gremlin-javascript/build/generate.groovy
+++ b/gremlin-javascript/build/generate.groovy
@@ -91,7 +91,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
     writer.writeLine(
                     'const graphTraversalModule = 
require(\'../../lib/process/graph-traversal\');\n' +
                     'const traversalModule = 
require(\'../../lib/process/traversal\');\n' +
-                    'const { TraversalStrategies, VertexProgramStrategy, 
OptionsStrategy, SeedStrategy, ProductiveByStrategy } = 
require(\'../../lib/process/traversal-strategy\');\n' +
+                    'const { TraversalStrategies, VertexProgramStrategy, 
OptionsStrategy, ReadOnlyStrategy, SeedStrategy, ProductiveByStrategy } = 
require(\'../../lib/process/traversal-strategy\');\n' +
                     'const __ = graphTraversalModule.statics;\n' +
                     'const Barrier = traversalModule.barrier\n' +
                     'const Cardinality = traversalModule.cardinality\n' +
diff --git 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/package-lock.json 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/package-lock.json
index c2366e0..94788a9 100644
--- 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/package-lock.json
+++ 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/package-lock.json
@@ -13,6 +13,7 @@
       },
       "devDependencies": {
         "chai": "~4.1.2",
+        "chai-string": "~1.5.0",
         "colors": "1.4.0",
         "cucumber": "~4.2.1",
         "eslint": "^7.32.0",
@@ -579,6 +580,15 @@
         "node": ">=4"
       }
     },
+    "node_modules/chai-string": {
+      "version": "1.5.0",
+      "resolved": 
"https://registry.npmjs.org/chai-string/-/chai-string-1.5.0.tgz";,
+      "integrity": 
"sha512-sydDC3S3pNAQMYwJrs6dQX0oBQ6KfIPuOZ78n7rocW0eJJlsHPh2t3kwW7xfwYA/1Bf6/arGtSUo16rxR2JFlw==",
+      "dev": true,
+      "peerDependencies": {
+        "chai": "^4.1.2"
+      }
+    },
     "node_modules/chalk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz";,
@@ -4740,6 +4750,13 @@
         "type-detect": "^4.0.0"
       }
     },
+    "chai-string": {
+      "version": "1.5.0",
+      "resolved": 
"https://registry.npmjs.org/chai-string/-/chai-string-1.5.0.tgz";,
+      "integrity": 
"sha512-sydDC3S3pNAQMYwJrs6dQX0oBQ6KfIPuOZ78n7rocW0eJJlsHPh2t3kwW7xfwYA/1Bf6/arGtSUo16rxR2JFlw==",
+      "dev": true,
+      "requires": {}
+    },
     "chalk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz";,
diff --git 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/package.json 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/package.json
index 72ba43c..11fca74 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/package.json
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/package.json
@@ -18,6 +18,7 @@
   },
   "devDependencies": {
     "chai": "~4.1.2",
+    "chai-string": "~1.5.0",
     "colors": "1.4.0",
     "cucumber": "~4.2.1",
     "eslint": "^7.32.0",
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 6b85eb3..c5faddc 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
@@ -23,7 +23,9 @@
 'use strict';
 
 const {Given, Then, When} = require('cucumber');
-const expect = require('chai').expect;
+const chai = require('chai')
+chai.use(require('chai-string'));
+const expect = chai.expect;
 const util = require('util');
 const gremlin = require('./gremlin').gremlin;
 const graphModule = require('../../lib/structure/graph');
@@ -147,6 +149,19 @@ Then('the traversal will raise an error', function() {
   expect(this.result).to.be.a.instanceof(Error);
 });
 
+Then(/^the traversal will raise an error with message (\w+) text of "(.+)"$/, 
function(comparison, expectedMessage) {
+  expect(this.result).to.be.a.instanceof(Error);
+  if (comparison === "containing") {
+    expect(this.result.message).to.contain(expectedMessage)
+  } else if (comparison === "starting") {
+    expect(this.result.message).to.startWith(expectedMessage)
+  } else if (comparison === "ending") {
+    expect(this.result.message).to.endWith(expectedMessage)
+  } else {
+    throw new Error('unknown comparison \'' + comparison + '\'- must be: 
containing, ending or starting');
+  }
+});
+
 Then(/^the result should be (\w+)$/, function assertResult(characterizedAs, 
resultTable) {
   expect(this.result).to.not.be.a.instanceof(Error);
 
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 31387de..b66244e 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
@@ -26,7 +26,7 @@
 
 const graphTraversalModule = require('../../lib/process/graph-traversal');
 const traversalModule = require('../../lib/process/traversal');
-const { TraversalStrategies, VertexProgramStrategy, OptionsStrategy, 
SeedStrategy, ProductiveByStrategy } = 
require('../../lib/process/traversal-strategy');
+const { TraversalStrategies, VertexProgramStrategy, OptionsStrategy, 
ReadOnlyStrategy, SeedStrategy, ProductiveByStrategy } = 
require('../../lib/process/traversal-strategy');
 const __ = graphTraversalModule.statics;
 const Barrier = traversalModule.barrier
 const Cardinality = traversalModule.cardinality
@@ -334,6 +334,13 @@ const gremlins = {
     g_V_coworker_with_midV: [function({g}) { return 
g.V().hasLabel("person").filter(__.outE("created")).as("p1").V().hasLabel("person").where(P.neq("p1")).filter(__.outE("created")).as("p2").map(__.out("created").where(__.in_("created").as("p1")).values("name").fold()).group().by(__.select("p1").by("name")).by(__.group().by(__.select("p2").by("name")).by(__.project("numCoCreated","coCreated").by(__.count(Scope.local)).by())).unfold()
 }], 
     g_V_playlist_paths: [function({g}) { return 
g.V().has("name","Bob_Dylan").in_("sungBy").order().by("name").as("a").repeat(__.out().order().by("name").simplePath().from_("a")).until(__.out("writtenBy").has("name","Johnny_Cash")).limit(1).as("b").repeat(__.out().order().by("name").as("c").simplePath().from_("b").to("c")).until(__.out("sungBy").has("name","Grateful_Dead")).limit(1).path().from_("a").unfold().project("song","artists").by("name").by(__.coalesce(__.out("sungBy","writtenBy"
 [...]
     g_V_shortestpath: [function({g}) { return 
g.V().as("v").both().as("v").project("src","tgt","p").by(__.select(Pop.first,"v")).by(__.select(Pop.last,"v")).by(__.select(Pop.all,"v")).as("triple").group("x").by(__.select("src","tgt")).by(__.select("p").fold()).select("tgt").barrier().repeat(__.both().as("v").project("src","tgt","p").by(__.select(Pop.first,"v")).by(__.select(Pop.last,"v")).by(__.select(Pop.all,"v")).as("t").filter(__.select(Pop.all,"p").count(Scope.local).as("l").select(P
 [...]
+    g_withStrategiesXReadOnlyStrategyX_V: [function({g}) { return 
g.withStrategies(new ReadOnlyStrategy()).V() }], 
+    g_withStrategiesXReadOnlyStrategyX_V_outXknowsX_name: [function({g}) { 
return g.withStrategies(new ReadOnlyStrategy()).V().out("knows").values("name") 
}], 
+    g_withStrategiesXReadOnlyStrategyX_addVXpersonX: [function({g}) { return 
g.withStrategies(new ReadOnlyStrategy()).addV("person") }], 
+    g_withStrategiesXReadOnlyStrategyX_addVXpersonX_fromXVX1XX_toXVX2XX: 
[function({g}) { return g.withStrategies(new 
ReadOnlyStrategy()).addE("link").from_(__.V(1)).to(__.V(2)) }], 
+    g_withStrategiesXReadOnlyStrategyX_V_addVXpersonX_fromXVX1XX: 
[function({g}) { return g.withStrategies(new 
ReadOnlyStrategy()).V().addE("link").from_(__.V(1)) }], 
+    g_withStrategiesXReadOnlyStrategyX_V_propertyXname_joshX: [function({g}) { 
return g.withStrategies(new ReadOnlyStrategy()).V().property("name","josh") }], 
+    g_withStrategiesXReadOnlyStrategyX_E_propertyXweight_0X: [function({g}) { 
return g.withStrategies(new ReadOnlyStrategy()).E().property("weight",0) }], 
     g_V_classic_recommendation: [function({g}) { return g.V().has("name","DARK 
STAR").as("a").out("followedBy").aggregate("stash").in_("followedBy").where(P.neq("a").and(P.without(["stash"]))).groupCount().unfold().project("x","y","z").by(__.select(Column.keys).values("name")).by(__.select(Column.keys).values("performances")).by(__.select(Column.values)).order().by(__.select("z"),Order.desc).by(__.select("y"),Order.asc).limit(5).aggregate(Scope.local,"m").select("x")
 }], 
     g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX: [function({g, vid1}) { 
return 
g.addV("person").property("name","marko").property("age",29).as("marko").addV("person").property("name","vadas").property("age",27).as("vadas").addV("software").property("name","lop").property("lang","java").as("lop").addV("person").property("name","josh").property("age",32).as("josh").addV("software").property("name","ripple").property("lang","java").as("ripple").addV("person").property("name","peter").pr
 [...]
     g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_2X: 
[function({g, vid1}) { return 
g.addV("person").property("name","marko").property("age",29).as("marko").addV("person").property("name","vadas").property("age",27).as("vadas").addV("software").property("name","lop").property("lang","java").as("lop").addV("person").property("name","josh").property("age",32).as("josh").addV("software").property("name","ripple").property("lang","java").as("ripple").addV("person").property
 [...]
diff --git a/gremlin-python/src/main/python/radish/feature_steps.py 
b/gremlin-python/src/main/python/radish/feature_steps.py
index 4a4bc70..4227f56 100644
--- a/gremlin-python/src/main/python/radish/feature_steps.py
+++ b/gremlin-python/src/main/python/radish/feature_steps.py
@@ -23,7 +23,8 @@ from gremlin_python.statics import long
 from gremlin_python.structure.graph import Path, Vertex
 from gremlin_python.process.anonymous_traversal import traversal
 from gremlin_python.process.graph_traversal import __
-from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, 
Pop, Scope, Column, Order, Direction, T, Pick, Operator, IO, WithOptions
+from gremlin_python.process.traversal import Barrier, Cardinality, P, TextP, 
Pop, Scope, Column, Order, Direction, T, \
+    Pick, Operator, IO, WithOptions
 from radish import given, when, then, world
 from hamcrest import *
 
@@ -87,7 +88,7 @@ def translate_traversal(step):
     step.context.ignore = step.text in ignores
     p = step.context.traversal_params if hasattr(step.context, 
"traversal_params") else {}
     localg = step.context.g
-    tagset = [ tag.name for tag in step.all_tags ]
+    tagset = [tag.name for tag in step.all_tags]
 
     if not step.context.ignore:
         step.context.ignore = "AllowNullPropertyValues" in tagset
@@ -106,8 +107,9 @@ def iterate_the_traversal(step):
     try:
         step.context.result = list(map(lambda x: _convert_results(x), 
step.context.traversal.toList()))
         step.context.failed = False
-    except:
+    except Exception as e:
         step.context.failed = True
+        step.context.failed_message = getattr(e, 'message', repr(e))
 
 
 @when("iterated next")
@@ -118,8 +120,9 @@ def next_the_traversal(step):
     try:
         step.context.result = list(map(lambda x: _convert_results(x), 
step.context.traversal.next()))
         step.context.failed = False
-    except:
+    except Exception as e:
         step.context.failed = True
+        step.context.failed_message = getattr(e, 'message', repr(e))
 
 
 @then("the traversal will raise an error")
@@ -127,6 +130,20 @@ def raise_an_error(step):
     assert_that(step.context.failed, equal_to(True))
 
 
+@then("the traversal will raise an error with message {comparison:w} text of 
{expected_message:QuotedString}")
+def raise_an_error_with_message(step, comparison, expected_message):
+    assert_that(step.context.failed, equal_to(True))
+
+    if comparison == "containing":
+        assert_that(step.context.failed_message, 
contains_string(expected_message))
+    elif comparison == "ending":
+        assert_that(step.context.failed_message, ends_with(expected_message))
+    elif comparison == "starting":
+        assert_that(step.context.failed_message, starts_with(expected_message))
+    else:
+        raise ValueError("unknown comparison '" + comparison + "'- must be: 
containing, ending or starting")
+
+
 @then("the result should be {characterized_as:w}")
 def assert_result(step, characterized_as):
     if step.context.ignore:
@@ -134,13 +151,13 @@ def assert_result(step, characterized_as):
 
     assert_that(step.context.failed, equal_to(False))
 
-    if characterized_as == "empty":        # no results
+    if characterized_as == "empty":  # no results
         assert_that(len(step.context.result), equal_to(0))
-    elif characterized_as == "ordered":    # results asserted in the order of 
the data table
+    elif characterized_as == "ordered":  # results asserted in the order of 
the data table
         _table_assertion(step.table, step.context.result, step.context, True)
     elif characterized_as == "unordered":  # results asserted in any order
         _table_assertion(step.table, step.context.result, step.context, False)
-    elif characterized_as == "of":         # results may be of any of the 
specified items in the data table
+    elif characterized_as == "of":  # results may be of any of the specified 
items in the data table
         _any_assertion(step.table, step.context.result, step.context)
     else:
         raise ValueError("unknown data characterization of " + 
characterized_as)
@@ -177,55 +194,55 @@ def nothing_happening(step):
 
 def _convert(val, ctx):
     graph_name = ctx.graph_name
-    if isinstance(val, dict):                                            # 
convert dictionary keys/values
+    if isinstance(val, dict):  # convert dictionary keys/values
         n = {}
         for key, value in val.items():
             k = _convert(key, ctx)
             # convert to tuple key if list/set as neither are hashable
             n[tuple(k) if isinstance(k, (set, list)) else k] = _convert(value, 
ctx)
         return n
-    elif isinstance(val, str) and re.match(r"^l\[.*\]$", val):           # 
parse list
+    elif isinstance(val, str) and re.match(r"^l\[.*\]$", val):  # parse list
         return [] if val == "l[]" else list(map((lambda x: _convert(x, ctx)), 
val[2:-1].split(",")))
-    elif isinstance(val, str) and re.match(r"^s\[.*\]$", val):           # 
parse set
+    elif isinstance(val, str) and re.match(r"^s\[.*\]$", val):  # parse set
         return set() if val == "s[]" else set(map((lambda x: _convert(x, 
ctx)), val[2:-1].split(",")))
-    elif isinstance(val, str) and re.match(r"^d\[NaN\]$", val):          # 
parse nan
+    elif isinstance(val, str) and re.match(r"^d\[NaN\]$", val):  # parse nan
         return float("nan")
-    elif isinstance(val, str) and re.match(r"^d\[Infinity\]$", val):     # 
parse +inf
+    elif isinstance(val, str) and re.match(r"^d\[Infinity\]$", val):  # parse 
+inf
         return float("inf")
-    elif isinstance(val, str) and re.match(r"^d\[-Infinity\]$", val):    # 
parse -inf
+    elif isinstance(val, str) and re.match(r"^d\[-Infinity\]$", val):  # parse 
-inf
         return float("-inf")
     elif isinstance(val, str) and re.match(r"^d\[.*\]\.[bsilfdmn]$", val):  # 
parse numeric
         return float(val[2:-3]) if val[2:-3].__contains__(".") else 
long(val[2:-3])
-    elif isinstance(val, str) and re.match(r"^v\[.*\]\.id$", val):       # 
parse vertex id
+    elif isinstance(val, str) and re.match(r"^v\[.*\]\.id$", val):  # parse 
vertex id
         return __find_cached_element(ctx, graph_name, val[2:-4], "v").id
-    elif isinstance(val, str) and re.match(r"^v\[.*\]\.sid$", val):      # 
parse vertex id as string
+    elif isinstance(val, str) and re.match(r"^v\[.*\]\.sid$", val):  # parse 
vertex id as string
         return str(__find_cached_element(ctx, graph_name, val[2:-5], "v").id)
-    elif isinstance(val, str) and re.match(r"^v\[.*\]$", val):           # 
parse vertex
+    elif isinstance(val, str) and re.match(r"^v\[.*\]$", val):  # parse vertex
         return __find_cached_element(ctx, graph_name, val[2:-1], "v")
-    elif isinstance(val, str) and re.match(r"^e\[.*\]\.id$", val):       # 
parse edge id
+    elif isinstance(val, str) and re.match(r"^e\[.*\]\.id$", val):  # parse 
edge id
         return __find_cached_element(ctx, graph_name, val[2:-4], "e").id
-    elif isinstance(val, str) and re.match(r"^e\[.*\]\.sid$", val):      # 
parse edge id as string
+    elif isinstance(val, str) and re.match(r"^e\[.*\]\.sid$", val):  # parse 
edge id as string
         return str(__find_cached_element(ctx, graph_name, val[2:-5], "e").id)
-    elif isinstance(val, str) and re.match(r"^e\[.*\]$", val):           # 
parse edge
+    elif isinstance(val, str) and re.match(r"^e\[.*\]$", val):  # parse edge
         return __find_cached_element(ctx, graph_name, val[2:-1], "e")
-    elif isinstance(val, str) and re.match(r"^vp\[.*\]$", val):          # 
parse vertexproperty
+    elif isinstance(val, str) and re.match(r"^vp\[.*\]$", val):  # parse 
vertexproperty
         return __find_cached_element(ctx, graph_name, val[3:-1], "vp")
-    elif isinstance(val, str) and re.match(r"^m\[.*\]$", val):           # 
parse json as a map
+    elif isinstance(val, str) and re.match(r"^m\[.*\]$", val):  # parse json 
as a map
         return _convert(json.loads(val[2:-1]), ctx)
-    elif isinstance(val, str) and re.match(r"^p\[.*\]$", val):           # 
parse path
+    elif isinstance(val, str) and re.match(r"^p\[.*\]$", val):  # parse path
         path_objects = list(map((lambda x: _convert(x, ctx)), 
val[2:-1].split(",")))
         return Path([set([])], path_objects)
-    elif isinstance(val, str) and re.match(r"^c\[.*\]$", val):           # 
parse lambda/closure
+    elif isinstance(val, str) and re.match(r"^c\[.*\]$", val):  # parse 
lambda/closure
         return lambda: (val[2:-1], "gremlin-groovy")
-    elif isinstance(val, str) and re.match(r"^t\[.*\]$", val):           # 
parse instance of T enum
+    elif isinstance(val, str) and re.match(r"^t\[.*\]$", val):  # parse 
instance of T enum
         return T[val[2:-1]]
-    elif isinstance(val, str) and re.match(r"^D\[.*\]$", val):           # 
parse instance of Direction enum
+    elif isinstance(val, str) and re.match(r"^D\[.*\]$", val):  # parse 
instance of Direction enum
         return direction[__alias_direction(val[2:-1])]
-    elif isinstance(val, str) and re.match(r"^null$", val):              # 
parse null to None
+    elif isinstance(val, str) and re.match(r"^null$", val):  # parse null to 
None
         return None
-    elif isinstance(val, str) and re.match(r"^true$", val):              # 
parse to boolean
+    elif isinstance(val, str) and re.match(r"^true$", val):  # parse to boolean
         return True
-    elif isinstance(val, str) and re.match(r"^false$", val):             # 
parse to boolean
+    elif isinstance(val, str) and re.match(r"^false$", val):  # parse to 
boolean
         return False
     else:
         return val
diff --git a/gremlin-python/src/main/python/radish/gremlin.py 
b/gremlin-python/src/main/python/radish/gremlin.py
index a91dfad..7c779aa 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -316,6 +316,13 @@ world.gremlins = {
     'g_V_coworker_with_midV': [(lambda 
g:g.V().hasLabel('person').filter_(__.outE('created')).as_('p1').V().hasLabel('person').where(P.neq('p1')).filter_(__.outE('created')).as_('p2').map(__.out('created').where(__.in_('created').as_('p1')).name.fold()).group().by(__.select('p1').by('name')).by(__.group().by(__.select('p2').by('name')).by(__.project('numCoCreated','coCreated').by(__.count(Scope.local)).by())).unfold())],
 
     'g_V_playlist_paths': [(lambda 
g:g.V().has('name','Bob_Dylan').in_('sungBy').order().by('name').as_('a').repeat(__.out().order().by('name').simplePath().from_('a')).until(__.out('writtenBy').has('name','Johnny_Cash'))[0:1].as_('b').repeat(__.out().order().by('name').as_('c').simplePath().from_('b').to('c')).until(__.out('sungBy').has('name','Grateful_Dead'))[0:1].path().from_('a').unfold().project('song','artists').by('name').by(__.coalesce(__.out('sungBy','writtenBy').dedup().name.o
 [...]
     'g_V_shortestpath': [(lambda 
g:g.V().as_('v').both().as_('v').project('src','tgt','p').by(__.select(Pop.first,'v')).by(__.select(Pop.last,'v')).by(__.select(Pop.all_,'v')).as_('triple').group('x').by(__.select('src','tgt')).by(__.select('p').fold()).select('tgt').barrier().repeat(__.both().as_('v').project('src','tgt','p').by(__.select(Pop.first,'v')).by(__.select(Pop.last,'v')).by(__.select(Pop.all_,'v')).as_('t').filter_(__.select(Pop.all_,'p').count(Scope.local).as_('l').select(Po
 [...]
+    'g_withStrategiesXReadOnlyStrategyX_V': [(lambda 
g:g.withStrategies(*[TraversalStrategy('ReadOnlyStrategy', None, 
'org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy')]).V())],
 
+    'g_withStrategiesXReadOnlyStrategyX_V_outXknowsX_name': [(lambda 
g:g.withStrategies(*[TraversalStrategy('ReadOnlyStrategy', None, 
'org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy')]).V().out('knows').name)],
 
+    'g_withStrategiesXReadOnlyStrategyX_addVXpersonX': [(lambda 
g:g.withStrategies(*[TraversalStrategy('ReadOnlyStrategy', None, 
'org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy')]).addV('person'))],
 
+    'g_withStrategiesXReadOnlyStrategyX_addVXpersonX_fromXVX1XX_toXVX2XX': 
[(lambda g:g.withStrategies(*[TraversalStrategy('ReadOnlyStrategy', None, 
'org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy')]).addE('link').from_(__.V(1)).to(__.V(2)))],
 
+    'g_withStrategiesXReadOnlyStrategyX_V_addVXpersonX_fromXVX1XX': [(lambda 
g:g.withStrategies(*[TraversalStrategy('ReadOnlyStrategy', None, 
'org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy')]).V().addE('link').from_(__.V(1)))],
 
+    'g_withStrategiesXReadOnlyStrategyX_V_propertyXname_joshX': [(lambda 
g:g.withStrategies(*[TraversalStrategy('ReadOnlyStrategy', None, 
'org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy')]).V().property('name','josh'))],
 
+    'g_withStrategiesXReadOnlyStrategyX_E_propertyXweight_0X': [(lambda 
g:g.withStrategies(*[TraversalStrategy('ReadOnlyStrategy', None, 
'org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy')]).E().property('weight',0))],
 
     'g_V_classic_recommendation': [(lambda g:g.V().has('name','DARK 
STAR').as_('a').out('followedBy').aggregate('stash').in_('followedBy').where(P.neq('a').and_(P.without(['stash']))).groupCount().unfold().project('x','y','z').by(__.select(Column.keys).name).by(__.select(Column.keys).performances).by(__.select(Column.values)).order().by(__.select('z'),Order.desc).by(__.select('y'),Order.asc)[0:5].aggregate(Scope.local,'m').select('x'))],
 
     'g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX': [(lambda g, 
vid1=None:g.addV('person').property('name','marko').property('age',29).as_('marko').addV('person').property('name','vadas').property('age',27).as_('vadas').addV('software').property('name','lop').property('lang','java').as_('lop').addV('person').property('name','josh').property('age',32).as_('josh').addV('software').property('name','ripple').property('lang','java').as_('ripple').addV('person').property('name','peter').pro
 [...]
     'g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_2X': 
[(lambda g, 
vid1=None:g.addV('person').property('name','marko').property('age',29).as_('marko').addV('person').property('name','vadas').property('age',27).as_('vadas').addV('software').property('name','lop').property('lang','java').as_('lop').addV('person').property('name','josh').property('age',32).as_('josh').addV('software').property('name','ripple').property('lang','java').as_('ripple').addV('person').property(
 [...]
diff --git a/gremlin-test/features/filter/Coin.feature 
b/gremlin-test/features/filter/Coin.feature
index 8def707..802fa9d 100644
--- a/gremlin-test/features/filter/Coin.feature
+++ b/gremlin-test/features/filter/Coin.feature
@@ -43,7 +43,7 @@ Feature: Step - coin()
     When iterated to list
     Then the result should be empty
 
-  @GraphComputerVerificationStrategyNotSupported
+  @GraphComputerVerificationStrategyNotSupported @WithSeedStrategy
   Scenario: g_withStrategiesXSeedStrategyX_V_coinX50X
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/filter/Dedup.feature 
b/gremlin-test/features/filter/Dedup.feature
index 8136730..e93d1d7 100644
--- a/gremlin-test/features/filter/Dedup.feature
+++ b/gremlin-test/features/filter/Dedup.feature
@@ -313,6 +313,7 @@ Feature: Step - dedup()
       | marko |
       | josh  |
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_order_byXname_descX_barrier_dedup_age_name
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/filter/Sample.feature 
b/gremlin-test/features/filter/Sample.feature
index 3b807e8..8a8bee7 100644
--- a/gremlin-test/features/filter/Sample.feature
+++ b/gremlin-test/features/filter/Sample.feature
@@ -45,6 +45,7 @@ Feature: Step - sample()
     When iterated to list
     Then the result should have a count of 3
 
+  @WithSeedStrategy
   Scenario: 
g_withStrategiesXSeedStrategyX_V_group_byXlabelX_byXbothE_weight_order_sampleX2X_foldXunfold
     Given the modern graph
     And the traversal of
@@ -57,6 +58,7 @@ Feature: Step - sample()
       | m[{"software":"l[d[1.0].d,d[0.4].d]"}] |
       | m[{"person":"l[d[0.5].d,d[1.0].d]"}] |
 
+  @WithSeedStrategy
   Scenario: 
g_withStrategiesXSeedStrategyX_V_group_byXlabelX_byXbothE_weight_order_fold_sampleXlocal_5XXunfold
     Given the modern graph
     And the traversal of
@@ -69,6 +71,7 @@ Feature: Step - sample()
       | m[{"software":"l[d[0.2].d,d[0.4].d,d[0.4].d,d[1.0].d]"}] |
       | m[{"person":"l[d[0.5].d,d[1.0].d,d[0.4].d,d[0.2].d,d[1.0].d]"}] |
 
+  @WithSeedStrategy
   Scenario: 
g_withStrategiesXSeedStrategyX_V_order_byXlabel_descX_sampleX1X_byXageX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/filter/Where.feature 
b/gremlin-test/features/filter/Where.feature
index 137ed5c..e8e102b 100644
--- a/gremlin-test/features/filter/Where.feature
+++ b/gremlin-test/features/filter/Where.feature
@@ -345,7 +345,7 @@ Feature: Step - where()
     Then the result should be empty
 
   # comparison of null "age" values in where()
-  @GraphComputerVerificationReferenceOnly
+  @GraphComputerVerificationReferenceOnly @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_VX3X_asXaX_in_out_asXbX_whereXa_eqXbXX_byXageX_name
     Given the modern graph
     And using the parameter vid3 defined as "v[lop].id"
diff --git a/gremlin-test/features/integrated/ReadOnlyStrategy.feature 
b/gremlin-test/features/integrated/ReadOnlyStrategy.feature
new file mode 100644
index 0000000..9feaba4
--- /dev/null
+++ b/gremlin-test/features/integrated/ReadOnlyStrategy.feature
@@ -0,0 +1,100 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+@StepClassIntegrated
+Feature: Step - ReadOnlyStrategy
+
+  @WithReadOnlyStrategy
+  Scenario: g_withStrategiesXReadOnlyStrategyX_V
+    Given the modern graph
+    And the traversal of
+      """
+      g.withStrategies(ReadOnlyStrategy).V()
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[marko] |
+      | v[vadas] |
+      | v[lop] |
+      | v[josh] |
+      | v[ripple] |
+      | v[peter]  |
+
+  @WithReadOnlyStrategy
+  Scenario: g_withStrategiesXReadOnlyStrategyX_V_outXknowsX_name
+    Given the modern graph
+    And the traversal of
+      """
+      g.withStrategies(ReadOnlyStrategy).V().out("knows").values("name")
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | vadas |
+      | josh |
+
+  @WithReadOnlyStrategy
+  Scenario: g_withStrategiesXReadOnlyStrategyX_addVXpersonX
+    Given the empty graph
+    And the traversal of
+      """
+      g.withStrategies(ReadOnlyStrategy).addV("person")
+      """
+    When iterated to list
+    Then the traversal will raise an error with message containing text of 
"The provided traversal has a mutating step and thus is not read only"
+
+  @WithReadOnlyStrategy
+  Scenario: g_withStrategiesXReadOnlyStrategyX_addVXpersonX_fromXVX1XX_toXVX2XX
+    Given the empty graph
+    And the traversal of
+      """
+      g.withStrategies(ReadOnlyStrategy).addE("link").from(__.V(1)).to(__.V(2))
+      """
+    When iterated to list
+    Then the traversal will raise an error with message containing text of 
"The provided traversal has a mutating step and thus is not read only"
+
+  @WithReadOnlyStrategy
+  Scenario: g_withStrategiesXReadOnlyStrategyX_V_addVXpersonX_fromXVX1XX
+    Given the empty graph
+    And the traversal of
+      """
+      g.withStrategies(ReadOnlyStrategy).V().addE("link").from(__.V(1))
+      """
+    When iterated to list
+    Then the traversal will raise an error with message containing text of 
"The provided traversal has a mutating step and thus is not read only"
+
+  @WithReadOnlyStrategy
+  Scenario: g_withStrategiesXReadOnlyStrategyX_V_propertyXname_joshX
+    Given the empty graph
+    And the traversal of
+      """
+      g.withStrategies(ReadOnlyStrategy).V().property("name","josh")
+      """
+    When iterated to list
+    Then the traversal will raise an error with message containing text of 
"The provided traversal has a mutating step and thus is not read only"
+
+  @WithReadOnlyStrategy
+  Scenario: g_withStrategiesXReadOnlyStrategyX_E_propertyXweight_0X
+    Given the empty graph
+    And the traversal of
+      """
+      g.withStrategies(ReadOnlyStrategy).E().property("weight", 0)
+      """
+    When iterated to list
+    Then the traversal will raise an error with message containing text of 
"The provided traversal has a mutating step and thus is not read only"
+
diff --git a/gremlin-test/features/map/Max.feature 
b/gremlin-test/features/map/Max.feature
index ef5a127..38fb7db 100644
--- a/gremlin-test/features/map/Max.feature
+++ b/gremlin-test/features/map/Max.feature
@@ -71,6 +71,7 @@ Feature: Step - max()
       | result |
       | d[35].i |
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_capXaX_maxXlocalX
     Given the modern graph
     And the traversal of
@@ -93,6 +94,7 @@ Feature: Step - max()
       | result |
       | d[35].i |
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_capXaX_unfold_max
     Given the modern graph
     And the traversal of
@@ -113,6 +115,7 @@ Feature: Step - max()
     When iterated to list
     Then the result should be empty
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_maxXlocalX
     Given the modern graph
     And the traversal of
@@ -133,6 +136,7 @@ Feature: Step - max()
     When iterated to list
     Then the result should be empty
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_unfold_max
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/map/Mean.feature 
b/gremlin-test/features/map/Mean.feature
index 1b1de2f..7190ac4 100644
--- a/gremlin-test/features/map/Mean.feature
+++ b/gremlin-test/features/map/Mean.feature
@@ -82,6 +82,7 @@ Feature: Step - mean()
       | d[30.75].d |
 
   # null values are ignored in mean() which is similar to sum aggregation 
works in SQL
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_meanXlocalX
     Given the modern graph
     And the traversal of
@@ -106,6 +107,7 @@ Feature: Step - mean()
       | d[30.75].d |
 
   # null values are ignored in mean() which is similar to sum aggregation 
works in SQL
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_capXaX_unfold_mean
     Given the modern graph
     And the traversal of
@@ -128,6 +130,7 @@ Feature: Step - mean()
     Then the result should be empty
 
   # if all values are null then the result is null
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_meanXlocalX
     Given the modern graph
     And the traversal of
@@ -150,6 +153,7 @@ Feature: Step - mean()
     Then the result should be empty
 
   # if all values are null then the result is null
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_unfold_mean
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/map/Min.feature 
b/gremlin-test/features/map/Min.feature
index 3146aa9..3134809 100644
--- a/gremlin-test/features/map/Min.feature
+++ b/gremlin-test/features/map/Min.feature
@@ -71,6 +71,7 @@ Feature: Step - min()
       | result |
       | d[27].i |
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_capXaX_minXlocalX
     Given the modern graph
     And the traversal of
@@ -93,6 +94,7 @@ Feature: Step - min()
       | result |
       | d[27].i |
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_capXaX_unfold_min
     Given the modern graph
     And the traversal of
@@ -113,6 +115,7 @@ Feature: Step - min()
     When iterated to list
     Then the result should be empty
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_minXlocalX
     Given the modern graph
     And the traversal of
@@ -133,6 +136,7 @@ Feature: Step - min()
     When iterated to list
     Then the result should be empty
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_unfold_min
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/map/Order.feature 
b/gremlin-test/features/map/Order.feature
index e1298e6..84b4d61 100644
--- a/gremlin-test/features/map/Order.feature
+++ b/gremlin-test/features/map/Order.feature
@@ -368,6 +368,7 @@ Feature: Step - order()
       | v[peter] |
 
   # tests order().by() where a property isn't present to ensure null comes 
first
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_orXhasLabelXpersonX_hasXsoftware_name_lopXX_order_byXageX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/map/Path.feature 
b/gremlin-test/features/map/Path.feature
index a5e5605..3563cd7 100644
--- a/gremlin-test/features/map/Path.feature
+++ b/gremlin-test/features/map/Path.feature
@@ -123,7 +123,7 @@ Feature: Step - path()
       | p[d[29].i,d[27].i] |
       | p[d[29].i,d[32].i] |
 
-  @GraphComputerVerificationReferenceOnly
+  @GraphComputerVerificationReferenceOnly @WithProductiveByStrategy
   Scenario: g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
diff --git a/gremlin-test/features/map/Project.feature 
b/gremlin-test/features/map/Project.feature
index 645450f..0e6e875 100644
--- a/gremlin-test/features/map/Project.feature
+++ b/gremlin-test/features/map/Project.feature
@@ -89,6 +89,7 @@ Feature: Step - project()
       | m[{"a":"d[1].l", "b":"d[32].i"}] |
       | m[{"a":"d[0].l", "b":"d[35].i"}] |
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_projectXa_bX_byXinE_countX_byXageX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/map/Select.feature 
b/gremlin-test/features/map/Select.feature
index de599f2..b5dabb5 100644
--- a/gremlin-test/features/map/Select.feature
+++ b/gremlin-test/features/map/Select.feature
@@ -847,7 +847,7 @@ Feature: Step - select()
       | m[{"a":"d[32].i","n":"josh"}] |
       | m[{"a":"d[35].i","n":"peter"}] |
 
-  @GraphComputerVerificationReferenceOnly
+  @GraphComputerVerificationReferenceOnly @WithProductiveByStrategy
   Scenario: g_withStrategiesXProductiveByStrategyX_V_asXaX_selectXaX_byXageX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/map/Sum.feature 
b/gremlin-test/features/map/Sum.feature
index cf1c3c9..b2f23ac 100644
--- a/gremlin-test/features/map/Sum.feature
+++ b/gremlin-test/features/map/Sum.feature
@@ -82,6 +82,7 @@ Feature: Step - sum()
       | d[123].i |
 
   # null values are ignored in sum() which is similar to sum aggregation works 
in SQL
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_sumXlocalX
     Given the modern graph
     And the traversal of
@@ -106,6 +107,7 @@ Feature: Step - sum()
       | d[123].l |
 
   # null values are ignored in sum() which is similar to sum aggregation works 
in SQL
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_capXaX_unfold_sum
     Given the modern graph
     And the traversal of
@@ -127,6 +129,7 @@ Feature: Step - sum()
     Then the result should be empty
 
   # if all values are null then the result is null
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_sumXlocalX
     Given the modern graph
     And the traversal of
@@ -149,6 +152,7 @@ Feature: Step - sum()
     Then the result should be empty
 
   # if all values are null then the result is null
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXfooX_capXaX_unfold_sum
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/sideEffect/Aggregate.feature 
b/gremlin-test/features/sideEffect/Aggregate.feature
index b3de3ce..5f8287b 100644
--- a/gremlin-test/features/sideEffect/Aggregate.feature
+++ b/gremlin-test/features/sideEffect/Aggregate.feature
@@ -124,6 +124,7 @@ Feature: Step - aggregate()
       | d[32].i |
       | d[35].i |
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXlocal_xX_byXageX_capXxX
     Given the modern graph
     And the traversal of
@@ -222,6 +223,7 @@ Feature: Step - aggregate()
       | d[32].i |
       | d[35].i |
 
+  @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXxX_byXvaluesXageX_isXgtX29XXX_capXxX
     Given the modern graph
     And the traversal of
@@ -252,7 +254,7 @@ Feature: Step - aggregate()
       | v[lop] |
       | v[lop] |
 
-  @GraphComputerVerificationReferenceOnly
+  @GraphComputerVerificationReferenceOnly @WithProductiveByStrategy
   Scenario: 
g_withStrategiesXProductiveByStrategyX_V_aggregateXxX_byXout_order_byXnameXX_capXxX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/sideEffect/Fail.feature 
b/gremlin-test/features/sideEffect/Fail.feature
index 53f2d56..0820f82 100644
--- a/gremlin-test/features/sideEffect/Fail.feature
+++ b/gremlin-test/features/sideEffect/Fail.feature
@@ -34,7 +34,7 @@ Feature: Step - fail()
       g.V().fail("msg")
       """
     When iterated to list
-    Then the traversal will raise an error
+    Then the traversal will raise an error with message containing text of 
"msg"
 
   Scenario: g_V_unionXout_failX
     Given the modern graph
diff --git a/gremlin-test/features/sideEffect/Group.feature 
b/gremlin-test/features/sideEffect/Group.feature
index f4937cd..429f9ad 100644
--- a/gremlin-test/features/sideEffect/Group.feature
+++ b/gremlin-test/features/sideEffect/Group.feature
@@ -40,6 +40,7 @@ Feature: Step - group()
       | result |
       | m[{"d[35].i":"l[v[peter]]", "d[27].i":"l[v[vadas]]", "d[32].i": 
"l[v[josh]]", "d[29].i":"l[v[marko]]"}] |
 
+  @WithProductiveByStrategy
   Scenario: g_withStrategiesXProductiveByStrategyX_V_group_byXageX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/features/sideEffect/GroupCount.feature 
b/gremlin-test/features/sideEffect/GroupCount.feature
index e87d253..8298d21 100644
--- a/gremlin-test/features/sideEffect/GroupCount.feature
+++ b/gremlin-test/features/sideEffect/GroupCount.feature
@@ -40,6 +40,7 @@ Feature: Step - groupCount()
       | result |
       | m[{"d[27].i":"d[1].l", "d[29].i":"d[1].l", "d[32].i":"d[1].l", 
"d[35].i":"d[1].l"}] |
 
+  @WithProductiveByStrategy
   Scenario: g_withStrategiesXProductiveByStrategyX_V_groupCount_byXageX
     Given the modern graph
     And the traversal of
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 bfec6f9..54150ea 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
@@ -57,6 +57,9 @@ import static 
org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInA
 import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
 import static org.hamcrest.core.Every.everyItem;
 import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.hamcrest.core.StringEndsWith.endsWith;
+import static org.hamcrest.core.StringStartsWith.startsWith;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
@@ -370,6 +373,29 @@ public final class StepDefinition {
         error = null;
     }
 
+    @Then("the traversal will raise an error with message {word} text of 
{string}")
+    public void theTraversalWillRaiseAnErrorWithMessage(final String 
comparison, final String expectedMessage) {
+        assertNotNull(error);
+
+        switch (comparison) {
+            case "containing":
+                assertThat(error.getMessage(), 
containsString(expectedMessage));
+                break;
+            case "starting":
+                assertThat(error.getMessage(), startsWith(expectedMessage));
+                break;
+            case "ending":
+                assertThat(error.getMessage(), endsWith(expectedMessage));
+                break;
+            default:
+                throw new IllegalStateException(String.format(
+                        "Unknown comparison of %s - must be one of: 
containing, starting or ending", comparison));
+        }
+
+        // consume the error now that it has been asserted
+        error = null;
+    }
+
     //////////////////////////////////////////////
 
     @Given("an unsupported test")
diff --git 
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedComputerSuite.java
 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedComputerSuite.java
index bf3f357..24641f0 100644
--- 
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedComputerSuite.java
+++ 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedComputerSuite.java
@@ -138,8 +138,6 @@ public class ProcessLimitedComputerSuite extends 
AbstractGremlinSuite {
             TranslationStrategyProcessTest.class,
 
             // decorations
-            ReadOnlyStrategyProcessTest.class,
-            SeedStrategyProcessTest.class,
             SubgraphStrategyProcessTest.class,
 
             // optimizations
diff --git 
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
index b84cea2..19978db 100644
--- 
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
+++ 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
@@ -79,18 +79,12 @@ public class ProcessLimitedStandardSuite extends 
AbstractGremlinSuite {
             // decorations
             ElementIdStrategyProcessTest.class,
             EventStrategyProcessTest.class,
-            ReadOnlyStrategyProcessTest.class,
             PartitionStrategyProcessTest.class,
-            SeedStrategyProcessTest.class,
             SubgraphStrategyProcessTest.class,
 
             // optimizations
             IncidentToAdjacentStrategyProcessTest.class,
             EarlyLimitStrategyProcessTest.class,
-
-            // semantics
-            OrderabilityTest.Traversals.class,
-            TernaryBooleanLogicsTest.class,
     };
 
     /**

Reply via email to