运行 使用 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。
将步骤定义方法签名更改为异步和 return 任务:
[Given(@"messages are streaming from ""(.*)"" to topic ""(.*)"" on partition (.*)")]
public async Task GivenMessagesAreStreamingFromToTopicOnPartition(string binaryFile, string topic, int partition)
{
// ...
}
调用时使用await
TestHelper.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>
.
我在 运行 我的 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。
将步骤定义方法签名更改为异步和 return 任务:
[Given(@"messages are streaming from ""(.*)"" to topic ""(.*)"" on partition (.*)")] public async Task GivenMessagesAreStreamingFromToTopicOnPartition(string binaryFile, string topic, int partition) { // ... }
调用时使用
await
TestHelper.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>
.