internal static async Task <string> ExecuteInternal(Action <ArgumentsBuilder> configure, CancellationToken cancellationToken) { var toolPath = ToolPath; ThrowIfNull(toolPath, nameof(Emulator)); var builder = new ArgumentsBuilder(); configure(builder); var args = builder.Build(); Logger.WriteLine($"{toolPath} {args}", LogLevel.Normal); var stdErrBuffer = new StringBuilder(); var stdOutBuffer = new StringBuilder(); var stdOut = PipeTarget.Merge(PipeTarget.ToStringBuilder(stdOutBuffer), PipeTarget.ToDelegate(l => Logger.WriteLine(l, LogLevel.Verbose))); await Cli.Wrap(toolPath) .WithArguments(args) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) .WithStandardOutputPipe(stdOut) .ExecuteAsync(cancellationToken); var stdErr = stdErrBuffer.ToString().Trim(); if (!string.IsNullOrEmpty(stdErr)) { throw new Exception(stdErr); } return(stdOutBuffer.ToString().Trim()); }
public async Task I_can_execute_a_command_that_pipes_its_stdout_into_multiple_streams() { // Arrange const int expectedSize = 100_000; await using var stream1 = new MemoryStream(); await using var stream2 = new MemoryStream(); await using var stream3 = new MemoryStream(); var pipeTarget = PipeTarget.Merge( PipeTarget.ToStream(stream1), PipeTarget.ToStream(stream2), PipeTarget.ToStream(stream3) ); var cmd = Cli.Wrap("dotnet") .WithArguments(a => a .Add(Dummy.Program.FilePath) .Add(Dummy.Program.PrintRandomBinary) .Add(expectedSize)) | pipeTarget; // Act await cmd.ExecuteAsync(); // Assert stream1.Length.Should().Be(expectedSize); stream2.Length.Should().Be(expectedSize); stream3.Length.Should().Be(expectedSize); stream1.ToArray().Should().BeEquivalentTo(stream2.ToArray()); stream2.ToArray().Should().BeEquivalentTo(stream3.ToArray()); }
internal static async Task ExecuteInternal(Action <ArgumentsBuilder> configure, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return; } var builder = new ArgumentsBuilder(); configure(builder); var args = builder.Build(); Logger.WriteLine($"{ToolPath} {args}", LogLevel.Normal); var stdOutBuffer = new StringBuilder(); var stdOut = PipeTarget.Merge(PipeTarget.ToStringBuilder(stdOutBuffer), PipeTarget.ToDelegate(l => Logger.WriteLine(l, LogLevel.Verbose))); var stdError = PipeTarget.ToDelegate(l => { if (string.IsNullOrEmpty(l)) { return; } // Suppress errors Logger.WriteWarning(l); }); var result = await Cli.Wrap(ToolPath) .WithArguments(args) .WithValidation(CommandResultValidation.None) .WithStandardErrorPipe(stdError) .WithStandardOutputPipe(stdOut) .ExecuteAsync(cancellationToken); }
public async Task <int> ExecuteWithCliWrap() { await using var stream1 = new MemoryStream(); await using var stream2 = new MemoryStream(); var result = await(Cli.Wrap(FilePath).WithArguments(Args) | PipeTarget.Merge(PipeTarget.ToStream(stream1), PipeTarget.ToStream(stream2))).ExecuteAsync(); return(result.ExitCode); }
public static Task <IDisposable> Run(string baseWorkingDirectory) { var completed = false; var tcs = new TaskCompletionSource <IDisposable>(); var cancellationSource = new CancellationTokenSource(); var logDirectory = Path.Combine(baseWorkingDirectory, "logs"); if (!Directory.Exists(logDirectory)) { Directory.CreateDirectory(logDirectory); } void HandleConsoleLine(string line) { if (line.Contains("listener started on 0.0.0.0:4723")) { Logger.WriteLine(line, LogLevel.Minimal, defaultLog); if (!completed) { tcs.SetResult(new AppiumTask(cancellationSource)); } completed = true; } else if (line.Contains("make sure there is no other instance of this server running already") || line.Contains("listen EADDRINUSE: address already in use 0.0.0.0:4723")) { Logger.WriteWarning(line, defaultLog); if (!completed) { tcs.SetResult(new AppiumTask(cancellationSource)); } completed = true; } else { Logger.WriteLine(line, LogLevel.Verbose, defaultLog); } } var stdOut = PipeTarget.ToDelegate(HandleConsoleLine); var stdErr = PipeTarget.Merge( PipeTarget.ToFile(Path.Combine(logDirectory, "appium-error.log")), PipeTarget.ToDelegate(HandleConsoleLine)); Logger.WriteLine("Starting Appium...", LogLevel.Minimal); var toolPath = EnvironmentHelper.GetToolPath("appium"); var cmd = Cli.Wrap(toolPath) .WithStandardOutputPipe(stdOut) .WithStandardErrorPipe(stdErr) .WithValidation(CommandResultValidation.None) .ExecuteAsync(cancellationSource.Token); return(tcs.Task); }
public static Command AddTestOutputPipe(this Command command, ITestOutputHelper testOutputHelper) { return(command .WithStandardOutputPipe( PipeTarget.Merge( command.StandardOutputPipe, PipeTarget.ToDelegate(testOutputHelper.WriteLine))) .WithStandardErrorPipe( PipeTarget.Merge( command.StandardErrorPipe, PipeTarget.ToDelegate(testOutputHelper.WriteLine)))); }
/// <summary> /// Executes the command as an observable event stream. /// </summary> public static IObservable <CommandEvent> Observe( this Command command, Encoding standardOutputEncoding, Encoding standardErrorEncoding, CancellationToken cancellationToken = default) => Observable.Create <CommandEvent>(observer => { var stdOutPipe = PipeTarget.Merge( command.StandardOutputPipe, PipeTarget.ToDelegate( s => observer.OnNext(new StandardOutputCommandEvent(s)), standardOutputEncoding) ); var stdErrPipe = PipeTarget.Merge( command.StandardErrorPipe, PipeTarget.ToDelegate( s => observer.OnNext(new StandardErrorCommandEvent(s)), standardErrorEncoding) ); var commandPiped = command .WithStandardOutputPipe(stdOutPipe) .WithStandardErrorPipe(stdErrPipe); var commandTask = commandPiped.ExecuteAsync(cancellationToken); observer.OnNext(new StartedCommandEvent(commandTask.ProcessId)); // Don't pass cancellation token to continuation because we need it to always trigger // regardless of how the task completed. _ = commandTask .Task .ContinueWith(t => { // Canceled tasks don't have exception if (t.IsCanceled) { observer.OnError(new OperationCanceledException("Command execution has been canceled.")); } else if (t.Exception == null) { observer.OnNext(new ExitedCommandEvent(t.Result.ExitCode)); observer.OnCompleted(); } else { observer.OnError(t.Exception); } }, TaskContinuationOptions.None); return(Disposable.Null); });
private static async Task <string> ExecuteInternal(Action <ArgumentsBuilder> configure, CancellationToken cancellationToken, PipeSource stdInput = null) { var toolPath = ToolPath; ThrowIfNull(toolPath, nameof(AvdManager)); var builder = new ArgumentsBuilder(); configure(builder); var args = builder.Build(); Logger.WriteLine($"{toolPath} {args}", LogLevel.Normal); var errorBuffer = new List <string>(); var stdOutBuffer = new StringBuilder(); var stdOut = PipeTarget.Merge(PipeTarget.ToStringBuilder(stdOutBuffer), PipeTarget.ToDelegate(l => Logger.WriteLine(l, LogLevel.Verbose))); var stdErr = PipeTarget.ToDelegate(l => { if (string.IsNullOrEmpty(l)) { return; } else if (l.Contains("Warning: ")) { Logger.WriteWarning(l); } else { errorBuffer.Add(l); } }); var cmd = Cli.Wrap(toolPath) .WithArguments(args) .WithValidation(CommandResultValidation.None) .WithStandardErrorPipe(stdErr) .WithStandardOutputPipe(stdOut); if (stdInput != null) { cmd = cmd.WithStandardInputPipe(stdInput); } await cmd.ExecuteAsync(cancellationToken); if (errorBuffer.Any()) { throw new Exception(string.Join(Environment.NewLine, errorBuffer)); } return(stdOutBuffer.ToString().Trim()); }
/// <summary> /// Executes the command asynchronously. /// The result of this execution contains the standard output and standard error streams buffered in-memory as strings. /// This method can be awaited. /// </summary> public static CommandTask <BufferedCommandResult> ExecuteBufferedAsync( this Command command, Encoding standardOutputEncoding, Encoding standardErrorEncoding, CancellationToken cancellationToken = default) { var stdOutBuffer = new StringBuilder(); var stdErrBuffer = new StringBuilder(); var stdOutPipe = PipeTarget.Merge( command.StandardOutputPipe, PipeTarget.ToStringBuilder(stdOutBuffer, standardOutputEncoding) ); var stdErrPipe = PipeTarget.Merge( command.StandardErrorPipe, PipeTarget.ToStringBuilder(stdErrBuffer, standardErrorEncoding) ); var commandPiped = command .WithStandardOutputPipe(stdOutPipe) .WithStandardErrorPipe(stdErrPipe) .WithValidation(CommandResultValidation.None); // disable validation because we have our own return(commandPiped .ExecuteAsync(cancellationToken) .Select(r => { // Transform the result var result = new BufferedCommandResult( r.ExitCode, r.StartTime, r.ExitTime, stdOutBuffer.ToString(), stdErrBuffer.ToString() ); // We perform validation separately here because we want to include stderr in the exception as well if (result.ExitCode != 0 && command.Validation.IsZeroExitCodeValidationEnabled()) { throw CommandExecutionException.ExitCodeValidation( command.TargetFilePath, command.Arguments, result.ExitCode, result.StandardError.Trim() ); } return result; })); }
private async Task <CommandResult> RunAsyncInner( IEnumerable <string> args, string workingDir, IDictionary <string, string?> additionalEnv, Action <string>?onStandardOutput = null, Action <string>?onStandardError = null, EventLogFile?eventLogFile = null, CancellationToken cancellationToken = default) { var stdOutBuffer = new StringBuilder(); var stdOutPipe = PipeTarget.ToStringBuilder(stdOutBuffer); if (onStandardOutput != null) { stdOutPipe = PipeTarget.Merge(stdOutPipe, PipeTarget.ToDelegate(onStandardOutput)); } var stdErrBuffer = new StringBuilder(); var stdErrPipe = PipeTarget.ToStringBuilder(stdErrBuffer); if (onStandardError != null) { stdErrPipe = PipeTarget.Merge(stdErrPipe, PipeTarget.ToDelegate(onStandardError)); } var pulumiCmd = Cli.Wrap("pulumi") .WithArguments(PulumiArgs(args, eventLogFile), escape: true) .WithWorkingDirectory(workingDir) .WithEnvironmentVariables(PulumiEnvironment(additionalEnv, debugCommands: eventLogFile != null)) .WithStandardOutputPipe(stdOutPipe) .WithStandardErrorPipe(stdErrPipe) .WithValidation(CommandResultValidation.None); // we check non-0 exit code ourselves var pulumiCmdResult = await pulumiCmd.ExecuteAsync(cancellationToken); var result = new CommandResult( pulumiCmdResult.ExitCode, standardOutput: stdOutBuffer.ToString(), standardError: stdErrBuffer.ToString()); if (pulumiCmdResult.ExitCode != 0) { throw CommandException.CreateFromResult(result); } else { return(result); } }
public async Task <(Stream, Stream)> ExecuteWithCliWrap_PipeToMultipleStreams() { await using var stream1 = new MemoryStream(); await using var stream2 = new MemoryStream(); var target = PipeTarget.Merge( PipeTarget.ToStream(stream1), PipeTarget.ToStream(stream2) ); var command = Cli.Wrap(FilePath).WithArguments(Args) | target; await command.ExecuteAsync(); return(stream1, stream2); }
/// <summary> /// Executes the command as an asynchronous (pull-based) event stream. /// Use <code>await foreach</code> to listen to the stream and handle command events. /// </summary> public static async IAsyncEnumerable <CommandEvent> ListenAsync( this Command command, Encoding standardOutputEncoding, Encoding standardErrorEncoding, [EnumeratorCancellation] CancellationToken cancellationToken = default) { using var channel = new Channel <CommandEvent>(); var stdOutPipe = PipeTarget.Merge( command.StandardOutputPipe, PipeTarget.ToDelegate( s => channel.PublishAsync(new StandardOutputCommandEvent(s), cancellationToken), standardOutputEncoding) ); var stdErrPipe = PipeTarget.Merge( command.StandardErrorPipe, PipeTarget.ToDelegate( s => channel.PublishAsync(new StandardErrorCommandEvent(s), cancellationToken), standardErrorEncoding) ); var commandPiped = command .WithStandardOutputPipe(stdOutPipe) .WithStandardErrorPipe(stdErrPipe); var commandTask = commandPiped.ExecuteAsync(cancellationToken); yield return(new StartedCommandEvent(commandTask.ProcessId)); // Don't pass cancellation token to continuation because we need it to always trigger // regardless of how the task completed. _ = commandTask .Task .ContinueWith(_ => channel.Close(), TaskContinuationOptions.None); await foreach (var cmdEvent in channel.ReceiveAsync(cancellationToken).ConfigureAwait(false)) { yield return(cmdEvent); } var exitCode = await commandTask.Select(r => r.ExitCode).ConfigureAwait(false); yield return(new ExitedCommandEvent(exitCode)); }
internal static async Task <string> ExecuteInternal(Action <ArgumentsBuilder> configure, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(null); } var toolPath = ToolPath; var builder = new ArgumentsBuilder(); configure(builder); var args = builder.Build(); Logger.WriteLine($"{toolPath} {args}", LogLevel.Normal); var stdErrBuffer = new StringBuilder(); var stdOutBuffer = new StringBuilder(); var stdOut = PipeTarget.Merge(PipeTarget.ToStringBuilder(stdOutBuffer), PipeTarget.ToDelegate(l => Logger.WriteLine(l, LogLevel.Verbose))); var result = await Cli.Wrap(toolPath) .WithArguments(args) .WithValidation(CommandResultValidation.None) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) .WithStandardOutputPipe(stdOut) .ExecuteAsync(cancellationToken); var stdErr = stdErrBuffer.ToString().Trim(); if (!string.IsNullOrEmpty(stdErr)) { if (stdErr.Split('\n').Select(x => x.Trim()).All(x => x.StartsWith("Warning:", StringComparison.InvariantCultureIgnoreCase))) { Logger.WriteWarning(stdErr); } else { throw new Exception(stdErr); } } return(stdOutBuffer.ToString().Trim()); }