Add tests and changelog for TINKERPOP-2931 CTR
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 2e26688..94e3507 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -36,6 +36,7 @@
 * Improved performance of comparison (equals) between not compatible types and nulls.
 * Fixed MergeV/MergeE steps to work when onCreate is immutable map.
 * Introduced `Writing` and `Deleting` marker interfaces to identify whether a step can perform write or delete or both on Graph.
+* For mergeV/mergeE, added checks for illegal hidden keys and refactored searchVertices to allow subclasses to override search criteria.
 * Added static map capturing possible Traversal steps that shall be added to traversal for a given operator
 * Fixed bug which caused some traversals to throw `GremlinTypeErrorException` to users.
 
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
index cc75190..f5d6406 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
@@ -104,4 +104,60 @@
 
         assertEquals(CollectionFactory.asMap("key1", "value1", "key2", "value2"), onCreateMap);
     }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldFailToValidateWithNullKey() {
+        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+                null, "person");
+        MergeVertexStep.validateMapInput(m, true);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldFailToValidateWithNullLabelValue() {
+        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+                T.label, null);
+        MergeVertexStep.validateMapInput(m, false);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldFailToValidateWithObjectAsLabelValue() {
+        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+                T.label, new Object());
+        MergeVertexStep.validateMapInput(m, false);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldFailToValidateWithNullIdValue() {
+        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+                T.id, null);
+        MergeVertexStep.validateMapInput(m, false);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldFailToValidateWithNullDirectionValue() {
+        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+                Direction.IN, null);
+        MergeVertexStep.validateMapInput(m, false);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldFailToValidateWithHiddenIdKey() {
+        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+                "~id", 10000);
+        MergeVertexStep.validateMapInput(m, false);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldFailToValidateWithHiddenLabelKey() {
+        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+                "~label", "person");
+        MergeVertexStep.validateMapInput(m, false);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldFailToValidateWithHiddenLabelValue() {
+        final Map<Object,Object> m = CollectionFactory.asMap("k", "v",
+                T.label, "~person");
+        MergeVertexStep.validateMapInput(m, false);
+    }
 }
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index dfc35d0..6580b19 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -698,6 +698,15 @@
                {"g_mergeV_onCreate_inheritance_new_2", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx2"]), (g,p) =>g.V(), (g,p) =>g.V(1).Has("person","name","mike")}}, 
                {"g_mergeV_label_override_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx2"])}}, 
                {"g_mergeV_id_override_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx2"])}}, 
+               {"g_mergeV_hidden_id_key_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"])}}, 
+               {"g_mergeV_hidden_label_key_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"])}}, 
+               {"g_mergeV_hidden_label_value_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeV((IDictionary<object,object>) p["xx1"])}}, 
+               {"g_mergeV_hidden_id_key_onCreate_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx1"])}}, 
+               {"g_mergeV_hidden_label_key_onCreate_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx1"])}}, 
+               {"g_mergeV_hidden_label_value_onCreate_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnCreate, (IDictionary<object,object>) p["xx1"])}}, 
+               {"g_mergeV_hidden_id_key_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, 
+               {"g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, 
+               {"g_mergeV_hidden_label_value_onMatch_matched_prohibited", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("vertex"), (g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> {}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, 
                {"g_V_age_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Min<object>()}}, 
                {"g_V_foo_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Min<object>()}}, 
                {"g_V_name_min", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Min<object>()}}, 
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 4ae84dc..1444206 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -669,6 +669,15 @@
     "g_mergeV_onCreate_inheritance_new_2": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(p["xx1"]).Option(gremlingo.Merge.OnCreate, p["xx2"])}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V()}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(1).Has("person", "name", "mike")}}, 
     "g_mergeV_label_override_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(p["xx1"]).Option(gremlingo.Merge.OnCreate, p["xx2"])}}, 
     "g_mergeV_id_override_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(p["xx1"]).Option(gremlingo.Merge.OnCreate, p["xx2"])}}, 
+    "g_mergeV_hidden_id_key_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(p["xx1"])}}, 
+    "g_mergeV_hidden_label_key_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(p["xx1"])}}, 
+    "g_mergeV_hidden_label_value_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(p["xx1"])}}, 
+    "g_mergeV_hidden_id_key_onCreate_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnCreate, p["xx1"])}}, 
+    "g_mergeV_hidden_label_key_onCreate_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnCreate, p["xx1"])}}, 
+    "g_mergeV_hidden_label_value_onCreate_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnCreate, p["xx1"])}}, 
+    "g_mergeV_hidden_id_key_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, 
+    "g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, 
+    "g_mergeV_hidden_label_value_onMatch_matched_prohibited": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("vertex")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, p["xx1"])}}, 
     "g_V_age_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Min()}}, 
     "g_V_foo_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("foo").Min()}}, 
     "g_V_name_min": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("name").Min()}}, 
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 e5d0a51..513042b 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
@@ -688,6 +688,15 @@
     g_mergeV_onCreate_inheritance_new_2: [function({g, xx1, xx2}) { return g.mergeV(xx1).option(Merge.onCreate,xx2) }, function({g, xx1, xx2}) { return g.V() }, function({g, xx1, xx2}) { return g.V(1).has("person","name","mike") }], 
     g_mergeV_label_override_prohibited: [function({g, xx1, xx2}) { return g.mergeV(xx1).option(Merge.onCreate,xx2) }], 
     g_mergeV_id_override_prohibited: [function({g, xx1, xx2}) { return g.mergeV(xx1).option(Merge.onCreate,xx2) }], 
+    g_mergeV_hidden_id_key_prohibited: [function({g, xx1}) { return g.mergeV(xx1) }], 
+    g_mergeV_hidden_label_key_prohibited: [function({g, xx1}) { return g.mergeV(xx1) }], 
+    g_mergeV_hidden_label_value_prohibited: [function({g, xx1}) { return g.mergeV(xx1) }], 
+    g_mergeV_hidden_id_key_onCreate_prohibited: [function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onCreate,xx1) }], 
+    g_mergeV_hidden_label_key_onCreate_prohibited: [function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onCreate,xx1) }], 
+    g_mergeV_hidden_label_value_onCreate_prohibited: [function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onCreate,xx1) }], 
+    g_mergeV_hidden_id_key_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], 
+    g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], 
+    g_mergeV_hidden_label_value_onMatch_matched_prohibited: [function({g, xx1}) { return g.addV("vertex") }, function({g, xx1}) { return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], 
     g_V_age_min: [function({g}) { return g.V().values("age").min() }], 
     g_V_foo_min: [function({g}) { return g.V().values("foo").min() }], 
     g_V_name_min: [function({g}) { return g.V().values("name").min() }], 
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 188321d..998289b 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -670,6 +670,15 @@
     'g_mergeV_onCreate_inheritance_new_2': [(lambda g, xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_create,xx2)), (lambda g, xx1=None,xx2=None:g.V()), (lambda g, xx1=None,xx2=None:g.V(1).has('person','name','mike'))], 
     'g_mergeV_label_override_prohibited': [(lambda g, xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_create,xx2))], 
     'g_mergeV_id_override_prohibited': [(lambda g, xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_create,xx2))], 
+    'g_mergeV_hidden_id_key_prohibited': [(lambda g, xx1=None:g.merge_v(xx1))], 
+    'g_mergeV_hidden_label_key_prohibited': [(lambda g, xx1=None:g.merge_v(xx1))], 
+    'g_mergeV_hidden_label_value_prohibited': [(lambda g, xx1=None:g.merge_v(xx1))], 
+    'g_mergeV_hidden_id_key_onCreate_prohibited': [(lambda g, xx1=None:g.merge_v({}).option(Merge.on_create,xx1))], 
+    'g_mergeV_hidden_label_key_onCreate_prohibited': [(lambda g, xx1=None:g.merge_v({}).option(Merge.on_create,xx1))], 
+    'g_mergeV_hidden_label_value_onCreate_prohibited': [(lambda g, xx1=None:g.merge_v({}).option(Merge.on_create,xx1))], 
+    'g_mergeV_hidden_id_key_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 
+    'g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 
+    'g_mergeV_hidden_label_value_onMatch_matched_prohibited': [(lambda g, xx1=None:g.addV('vertex')), (lambda g, xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 
     'g_V_age_min': [(lambda g:g.V().age.min_())], 
     'g_V_foo_min': [(lambda g:g.V().foo.min_())], 
     'g_V_name_min': [(lambda g:g.V().name.min_())], 
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
index 860918b..91de838 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
@@ -732,3 +732,113 @@
     When iterated to list
     Then the traversal will raise an error
 
+  # cannot use hidden namespace for id key
+  Scenario: g_mergeV_hidden_id_key_prohibited
+    Given the empty graph
+    And using the parameter xx1 defined as "m[{\"~id\": 1}]"
+    And the traversal of
+      """
+      g.mergeV(xx1)
+      """
+    When iterated to list
+    Then the traversal will raise an error
+
+  # cannot use hidden namespace for label key
+  Scenario: g_mergeV_hidden_label_key_prohibited
+    Given the empty graph
+    And using the parameter xx1 defined as "m[{\"~label\":\"vertex\"}]"
+    And the traversal of
+      """
+      g.mergeV(xx1)
+      """
+    When iterated to list
+    Then the traversal will raise an error
+
+  # cannot use hidden namespace for label value
+  Scenario: g_mergeV_hidden_label_value_prohibited
+    Given the empty graph
+    And using the parameter xx1 defined as "m[{\"t[label]\":\"~vertex\"}]"
+    And the traversal of
+      """
+      g.mergeV(xx1)
+      """
+    When iterated to list
+    Then the traversal will raise an error
+
+  # cannot use hidden namespace for id key for onCreate
+  Scenario: g_mergeV_hidden_id_key_onCreate_prohibited
+    Given the empty graph
+    And using the parameter xx1 defined as "m[{\"~id\": 1}]"
+    And the traversal of
+      """
+      g.mergeV([:]).option(Merge.onCreate, xx1)
+      """
+    When iterated to list
+    Then the traversal will raise an error
+
+  # cannot use hidden namespace for label key for onCreate
+  Scenario: g_mergeV_hidden_label_key_onCreate_prohibited
+    Given the empty graph
+    And using the parameter xx1 defined as "m[{\"~label\":\"vertex\"}]"
+    And the traversal of
+      """
+      g.mergeV([:]).option(Merge.onCreate, xx1)
+      """
+    When iterated to list
+    Then the traversal will raise an error
+
+  # cannot use hidden namespace for label value for onCreate
+  Scenario: g_mergeV_hidden_label_value_onCreate_prohibited
+    Given the empty graph
+    And using the parameter xx1 defined as "m[{\"t[label]\":\"~vertex\"}]"
+    And the traversal of
+      """
+      g.mergeV([:]).option(Merge.onCreate, xx1)
+      """
+    When iterated to list
+    Then the traversal will raise an error
+
+  # cannot use hidden namespace for id key for onMatch
+  Scenario: g_mergeV_hidden_id_key_onMatch_matched_prohibited
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("vertex")
+      """
+    And using the parameter xx1 defined as "m[{\"~id\": 1}]"
+    And the traversal of
+      """
+      g.mergeV([:]).option(Merge.onMatch, xx1)
+      """
+    When iterated to list
+    Then the traversal will raise an error
+
+  # cannot use hidden namespace for label key for onMatch
+  Scenario: g_mergeV_hidden_label_key_matched_onMatch_matched_prohibited
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("vertex")
+      """
+    And using the parameter xx1 defined as "m[{\"~label\":\"vertex\"}]"
+    And the traversal of
+      """
+      g.mergeV([:]).option(Merge.onMatch, xx1)
+      """
+    When iterated to list
+    Then the traversal will raise an error
+
+  # cannot use hidden namespace for label value for onMatch
+  Scenario: g_mergeV_hidden_label_value_onMatch_matched_prohibited
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("vertex")
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\":\"~vertex\"}]"
+    And the traversal of
+      """
+      g.mergeV([:]).option(Merge.onMatch, xx1)
+      """
+    When iterated to list
+    Then the traversal will raise an error