Skip to content

Commit

Permalink
Review fixes. Add unit tests for exit condition.
Browse files Browse the repository at this point in the history
  • Loading branch information
dmilosz committed Apr 22, 2024
1 parent a1dbfd8 commit 2505aa5
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 8 deletions.
40 changes: 40 additions & 0 deletions CliWrap.Tests.Dummy/Commands/RunProcessCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;

namespace CliWrap.Tests.Dummy.Commands;

[Command("run process")]
public class RunProcessCommand : ICommand
{
[CommandOption("path")]
public string FilePath { get; init; } = string.Empty;

[CommandOption("arguments")]
public string Arguments { get; init; } = string.Empty;

public ValueTask ExecuteAsync(IConsole console)
{
var startInfo = new ProcessStartInfo
{
FileName = FilePath,
Arguments = Arguments,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

var process = new Process();
process.StartInfo = startInfo;
process.Start();

console.Output.WriteLine(process.Id);

return default;
}
}
73 changes: 73 additions & 0 deletions CliWrap.Tests/ExitConditionSpecs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using FluentAssertions;
using Xunit;

namespace CliWrap.Tests;

public class ExitConditionSpecs()
{
[Fact(Timeout = 15000)]
public async Task I_can_execute_a_command_that_creates_child_process_resuing_standard_output_and_finish_after_child_process_exits()
{
// Arrange
var cmd = this.PrepareCommand(line => { });

Check failure on line 15 in CliWrap.Tests/ExitConditionSpecs.cs

View workflow job for this annotation

GitHub Actions / main / test (ubuntu-latest)

I can execute a command that creates child process resuing standard output and finish after child process exits

Expected executionFinish.Subtract(executionStart) to be greater than or equal to 3s, but found 382ms and 985.2µs.

Check failure on line 15 in CliWrap.Tests/ExitConditionSpecs.cs

View workflow job for this annotation

GitHub Actions / main / test (macos-latest)

I can execute a command that creates child process resuing standard output and finish after child process exits

Expected executionFinish.Subtract(executionStart) to be greater than or equal to 3s, but found 1s, 356ms and 706.0µs.

// Act
var executionStart = DateTime.UtcNow;
var result = await cmd.ExecuteAsync();
var executionFinish = DateTime.UtcNow;

// Assert
executionFinish
.Subtract(executionStart)
.Should()
.BeGreaterThanOrEqualTo(TimeSpan.FromSeconds(3));
}

[Fact(Timeout = 15000)]
public async Task I_can_execute_a_command_that_creates_child_process_resuing_standard_output_and_finish_instantly_after_main_process_exits()
{
// Arrange
int childProcessId = -1;

Check failure on line 33 in CliWrap.Tests/ExitConditionSpecs.cs

View workflow job for this annotation

GitHub Actions / main / test (windows-latest)

I can execute a command that creates child process resuing standard output and finish instantly after main process exits

System.ArgumentException : Process with an Id of 4788 is not running.
var cmd = this.PrepareCommand(line => childProcessId = Convert.ToInt32(line.Trim()))
.WithExitCondition(CommandExitCondition.ProcessExited);

// Act
var executionStart = DateTime.UtcNow;
var result = await cmd.ExecuteAsync();
var executionFinish = DateTime.UtcNow;

var process = Process.GetProcessById(childProcessId);

// Assert
executionFinish.Subtract(executionStart).Should().BeLessThan(TimeSpan.FromSeconds(3));

process.HasExited.Should().BeFalse();
}

/// <summary>
/// Prepares a command that will create a sleeping child process and return its id via standard output.
/// </summary>
private Command PrepareCommand(Action<string> onStandardOutput)
{
var cmd = Cli.Wrap(Dummy.Program.FilePath)
.WithArguments(
[
"run",
"process",
"--path",
Dummy.Program.FilePath,
"--arguments",
"sleep 00:00:03"
]
)
.WithStandardOutputPipe(PipeTarget.ToDelegate(line => onStandardOutput(line)))
.WithStandardErrorPipe(
PipeTarget.ToDelegate(line => Console.WriteLine($"Error: {line}"))
);

return cmd;
}
}
13 changes: 5 additions & 8 deletions CliWrap/CommandExitCondition.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
using System;

namespace CliWrap;
namespace CliWrap;

/// <summary>
/// Strategy used for veryfing the end of command exectuion.
/// Strategy used for identifying the end of command exectuion.
/// </summary>
[Flags]
public enum CommandExitCondition
{
/// <summary>
/// Command is finished when process is finished and all pipes are closed.
/// Command execution is considered finished when the process exits and all standard input and output streams are closed.
/// </summary>
PipesClosed = 0,

/// <summary>
/// Command is finished when the main process exits,
/// even if they are child processes still running, which are reusing the same output/error streams.
/// Command execution is considered finished when the process exits, even if the process's standard input and output streams are still open,
/// for example after being inherited by a grandchild process.
/// </summary>
ProcessExited = 1
}
5 changes: 5 additions & 0 deletions CliWrap/ICommandConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public interface ICommandConfiguration
/// </summary>
Credentials Credentials { get; }

/// <summary>
/// Strategy used for veryfing the end of command exectuion.
/// </summary>
public CommandExitCondition CommandExitCondition { get; }

/// <summary>
/// Environment variables set for the underlying process.
/// </summary>
Expand Down

0 comments on commit 2505aa5

Please sign in to comment.