Skip to content

Commit

Permalink
Fix ManagedCommand. (#1)
Browse files Browse the repository at this point in the history
* Fix ManagedCommand.
Refactor Process management.
Add more tests

* Add more documentation.
  • Loading branch information
Gitii authored Dec 26, 2021
1 parent 559eae4 commit 057eac9
Show file tree
Hide file tree
Showing 12 changed files with 370 additions and 18 deletions.
21 changes: 21 additions & 0 deletions Community.Wsl.Sdk.Tests/IntegrationsTests/ManagedCommandTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Community.Wsl.Sdk.Strategies.Api;
using Community.Wsl.Sdk.Strategies.Command;
using Community.Wsl.Sdk.Strategies.NativeMethods;
Expand Down Expand Up @@ -88,4 +89,24 @@ public void Test_expect_stdout_to_equal_stdin()
result.Stderr.Should().BeEmpty();
result.StderrData.Should().BeNull();
}

[Test]
public async Task Test_async_wait()
{
var cmd = new ManagedCommand(
_distroName,
"echo",
new string[] { "-n", "test" },
new CommandExecutionOptions() { StdoutDataProcessingMode = DataProcessingMode.String }
);

cmd.Start();
var result = await cmd.WaitAndGetResultsAsync();

result.Stdout.Should().BeEquivalentTo("test");
result.StdoutData.Should().BeNull();

result.Stderr.Should().BeNull();
result.StderrData.Should().BeNull();
}
}
106 changes: 106 additions & 0 deletions Community.Wsl.Sdk.Tests/UnitTests/ManagedCommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Castle.Core.Resource;
using Community.Wsl.Sdk.Strategies.Api;
using Community.Wsl.Sdk.Strategies.Command;
using FakeItEasy;
using FluentAssertions;
using NUnit.Framework;

namespace Community.Wsl.Sdk.Tests.UnitTests
{
public class ManagedCommandTests
{
public ManagedCommand CreateCommand(
string distroName,
string command,
string[] arguments,
CommandExecutionOptions options,
bool asRoot = false,
bool shellExecute = false
)
{
var io = A.Fake<IIo>();
var env = A.Fake<IEnvironment>();
var pm = A.Fake<IProcessManager>();

return new ManagedCommand(
distroName,
command,
arguments,
options,
asRoot,
shellExecute,
env,
io,
pm
);
}

[Test]
public void Test_Constructor()
{
var cmd = CreateCommand("dn", "", Array.Empty<string>(), new CommandExecutionOptions());

cmd.HasExited.Should().BeFalse();
cmd.HasWaited.Should().BeFalse();
cmd.IsDisposed.Should().BeFalse();
cmd.IsStarted.Should().BeFalse();
}

[Test]
public void Test_Start()
{
var distroName = "distro";
var command = "command";
var arguments = new string[] { "args" };
var options = new CommandExecutionOptions();

var io = A.Fake<IIo>();
var env = A.Fake<IEnvironment>();
var pm = A.Fake<IProcessManager>();

var p = A.Fake<IProcess>();

ProcessStartInfo actualStartInfo = new ProcessStartInfo();
A.CallTo(() => pm.Start(A<ProcessStartInfo>._))
.Invokes((psi) => actualStartInfo = psi.GetArgument<ProcessStartInfo>(0)!)
.Returns(p);

var cmd = new ManagedCommand(
distroName,
command,
arguments,
options,
false,
false,
env,
io,
pm
);

var results = cmd.Start();

results.StandardOutput.Should().Be(StreamReader.Null);
results.StandardError.Should().Be(StreamReader.Null);
results.StandardInput.Should().Be(StreamWriter.Null);

A.CallTo(() => pm.Start(A<ProcessStartInfo>._)).MustHaveHappened();

actualStartInfo.ArgumentList
.Should()
.BeEquivalentTo("-d", distroName, "--exec", command, arguments[0]);
actualStartInfo.RedirectStandardOutput.Should().BeFalse();
actualStartInfo.RedirectStandardError.Should().BeFalse();
actualStartInfo.RedirectStandardInput.Should().BeFalse();

actualStartInfo.CreateNoWindow.Should().BeTrue();
}
}
}
55 changes: 55 additions & 0 deletions Community.Wsl.Sdk/Strategies/Api/IProcess.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace Community.Wsl.Sdk.Strategies.Api
{
/// <summary>
/// Wrapper for a <see cref="Process"/>.
/// <inheritdoc cref="Process"/>
/// </summary>
public interface IProcess : IDisposable
{
/// <summary>
/// <inheritdoc cref="Process.HasExited"/>
/// </summary>
public bool HasExited { get; }

/// <summary>
/// <inheritdoc cref="Process.StandardOutput"/>
/// </summary>
public StreamReader StandardOutput { get; }

/// <summary>
/// <inheritdoc cref="Process.StandardError"/>
/// </summary>
public StreamReader StandardError { get; }

/// <summary>
/// <inheritdoc cref="Process.StandardInput"/>
/// </summary>
public StreamWriter StandardInput { get; }

/// <summary>
/// <inheritdoc cref="Process.EnableRaisingEvents"/>
/// </summary>
public bool EnableRaisingEvents { get; set; }

/// <summary>
/// <inheritdoc cref="Process.Exited"/>
/// </summary>
public event EventHandler Exited;

/// <summary>
/// <inheritdoc cref="Process.ExitCode"/>
/// </summary>
public int ExitCode { get; }

/// <summary>
/// <inheritdoc cref="Process.WaitForExit()"/>
/// </summary>
public void WaitForExit();
}
}
21 changes: 21 additions & 0 deletions Community.Wsl.Sdk/Strategies/Api/IProcessManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace Community.Wsl.Sdk.Strategies.Api
{
/// <summary>
/// Wrapper for <see cref="Process.Start(System.Diagnostics.ProcessStartInfo)"/>.
/// Returns an <see cref="Process"/> wrapped in a <see cref="IProcess"/>.
/// </summary>
public interface IProcessManager
{
/// <summary>
/// <inheritdoc cref="Process.Start(System.Diagnostics.ProcessStartInfo)"/>
/// </summary>
/// <param name="startInfo"><inheritdoc cref="Process.Start(System.Diagnostics.ProcessStartInfo)" path="/param[@name='startInfo']"/></param>
/// <returns></returns>
public abstract IProcess? Start(ProcessStartInfo startInfo);
}
}
47 changes: 47 additions & 0 deletions Community.Wsl.Sdk/Strategies/Api/Win32Process.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Diagnostics;
using System.IO;

namespace Community.Wsl.Sdk.Strategies.Api;

internal class Win32Process : IProcess
{
private readonly Process _process;

public Win32Process(Process process)
{
_process = process;
}

public void Dispose()
{
_process.Dispose();
}

public bool HasExited => _process.HasExited;

public StreamReader StandardOutput => _process.StandardOutput;

public StreamReader StandardError => _process.StandardError;

public StreamWriter StandardInput => _process.StandardInput;

public bool EnableRaisingEvents
{
get => _process.EnableRaisingEvents;
set => _process.EnableRaisingEvents = value;
}

public event EventHandler? Exited
{
add => _process.Exited += value;
remove => _process.Exited -= value;
}

public int ExitCode => _process.ExitCode;

public void WaitForExit()
{
_process.WaitForExit();
}
}
17 changes: 17 additions & 0 deletions Community.Wsl.Sdk/Strategies/Api/Win32ProcessManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Diagnostics;

namespace Community.Wsl.Sdk.Strategies.Api;

internal class Win32ProcessManager : IProcessManager
{
public IProcess? Start(ProcessStartInfo startInfo)
{
var process = Process.Start(startInfo);
if (process == null)
{
return null;
}

return new Win32Process(process);
}
}
23 changes: 20 additions & 3 deletions Community.Wsl.Sdk/Strategies/Command/CommandStreams.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,26 @@

namespace Community.Wsl.Sdk.Strategies.Command;

/// <summary>
/// Encapsulates the io stream of a <see cref="ICommand"/>.
/// </summary>
public class CommandStreams
{
public StreamWriter? StandardInput { get; init; }
public StreamReader? StandardOutput { get; init; }
public StreamReader? StandardError { get; init; }
/// <summary>
/// The standard input stream.
/// Will be <see cref="StreamWriter.Null"/> if <see cref="CommandExecutionOptions.StdInDataProcessingMode"/> equals <see cref="DataProcessingMode.Drop"/>.
/// </summary>
public StreamWriter StandardInput { get; init; } = null!;

/// <summary>
/// The standard output stream.
/// Will be <see cref="StreamReader.Null"/> if <see cref="CommandExecutionOptions.StdoutDataProcessingMode"/> equals <see cref="DataProcessingMode.Drop"/>.
/// </summary>
public StreamReader StandardOutput { get; init; } = null!;

/// <summary>
/// The standard error stream.
/// Will be <see cref="StreamReader.Null"/> if <see cref="CommandExecutionOptions.StdErrDataProcessingMode"/> equals <see cref="DataProcessingMode.Drop"/>.
/// </summary>
public StreamReader StandardError { get; init; } = null!;
}
19 changes: 18 additions & 1 deletion Community.Wsl.Sdk/Strategies/Command/IStreamReader.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
namespace Community.Wsl.Sdk.Strategies.Command;
using System.Threading.Tasks;

namespace Community.Wsl.Sdk.Strategies.Command;

internal interface IStreamReader
{
/// <summary>
/// Starts copying the streamed data from the stream to an internal buffer.
/// This is done in a separate thread.
/// </summary>
void Fetch();

/// <summary>
/// Copies the contents of the internal buffer to <see cref="CommandResult"/>.
/// </summary>
/// <param name="result"></param>
/// <param name="isStdOut"></param>
void CopyResultTo(ref CommandResult result, bool isStdOut);

/// <summary>
/// Blocks the current thread until fetching has been finished.
/// </summary>
void Wait();

/// <summary>
/// Returns a task which completes when the fetching has been finished.
/// </summary>
Task WaitAsync();
}
Loading

0 comments on commit 057eac9

Please sign in to comment.