Esempio n. 1
0
 public void ExecuteTest(SimpleCommandWithIntegerResult command, Exception thrownException)
 {
     "Given a command whose handler throws an exception"
     .x(() => command = new SimpleCommandWithIntegerResult(new Exception("something went wrong")));
     "When I dispatch the command"
     .x(async() =>
     {
         try
         {
             await Dispatcher.DispatchAsync(command);
         }
         catch (Exception ex)
         {
             thrownException = ex;
         }
     });
     "Then the exception is propagated back to the caller"
     .x(() =>
     {
         Assert.NotNull(thrownException);
         Assert.IsType <CommandExecutionException>(thrownException);
         CommandExecutionException commandExecutionException = (CommandExecutionException)thrownException;
         Assert.NotNull(commandExecutionException.InnerException);
         Assert.Equal("something went wrong", commandExecutionException.InnerException.Message);
     });
 }
Esempio n. 2
0
        private async Task <CommandResult> ExecuteAsync(ProcessEx process, CancellationToken cancellationToken = default)
        {
            using var _ = process;

            process.Start();

            // Register cancellation
            using var cancellation = cancellationToken.Register(() => process.TryKill());

            // Handle stdin pipe
            using (process.StdIn)
                await StandardInputPipe.CopyToAsync(process.StdIn, cancellationToken);

            // Handle stdout/stderr pipes and wait for exit
            await Task.WhenAll(
                StandardOutputPipe.CopyFromAsync(process.StdOut, cancellationToken),
                StandardErrorPipe.CopyFromAsync(process.StdErr, cancellationToken),
                process.WaitUntilExitAsync());

            if (process.ExitCode != 0 && Validation.IsZeroExitCodeValidationEnabled())
            {
                throw CommandExecutionException.ExitCodeValidation(TargetFilePath, Arguments, process.ExitCode);
            }

            return(new CommandResult(process.ExitCode, process.StartTime, process.ExitTime));
        }
Esempio n. 3
0
        public async Task ExecuteCommandWithAnUnhandledException_ThrowsWrappedException()
        {
            ExceptionCommand command = new();

            CommandExecutionException exception = await Assert.ThrowsAsync <CommandExecutionException>(() => CommandExecutor.InvokeAsync(command, CancellationToken.None));

            Assert.IsType <CommandException>(exception.InnerException);
        }
        /// <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;
            }));
        }
Esempio n. 5
0
        private async Task <CommandResult> ExecuteAsync(ProcessEx process, CancellationToken cancellationToken = default)
        {
            // Additional cancellation for stdin in case the process terminates early and doesn't fully exhaust it
            using var stdInCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

            // Setup and start process
            using var _1 = process;
            process.Start();
            using var _2 = cancellationToken.Register(process.Kill);

            // Start piping in parallel
            var pipingTasks = new[]
            {
                PipeStandardInputAsync(process, stdInCts.Token),
                PipeStandardOutputAsync(process, cancellationToken),
                PipeStandardErrorAsync(process, cancellationToken)
            };

            // Wait until the process terminates or gets killed
            await process.WaitUntilExitAsync();

            // Cancel stdin in case the process terminated early and doesn't need it anymore
            stdInCts.Cancel();

            try
            {
                // Wait until piping is done and propagate exceptions
                await Task.WhenAll(pipingTasks);
            }
            catch (OperationCanceledException) when(!cancellationToken.IsCancellationRequested)
            {
                // Don't throw if cancellation happened internally and not by user request
            }

            // Validate exit code if required
            if (process.ExitCode != 0 && Validation.IsZeroExitCodeValidationEnabled())
            {
                throw CommandExecutionException.ExitCodeValidation(
                          TargetFilePath,
                          Arguments,
                          process.ExitCode
                          );
            }

            return(new CommandResult(
                       process.ExitCode,
                       process.StartTime,
                       process.ExitTime
                       ));
        }
Esempio n. 6
0
        private async Task <CommandResult> ExecuteAsync(ProcessEx process, CancellationToken cancellationToken = default)
        {
            using var _ = process;

            process.Start();

            // Register cancellation
            using var cancellation = cancellationToken.Register(() => process.TryKill());

            // Stdin must be closed after it finished to avoid deadlock if the process reads the stream to end
            async Task HandleStdInAsync()
            {
                using (process.StdIn)
                    await StandardInputPipe.CopyToAsync(process.StdIn, cancellationToken);
            }

            // Stdout doesn't need to be closed but we do it for good measure
            async Task HandleStdOutAsync()
            {
                using (process.StdOut)
                    await StandardOutputPipe.CopyFromAsync(process.StdOut, cancellationToken);
            }

            // Stderr doesn't need to be closed but we do it for good measure
            async Task HandleStdErrAsync()
            {
                using (process.StdErr)
                    await StandardErrorPipe.CopyFromAsync(process.StdErr, cancellationToken);
            }

            // Handle pipes in parallel to avoid deadlocks
            await Task.WhenAll(
                HandleStdInAsync(),
                HandleStdOutAsync(),
                HandleStdErrAsync(),
                process.WaitUntilExitAsync()
                );

            if (process.ExitCode != 0 && Validation.IsZeroExitCodeValidationEnabled())
            {
                throw CommandExecutionException.ExitCodeValidation(TargetFilePath, Arguments, process.ExitCode);
            }

            return(new CommandResult(process.ExitCode, process.StartTime, process.ExitTime));
        }
Esempio n. 7
0
        private async Task <CommandResult> ExecuteAsync(ProcessEx process, CancellationToken cancellationToken = default)
        {
            using var _ = process;
            process.Start();

            // Stdin pipe may need to be canceled early if the process terminates before it finishes
            using var stdInCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

            // Register early process termination
            using var cancellation = cancellationToken.Register(() => process.TryKill());

            // Stdin must be closed after it finished to avoid deadlock if the process reads the stream to end
            async Task HandleStdInAsync()
            {
                try
                {
                    // Some streams don't support cancellation, in which case we need a fallback mechanism to avoid deadlocks.
                    // For example, WindowsConsoleStream (from Console.OpenStandardInput()) in particular doesn't support cancellation.
                    // In the following case the operation will terminate but a rogue Task will leak and might cause problems.
                    // This is a non-issue, however, if the user closes the stream at the earliest opportunity.
                    // Otherwise we enter an indeterminate state and tell ourselves we did everything we could to avoid it.
                    await Task.WhenAny(
                        StandardInputPipe.CopyToAsync(process.StdIn, stdInCts.Token),
                        Task.Delay(-1, stdInCts.Token)
                        );
                }
                // Ignore cancellation here, will propagate later
                catch (OperationCanceledException)
                {
                }
                // We want to ignore I/O exceptions that happen when the output stream has already closed.
                // This can happen when the process reads only a portion of stdin and then exits.
                // Unfortunately we can't catch a specific exception for this exact event so we have no choice but to catch all of them.
                catch (IOException)
                {
                }
                finally
                {
                    await process.StdIn.DisposeAsync();
                }
            }

            // Stdout doesn't need to be closed but we do it for good measure
            async Task HandleStdOutAsync()
            {
                try
                {
                    await StandardOutputPipe.CopyFromAsync(process.StdOut, cancellationToken);
                }
                // Ignore cancellation here, will propagate later
                catch (OperationCanceledException)
                {
                }
                finally
                {
                    await process.StdOut.DisposeAsync();
                }
            }

            // Stderr doesn't need to be closed but we do it for good measure
            async Task HandleStdErrAsync()
            {
                try
                {
                    await StandardErrorPipe.CopyFromAsync(process.StdErr, cancellationToken);
                }
                // Ignore cancellation here, will propagate later
                catch (OperationCanceledException)
                {
                }
                finally
                {
                    await process.StdErr.DisposeAsync();
                }
            }

            // Handle pipes in background and in parallel to avoid deadlocks
            var pipingTasks = new[]
            {
                HandleStdInAsync(),
                HandleStdOutAsync(),
                HandleStdErrAsync()
            };

            // Wait until the process terminates or gets killed
            await process.WaitUntilExitAsync();

            // Stop piping stdin if the process has already exited (can happen if not all of stdin is read)
            stdInCts.Cancel();

            // Ensure all pipes are finished
            await Task.WhenAll(pipingTasks);

            // Propagate cancellation to the user
            cancellationToken.ThrowIfCancellationRequested();

            // Validate exit code
            if (process.ExitCode != 0 && Validation.IsZeroExitCodeValidationEnabled())
            {
                throw CommandExecutionException.ExitCodeValidation(TargetFilePath, Arguments, process.ExitCode);
            }

            return(new CommandResult(process.ExitCode, process.StartTime, process.ExitTime));
        }
 public CommandExecutedMessage(CommandExecutor executor, CommandExecutionException exception = null)
 {
     Executor  = executor;
     Exception = exception;
 }