Github user jorgebay commented on a diff in the pull request:

    https://github.com/apache/tinkerpop/pull/754#discussion_r154619459
  
    --- Diff: 
gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs ---
    @@ -0,0 +1,381 @@
    +#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.IO;
    +using System.Linq;
    +using System.Reflection;
    +using System.Text.RegularExpressions;
    +using Xunit;
    +using Gherkin;
    +using Gherkin.Ast;
    +using Gremlin.Net.IntegrationTest.Gherkin.Attributes;
    +using Xunit.Abstractions;
    +
    +namespace Gremlin.Net.IntegrationTest.Gherkin
    +{
    +    public class GherkinTestRunner
    +    {
    +        private static readonly IDictionary<string, IgnoreReason> 
IgnoredScenarios =
    +            new Dictionary<string, IgnoreReason>();
    +        
    +        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) }
    +        };
    +        
    +        private readonly ITestOutputHelper _output;
    +
    +        public GherkinTestRunner(ITestOutputHelper output)
    +        {
    +            _output = output;
    +        }
    +
    +        [Fact]
    +        public void RunGherkinBasedTests()
    +        {
    +            WriteOutput("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;
    +                    if (IgnoredScenarios.TryGetValue(scenario.Name, out 
var reason))
    +                    {
    +                        failedSteps.Add(scenario.Steps.First(), new 
IgnoreException(reason));
    +                        break;
    +                    }
    +                    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);
    +                            // Stop processing scenario
    +                            break;
    +                        }
    +                    }
    +                }
    +            }
    +            OutputResults(results);
    +            WriteOutput("Finished Gherkin-based tests");
    +            ScenarioData.Shutdown();
    +        }
    +
    +        private void WriteOutput(string line)
    +        {
    +#if DEBUG
    +            _output.WriteLine(line);
    +#else
    +            Console.WriteLine(line);
    +#endif
    +        }
    +
    +        private void OutputResults(List<ResultFeature> results)
    +        {
    +            WriteOutput("Gherkin tests result");
    +            var identifier = 0;
    +            var failures = new List<Tuple<string, Exception>>();
    +            var totalScenarios = 0;
    +            var totalFailed = 0;
    +            var totalIgnored = 0;
    +            foreach (var resultFeature in results)
    +            {
    +                foreach (var resultScenario in resultFeature.Scenarios)
    +                {
    +                    totalScenarios++;
    +                    WriteOutput($"  Scenario: {resultScenario.Key.Name}");
    +                    foreach (var step in resultScenario.Key.Steps)
    +                    {
    +                        resultScenario.Value.TryGetValue(step, out var 
failure);
    +                        if (failure == null)
    +                        {
    +                            WriteOutput($"    {step.Keyword} {step.Text}");
    +                        }
    +                        else
    +                        {
    +                            if (failure is IgnoreException)
    +                            {
    +                                totalIgnored++;
    +                                WriteOutput($"    {++identifier}) 
{step.Keyword} {step.Text} (ignored)");
    +                            }
    +                            else
    +                            {
    +                                totalFailed++;
    +                                WriteOutput($"    {++identifier}) 
{step.Keyword} {step.Text} (failed)");
    +                            }
    +                            
failures.Add(Tuple.Create(resultScenario.Key.Name, failure));
    +                        }
    +                    }
    +                }
    +            }
    +            if (totalFailed > 0)
    +            {
    +                WriteOutput("Failures" + (totalIgnored > 0 ? " and skipped 
scenarios" : "") + ":");
    +            }
    +            else if (totalIgnored > 0)
    +            {
    +                WriteOutput("Skipped scenarios:");
    +            }
    +            for (var index = 0; index < failures.Count; index++)
    +            {
    +                var failure = failures[index];
    +                var message = failure.Item2 is IgnoreException
    +                    ? ": " + failure.Item2.Message
    +                    : ": Failed\n" + failure.Item2;
    +                WriteOutput($"{index+1}) {failure.Item1}{message}");
    +            }
    +            WriteOutput("-----------------");
    +            WriteOutput($"Total scenarios: {totalScenarios}." +
    +                              $" Passed: 
{totalScenarios-totalFailed-totalIgnored}." +
    +                              $" Failed: {totalFailed}. Skipped: 
{totalIgnored}.");
    +            if (totalFailed == 0)
    +            {
    +                return;
    +            }
    +            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);
    +                    }
    +                    var methodParameters = m.GetParameters();
    +                    for (var i = parameters.Count; i < 
methodParameters.Length; i++)
    +                    {
    +                        // Try to complete with default parameter values
    +                        var paramInfo = methodParameters[i];
    +                        if (!paramInfo.HasDefaultValue)
    +                        {
    +                            break;
    +                        }
    +                        parameters.Add(paramInfo.DefaultValue);
    +                    }
    +                    if (methodParameters.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
    +            {
    +                var method = methodAndParameters.Item1;
    +                var parameters = methodAndParameters.Item2;
    +                var parameterInfos = method.GetParameters();
    +                for (var i = 0; i < parameterInfos.Length; i++)
    +                {
    +                    var paramInfo = parameterInfos[i];
    +                    // Do some minimal conversion => regex capturing 
groups to int
    +                    if (paramInfo.ParameterType == typeof(int))
    +                    {
    +                        parameters[i] = Convert.ToInt32(parameters[i]);
    +                    }
    +                }
    +                method.Invoke(instance, parameters);
    +            }
    +            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()
    +        {
    +            var rootPath = GetRootPath();
    +            var path = Path.Combine(rootPath, "gremlin-test", "features");
    +            var files = Directory.GetFiles(path, "*.feature", 
SearchOption.AllDirectories);
    +            foreach (var gherkinFile in files)
    +            {
    +                var parser = new Parser();
    +                var doc = parser.Parse(gherkinFile);
    +                yield return doc.Feature;
    +            }
    +        }
    +
    +        private string GetRootPath()
    +        {
    +            var codeBaseUrl = new 
Uri(GetType().GetTypeInfo().Assembly.CodeBase);
    +            var codeBasePath = 
Uri.UnescapeDataString(codeBaseUrl.AbsolutePath);
    +            DirectoryInfo rootDir = null;
    +            for (var dir = 
Directory.GetParent(Path.GetDirectoryName(codeBasePath));
    +                dir.Parent != null;
    +                dir = dir.Parent)
    +            {
    +                if (dir.Name == "gremlin-dotnet" && dir.Parent?.Name == 
"tinkerpop")
    --- End diff --
    
    Agree, looking for `tinkerpop` directory is incorrect.
    I've changed it to look for `gremlin-dotnet` directory which has a 
`pom.xml` file. There are different approaches we can use (like using an env 
var) but each has its own downsides.


---

Reply via email to