运行 使用 Specflow 处理时出现 InvalidOperationException

InvalidOperationException when running process with Specflow

我在 运行 我的 Specflow 测试时得到了这个:

[xUnit.net 00:00:06.30]       System.AggregateException : One or more errors occurred. (Cannot mix synchronous and asynchronous operation on process stream.)
[xUnit.net 00:00:06.30]       ---- System.InvalidOperationException : Cannot mix synchronous and asynchronous operation on process stream.
[xUnit.net 00:00:06.30]       Stack Trace:
[xUnit.net 00:00:06.31]            at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
[xUnit.net 00:00:06.31]            at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
[xUnit.net 00:00:06.31]            at System.Threading.Tasks.Task`1.get_Result()
[xUnit.net 00:00:06.31]         c:\Users\Me\Source\Repos\test_tool\Steps\IntegrationTestStepDefinitions.cs(51,0): at test_tool.Steps.IntegrationTestStepDefinitions.GivenMessagesAreStreamingFromToTopicOnPartition(String binaryFile, String topic, Int32 partition)
[xUnit.net 00:00:06.31]            at TechTalk.SpecFlow.Bindings.BindingInvoker.InvokeBinding(IBinding binding, IContextManager contextManager, Object[] arguments, ITestTracer testTracer, TimeSpan& duration)
[xUnit.net 00:00:06.31]            at TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.ExecuteStepMatch(BindingMatch match, Object[] arguments, TimeSpan& duration)
[xUnit.net 00:00:06.31]            at TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.ExecuteStep(IContextManager contextManager, StepInstance stepInstance)
[xUnit.net 00:00:06.31]            at TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.OnAfterLastStep()
[xUnit.net 00:00:06.31]            at TechTalk.SpecFlow.TestRunner.CollectScenarioErrors()
[xUnit.net 00:00:06.31]            at test_tool.Features.IntegrationTestFeature.ScenarioCleanup()

这是步骤(第 43-53 行):

        [Given(@"messages are streaming from ""(.*)"" to topic ""(.*)"" on partition (.*)")]
        public void GivenMessagesAreStreamingFromToTopicOnPartition(string binaryFile, string topic, int partition)
        {
            var MessageInjectorOptions = new MessageInjectorOptions();
            _configuration.GetSection("JavaMessageInjector").Bind(MessageInjectorOptions);
            _logger.LogInformation("MessageInjector Command: " + MessageInjectorOptions.MessageInjectorFilename + " " + MessageInjectorOptions.MessageInjectorParameters + " " + binaryFile + " " + topic + " " + partition);
            _output.WriteLine("Console MessageInjector Command: " + MessageInjectorOptions.MessageInjectorFilename + " " + MessageInjectorOptions.MessageInjectorParameters + " " + binaryFile + " " + topic + " " + partition);
            var result = TestHelper.ExecuteShellCommand(MessageInjectorOptions.MessageInjectorFilename, MessageInjectorOptions.MessageInjectorParameters + " " + binaryFile + " " + topic + " " + partition, 300000, true);
            _logger.LogInformation("Output: " + result.Result.Output);
            _logger.LogInformation("MessageInjector Exit Code: " + result.Result.ExitCode + ", Completed: " + result.Result.Completed + " Output: " + result.Result.Output);
        }

这是我的 ExecuteShellCommand:

public static async Task<ProcessResult> ExecuteShellCommand(string command, string arguments="", int timeout=1000, bool insertWait=false)
{
    var result = new ProcessResult();

    using (var process = new Process())
    {
        process.StartInfo.FileName = command;
        process.StartInfo.Arguments = arguments;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardInput = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        process.StartInfo.CreateNoWindow = true;

        var outputBuilder = new StringBuilder();
        var outputCloseEvent = new TaskCompletionSource<bool>();

        process.OutputDataReceived += (s, e) =>
        {
            // The output stream has been closed i.e. the process has terminated
            if (e.Data == null)
            {
                outputCloseEvent.SetResult(true);
            }
            else
            {
                outputBuilder.AppendLine(e.Data);
            }
        };

        var errorBuilder = new StringBuilder();
        var errorCloseEvent = new TaskCompletionSource<bool>();

        process.ErrorDataReceived += (s, e) =>
        {
            // The error stream has been closed i.e. the process has terminated
            if (e.Data == null)
            {
                errorCloseEvent.SetResult(true);
            }
            else
            {
                errorBuilder.AppendLine(e.Data);
            }
        };

        bool isStarted;

        try
        {
            process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
            process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);
            isStarted = process.Start(); 
            StreamReader reader = process.StandardOutput;
            string output = reader.ReadToEnd();
            result.Output = output;
        }
        catch (Exception error)
        {
            // Usually it occurs when an executable file is not found or is not executable

            result.Completed = true;
            result.ExitCode = -1;
            result.Output = error.Message;

            isStarted = false;
        }

        if (isStarted)
        {
            // Reads the output stream first and then waits because deadlocks are possible
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            if (insertWait)
            {
                await Task.Delay(150000);
            }

            // Creates task to wait for process exit using timeout
            var waitForExit = WaitForExitAsync(process, timeout);

            // Create task to wait for process exit and closing all output streams
            var processTask = Task.WhenAll(waitForExit, outputCloseEvent.Task, errorCloseEvent.Task);

            // Waits process completion and then checks it was not completed by timeout
            if (await Task.WhenAny(Task.Delay(timeout), processTask) == processTask && waitForExit.Result)
            {
                result.Completed = true;
                result.ExitCode = process.ExitCode;

                // Adds process output if it was completed with error
                if (process.ExitCode != 0)
                {
                    result.Output = $"{outputBuilder}{errorBuilder}";
                }
            }
            else
            {
                try
                {
                    // Kill hung process
                    process.Kill();
                }
                catch
                {
                }
            }
        }
    }

    return result;
}

我不明白错误是什么。我假设它来自 TestHelper 中的错误。是我必须等到return还是我应该如何解决这个问题?

您不能从同步步骤定义中调用 async 方法。您需要改用 Asynchronous Bindings

  1. 将步骤定义方法签名更改为异步和 return 任务:

    [Given(@"messages are streaming from ""(.*)"" to topic ""(.*)"" on partition (.*)")]
    public async Task GivenMessagesAreStreamingFromToTopicOnPartition(string binaryFile, string topic, int partition)
    {
        // ...
    }
    
  2. 调用时使用awaitTestHelper.ExecuteShellCommand(...):

    var result = await TestHelper.ExecuteShellCommand(MessageInjectorOptions.MessageInjectorFilename, MessageInjectorOptions.MessageInjectorParameters + " " + binaryFile + " " + topic + " " + partition, 300000, true);
    
    _logger.LogInformation("Output: " + result.Output);
    _logger.LogInformation("MessageInjector Exit Code: " + result.ExitCode + ", Completed: " + result.Completed + " Output: " + result.Output);
    

    请务必将所有出现的 result.Result.X 替换为 result.X,因为 result 变量现在将是一个 ProcessResult 对象而不是 Task<ProcessResult>.