Gherkin-based test runner
Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/d000fce0 Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/d000fce0 Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/d000fce0 Branch: refs/heads/TINKERPOP-1827 Commit: d000fce0006552f537cb3e580f4c70c60c895e88 Parents: e03cbd2 Author: Jorge Bay Gondra <jorgebaygon...@gmail.com> Authored: Mon Sep 25 17:27:13 2017 +0200 Committer: Jorge Bay Gondra <jorgebaygon...@gmail.com> Committed: Tue Nov 21 11:46:17 2017 +0100 ---------------------------------------------------------------------- .../Gherkin/Attributes/BddAttribute.cs | 37 ++ .../Gherkin/Attributes/GivenAttribute.cs | 33 ++ .../Gherkin/Attributes/ThenAttribute.cs | 33 ++ .../Gherkin/Attributes/WhenAttribute.cs | 33 ++ .../Gherkin/CommonSteps.cs | 156 +++++++++ .../Gherkin/GherkinTestRunner.cs | 335 +++++++++++++++++++ .../Gherkin/StepDefinition.cs | 39 +++ .../Gherkin/TraversalTranslations.cs | 102 ++++++ .../Gremlin.Net.IntegrationTest.csproj | 13 +- .../RemoteConnectionFactory.cs | 18 +- 10 files changed, 790 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d000fce0/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/BddAttribute.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/BddAttribute.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/BddAttribute.cs new file mode 100644 index 0000000..1e9a242 --- /dev/null +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/BddAttribute.cs @@ -0,0 +1,37 @@ +#region License + +/* + * 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. + */ + +#endregion + +using System; + +namespace Gremlin.Net.IntegrationTest.Gherkin.Attributes +{ + internal class BddAttribute : Attribute + { + public string Message { get; } + + internal BddAttribute(string message) + { + Message = message; + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d000fce0/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/GivenAttribute.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/GivenAttribute.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/GivenAttribute.cs new file mode 100644 index 0000000..7266145 --- /dev/null +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/GivenAttribute.cs @@ -0,0 +1,33 @@ +#region License + +/* + * 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. + */ + +#endregion + +namespace Gremlin.Net.IntegrationTest.Gherkin.Attributes +{ + internal class GivenAttribute : BddAttribute + { + public GivenAttribute(string message) : base(message) + { + + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d000fce0/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/ThenAttribute.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/ThenAttribute.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/ThenAttribute.cs new file mode 100644 index 0000000..25e1932 --- /dev/null +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/ThenAttribute.cs @@ -0,0 +1,33 @@ +#region License + +/* + * 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. + */ + +#endregion + +namespace Gremlin.Net.IntegrationTest.Gherkin.Attributes +{ + internal class ThenAttribute : BddAttribute + { + public ThenAttribute(string message) : base(message) + { + + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d000fce0/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/WhenAttribute.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/WhenAttribute.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/WhenAttribute.cs new file mode 100644 index 0000000..26286c6 --- /dev/null +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/WhenAttribute.cs @@ -0,0 +1,33 @@ +#region License + +/* + * 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. + */ + +#endregion + +namespace Gremlin.Net.IntegrationTest.Gherkin.Attributes +{ + internal class WhenAttribute : BddAttribute + { + public WhenAttribute(string message) : base(message) + { + + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d000fce0/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs new file mode 100644 index 0000000..50ea9b8 --- /dev/null +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs @@ -0,0 +1,156 @@ +#region License + +/* + * 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. + */ + +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using System.Runtime.CompilerServices; +using Gherkin.Ast; +using Gremlin.Net.IntegrationTest.Gherkin.Attributes; +using Gremlin.Net.Process.Traversal; +using Gremlin.Net.Structure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Gremlin.Net.IntegrationTest.Gherkin +{ + internal class GeneralDefinitions : StepDefinition + { + private GraphTraversalSource _g; + private dynamic _traversal; + private object[] _result; + + private static readonly IDictionary<string, Func<GraphTraversalSource, ITraversal>> FixedTranslations = + new Dictionary<string, Func<GraphTraversalSource, ITraversal>> + { + { "g.V().has(\"no\").count()", g => g.V().Has("no").Count() } + }; + + [Given("the modern graph")] + public void ChooseModernGraph() + { + var connection = ConnectionFactory.CreateRemoteConnection(); + _g = new Graph().Traversal().WithRemote(connection); + } + + [When("iterated to list")] + public void IterateToList() + { + if (!(_traversal is ITraversal)) + { + throw new InvalidOperationException("Traversal should be set before iterating"); + } + IEnumerable enumerable = _traversal.ToList(); + _result = enumerable.Cast<object>().ToArray(); + } + + [Given("the traversal of")] + public void TranslateTraversal(string traversalText) + { + if (_g == null) + { + throw new InvalidOperationException("g should be a traversal source"); + } + _traversal = TraversalTranslations.GetTraversal(traversalText, _g); + } + + [Then("the result should be (\\w+)")] + public void AssertResult(string characterizedAs, DataTable table) + { + TableRow[] rows; + switch (characterizedAs) + { + case "empty": + Assert.Equal(0, _result.Length); + return; + case "ordered": + rows = table.Rows.ToArray(); + Assert.Equal(rows.Length, _result.Length); + for (var i = 0; i < rows.Length; i++) + { + var row = rows[i]; + var cells = row.Cells.ToArray(); + var typeName = cells[0].Value; + var expectedValue = ConvertExpectedToType(typeName, cells[1].Value); + var resultItem = ConvertResultItem(typeName, _result[i]); + Assert.Equal(expectedValue, resultItem); + } + break; + case "unordered": + rows = table.Rows.ToArray(); + Assert.Equal(rows.Length, _result.Length); + foreach (var row in rows) + { + var cells = row.Cells.ToArray(); + var typeName = cells[0].Value; + var expectedValue = ConvertExpectedToType(typeName, cells[1].Value); + // Convert all the values in the result to the type + var convertedResult = _result.Select(item => ConvertResultItem(typeName, item)); + Assert.Contains(expectedValue, convertedResult); + } + break; + default: + throw new NotSupportedException($"Result as '{characterizedAs}' not supported"); + } + } + + private object ConvertResultItem(string typeName, object value) + { + if (typeName == "map") + { + // We need to convert the original typed value into IDictionary<string, string> + return StringMap( + Assert.IsAssignableFrom<IDictionary>(value)); + } + return value; + } + + private IDictionary<string, string> StringMap(IDictionary originalMap) + { + var result = new Dictionary<string, string>(originalMap.Count); + foreach (var key in originalMap.Keys) + { + result.Add(key.ToString(), originalMap[key]?.ToString()); + } + return result; + } + + private object ConvertExpectedToType(string typeName, string stringValue) + { + switch (typeName) + { + case "numeric": + return Convert.ToInt64(stringValue); + case "string": + return stringValue; + case "map": + IDictionary<string, JToken> jsonObject = JObject.Parse(stringValue); + return jsonObject.ToDictionary(item => item.Key, item => item.Value.ToString()); + } + throw new NotSupportedException($"Data table result with subtype of {typeName} not supported"); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d000fce0/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs new file mode 100644 index 0000000..e1df3a8 --- /dev/null +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs @@ -0,0 +1,335 @@ +#region License + +/* + * 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. + */ + +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using Xunit; +using Gherkin; +using Gherkin.Ast; +using Gremlin.Net.IntegrationTest.Gherkin.Attributes; +using Microsoft.VisualStudio.TestPlatform.Utilities; +using Newtonsoft.Json.Serialization; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Gremlin.Net.IntegrationTest.Gherkin +{ + public class GherkinTestRunner + { + private readonly ITestOutputHelper _output; + + private static class Keywords + { + public const string Given = "GIVEN"; + public const string And = "AND"; + public const string But = "BUT"; + public const string When = "WHEN"; + public const string Then = "THEN"; + } + + public enum StepBlock + { + Given, + When, + Then + } + + private static readonly IDictionary<StepBlock, Type> Attributes = new Dictionary<StepBlock, Type> + { + { StepBlock.Given, typeof(GivenAttribute) }, + { StepBlock.When, typeof(WhenAttribute) }, + { StepBlock.Then, typeof(ThenAttribute) } + }; + + public GherkinTestRunner(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void RunGherkinBasedTests() + { + Console.WriteLine("Starting Gherkin-based tests"); + var stepDefinitionTypes = GetStepDefinitionTypes(); + var results = new List<ResultFeature>(); + foreach (var feature in GetFeatures()) + { + var resultFeature = new ResultFeature(feature); + results.Add(resultFeature); + foreach (var scenario in feature.Children) + { + var failedSteps = new Dictionary<Step, Exception>(); + resultFeature.Scenarios[scenario] = failedSteps; + StepBlock? currentStep = null; + StepDefinition stepDefinition = null; + foreach (var step in scenario.Steps) + { + var previousStep = currentStep; + currentStep = GetStepBlock(currentStep, step.Keyword); + if (currentStep == StepBlock.Given && previousStep != StepBlock.Given) + { + stepDefinition?.Dispose(); + stepDefinition = GetStepDefinitionInstance(stepDefinitionTypes, step.Text); + } + if (stepDefinition == null) + { + throw new NotSupportedException( + $"Step '{step.Text} not supported without a 'Given' step first"); + } + var result = ExecuteStep(stepDefinition, currentStep.Value, step); + if (result != null) + { + failedSteps.Add(step, result); + } + } + } + } + OutputResults(results); + Console.WriteLine("Finished Gherkin-based tests"); + } + + private void WriteOutput(string line) + { + _output.WriteLine(line); + } + + private void OutputResults(List<ResultFeature> results) + { + var totalScenarios = results.Sum(f => f.Scenarios.Count); + var totalFailedScenarios = results.Sum(f => f.Scenarios.Count(s => s.Value.Count > 0)); + WriteOutput("Gherkin tests summary"); + WriteOutput($"Total scenarios: {totalScenarios}. " + + $"Passed: {totalScenarios-totalFailedScenarios}. Failed: {totalFailedScenarios}."); + if (totalFailedScenarios == 0) + { + return; + } + var identifier = 0; + var failures = new List<Exception>(); + foreach (var resultFeature in results) + { + var failedScenarios = resultFeature.Scenarios.Where(s => s.Value.Count > 0).ToArray(); + if (failedScenarios.Length > 0) + { + WriteOutput($"Feature: {resultFeature.Feature.Name}"); + } + else + { + continue; + } + foreach (var resultScenario in failedScenarios) + { + WriteOutput($" Scenario: {resultScenario.Key.Name}"); + foreach (var step in resultScenario.Key.Steps) + { + Exception failure; + resultScenario.Value.TryGetValue(step, out failure); + if (failure == null) + { + WriteOutput($" {step.Keyword} {step.Text}"); + } + else + { + WriteOutput($" {++identifier}) {step.Keyword} {step.Text} (failed)"); + failures.Add(failure); + } + } + } + } + WriteOutput("Failures:"); + for (var index = 0; index < failures.Count; index++) + { + WriteOutput($"{index+1}) {failures[index]}"); + } + throw new Exception($"Gherkin test failed, see summary above for more detail"); + } + + public class ResultFeature + { + public Feature Feature { get;} + + public IDictionary<ScenarioDefinition, IDictionary<Step, Exception>> Scenarios { get; } + + public ResultFeature(Feature feature) + { + Feature = feature; + Scenarios = new Dictionary<ScenarioDefinition, IDictionary<Step, Exception>>(); + } + } + + private Exception ExecuteStep(StepDefinition instance, StepBlock stepBlock, Step step) + { + var attribute = Attributes[stepBlock]; + var methodAndParameters = instance.GetType().GetMethods() + .Select(m => + { + var attr = (BddAttribute) m.GetCustomAttribute(attribute); + if (attr == null) + { + return null; + } + var match = Regex.Match(step.Text, attr.Message); + if (!match.Success) + { + return null; + } + var parameters = new List<object>(); + for (var i = 1; i < match.Groups.Count; i++) + { + parameters.Add(match.Groups[i].Value); + } + if (step.Argument is DocString) + { + parameters.Add(((DocString) step.Argument).Content); + } + else if (step.Argument != null) + { + parameters.Add(step.Argument); + } + if (m.GetParameters().Length != parameters.Count) + { + return null; + } + return Tuple.Create(m, parameters.ToArray()); + }) + .FirstOrDefault(t => t != null); + if (methodAndParameters == null) + { + throw new InvalidOperationException( + $"There is no step definition method for {stepBlock} '{step.Text}'"); + } + try + { + methodAndParameters.Item1.Invoke(instance, methodAndParameters.Item2); + } + catch (TargetInvocationException ex) + { + // Exceptions should not be thrown + // Should be captured for result + return ex.InnerException; + } + catch (Exception ex) + { + return ex; + } + // Success + return null; + } + + private static StepBlock GetStepBlock(StepBlock? currentStep, string stepKeyword) + { + switch (stepKeyword.Trim().ToUpper()) + { + case Keywords.Given: + return StepBlock.Given; + case Keywords.When: + return StepBlock.When; + case Keywords.Then: + return StepBlock.Then; + case Keywords.And: + case Keywords.But: + if (currentStep == null) + { + throw new InvalidOperationException("'And' or 'But' is not supported outside a step"); + } + return currentStep.Value; + } + throw new NotSupportedException($"Step with keyword {stepKeyword} not supported"); + } + + private static StepDefinition GetStepDefinitionInstance(IEnumerable<Type> stepDefinitionTypes, string stepText) + { + var type = stepDefinitionTypes + .FirstOrDefault(t => t.GetMethods().Any(m => + { + var attr = m.GetCustomAttribute<GivenAttribute>(); + if (attr == null) + { + return false; + } + return Regex.IsMatch(stepText, attr.Message); + })); + if (type == null) + { + throw new InvalidOperationException($"No step definition class matches Given '{stepText}'"); + } + return (StepDefinition) Activator.CreateInstance(type); + } + + private ICollection<Type> GetStepDefinitionTypes() + { + var assembly = GetType().GetTypeInfo().Assembly; + var types = assembly.GetTypes() + .Where(t => typeof(StepDefinition).IsAssignableFrom(t) && !t.GetTypeInfo().IsAbstract) + .ToArray(); + if (types.Length == 0) + { + throw new InvalidOperationException($"No step definitions in {assembly.FullName}"); + } + return types; + } + + private IEnumerable<Feature> GetFeatures() + { + // TODO: go through all the .feature files + const string gherkinFile = "/Users/jorge/workspace/temp/count.feature"; + var parser = new Parser(); + var doc = parser.Parse(gherkinFile); + yield return doc.Feature; + } + + private void PrintGherkin() + { + var gherkinFile = "/Users/jorge/workspace/temp/count.feature"; + var parser = new Parser(); + GherkinDocument doc = parser.Parse(gherkinFile); + foreach (var scenario in doc.Feature.Children) + { + WriteOutput("--------"); + WriteOutput("Scenario: " + scenario.Name); + foreach (var step in scenario.Steps) + { + WriteOutput(" Step"); + WriteOutput(" Keyword: " + step.Keyword); + WriteOutput(" Text: " + step.Text); + WriteOutput(" Argument: " + step.Argument); + if (step.Argument is DocString) + { + WriteOutput(" " + ((DocString)step.Argument).Content); + } + if (step.Argument is DataTable) + { + foreach (var row in ((DataTable)step.Argument).Rows) + { + WriteOutput(" Row: " + string.Join(", ", row.Cells.Select(x => x.Value))); + } + } + } + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d000fce0/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/StepDefinition.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/StepDefinition.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/StepDefinition.cs new file mode 100644 index 0000000..3412fbd --- /dev/null +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/StepDefinition.cs @@ -0,0 +1,39 @@ +#region License + +/* + * 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. + */ + +#endregion + +using System; +using System.Dynamic; +using Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection; + +namespace Gremlin.Net.IntegrationTest.Gherkin +{ + public abstract class StepDefinition : IDisposable + { + internal RemoteConnectionFactory ConnectionFactory = new RemoteConnectionFactory(); + + public virtual void Dispose() + { + ConnectionFactory.Dispose(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d000fce0/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalTranslations.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalTranslations.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalTranslations.cs new file mode 100644 index 0000000..af02b8c --- /dev/null +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalTranslations.cs @@ -0,0 +1,102 @@ +#region License + +/* + * 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. + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Reflection; +using Gremlin.Net.Process.Traversal; + +namespace Gremlin.Net.IntegrationTest.Gherkin +{ + internal class TraversalTranslations + { + private static readonly IDictionary<string, Func<GraphTraversalSource, ITraversal>> FixedTranslations = + new Dictionary<string, Func<GraphTraversalSource, ITraversal>> + { + { "g.V().has(\"no\").count()", g => g.V().Has("no").Count() }, + { "g.V().fold().count(Scope.local)", g => g.V().Fold<object>().Count(Scope.Local)} + }; + + internal static ITraversal GetTraversal(string traversalText, GraphTraversalSource g) + { + Func<GraphTraversalSource, ITraversal> traversalBuilder; + if (!FixedTranslations.TryGetValue(traversalText, out traversalBuilder)) + { + return BuildFromMethods(traversalText, g); + } + return traversalBuilder(g); + } + + private static ITraversal BuildFromMethods(string traversalText, GraphTraversalSource g) + { + var parts = traversalText.Split('.'); + if (parts[0] != "g") + { + throw BuildException(traversalText); + } + ITraversal traversal; + switch (parts[1]) + { + case "V()": + traversal = g.V(); + break; + case "E()": + traversal = g.E(); + break; + default: + throw BuildException(traversalText); + } + for (var i = 2; i < parts.Length; i++) + { + var name = GetCsharpName(parts[i], traversalText); + var method = traversal.GetType().GetMethod(name); + if (method == null) + { + throw new InvalidOperationException($"Traversal method '{parts[i]}' not found for testing"); + } + if (method.IsGenericMethod) + { + throw new InvalidOperationException( + $"Can not build traversal to test as '{name}()' method is generic"); + } + traversal = (ITraversal) method.Invoke(traversal, new object[] { new object[0]}); + } + return traversal; + } + + private static string GetCsharpName(string part, string traversalText) + { + if (!part.EndsWith("()")) + { + throw BuildException(traversalText); + } + // Transform to PascalCasing and remove the parenthesis + return char.ToUpper(part[0]) + part.Substring(1, part.Length - 3); + } + + private static Exception BuildException(string traversalText) + { + return new InvalidOperationException($"Can not build a traversal to test from '{traversalText}'"); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d000fce0/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj index c5e923d..40552ad 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj @@ -1,5 +1,4 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> <TargetFramework>netcoreapp1.0</TargetFramework> <DebugType>portable</DebugType> @@ -10,17 +9,14 @@ <SignAssembly>true</SignAssembly> <PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign> </PropertyGroup> - <ItemGroup> <None Update="appsettings.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> - <ItemGroup> <ProjectReference Include="..\..\src\Gremlin.Net\Gremlin.Net.csproj" /> </ItemGroup> - <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" /> @@ -31,9 +27,12 @@ <PackageReference Include="OpenCover" Version="4.6.519" /> <PackageReference Include="coveralls.io" Version="1.3.4" /> </ItemGroup> - <ItemGroup> <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" /> </ItemGroup> - -</Project> + <ItemGroup> + <Reference Include="Gherkin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> + <HintPath>..\..\..\..\temp\Gherkin.dll</HintPath> + </Reference> + </ItemGroup> +</Project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/d000fce0/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs index 47d4f06..249db60 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs @@ -22,16 +22,20 @@ #endregion using System; +using System.Collections.Generic; using Gremlin.Net.Driver; using Gremlin.Net.Process.Remote; +using DriverRemoteConnectionImpl = Gremlin.Net.Driver.Remote.DriverRemoteConnection; namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection { - internal class RemoteConnectionFactory + internal class RemoteConnectionFactory : IDisposable { private static readonly string TestHost = ConfigProvider.Configuration["TestServerIpAddress"]; private static readonly int TestPort = Convert.ToInt32(ConfigProvider.Configuration["TestServerPort"]); + private readonly IList<DriverRemoteConnectionImpl> _connections = new List<DriverRemoteConnectionImpl>(); + public IRemoteConnection CreateRemoteConnection() { // gmodern is the standard test traversalsource that the main body of test uses @@ -40,8 +44,18 @@ namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection public IRemoteConnection CreateRemoteConnection(string traversalSource) { - return new Net.Driver.Remote.DriverRemoteConnection( + var c = new DriverRemoteConnectionImpl( new GremlinClient(new GremlinServer(TestHost, TestPort)), traversalSource); + _connections.Add(c); + return c; + } + + public void Dispose() + { + foreach (var connection in _connections) + { + connection.Dispose(); + } } } } \ No newline at end of file