build: initial project release
This commit is contained in:
commit
2e94c513fc
47 changed files with 4935 additions and 0 deletions
13
src/process.dummy.app/AsyncOutputCommand.cs
Normal file
13
src/process.dummy.app/AsyncOutputCommand.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
internal abstract class AsyncOutputCommand<T> : AsyncCommand<T> where T : OutputCommandSettings
|
||||
{
|
||||
}
|
||||
|
||||
internal abstract class OutputCommandSettings : CommandSettings
|
||||
{
|
||||
[CommandOption("--target")] public OutputTarget Target { get; init; } = OutputTarget.StdOut;
|
||||
}
|
||||
17
src/process.dummy.app/Geekeey.Process.Dummy.App.csproj
Normal file
17
src/process.dummy.app/Geekeey.Process.Dummy.App.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Geekeey.Process</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Spectre.Console" PrivateAssets="compile" />
|
||||
<PackageReference Include="Spectre.Console.Cli" PrivateAssets="compile" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
42
src/process.dummy.app/Output.cs
Normal file
42
src/process.dummy.app/Output.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
internal sealed class Output : IDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
|
||||
public Output()
|
||||
{
|
||||
Console.CancelKeyPress += Cancel;
|
||||
}
|
||||
|
||||
public StreamReader Stdin { get; } = new(Console.OpenStandardInput(), leaveOpen: false);
|
||||
|
||||
public StreamWriter Stdout { get; } = new(Console.OpenStandardOutput(), leaveOpen: false);
|
||||
|
||||
public StreamWriter Stderr { get; } = new(Console.OpenStandardError(), leaveOpen: false);
|
||||
|
||||
public CancellationToken CancellationToken => _cts.Token;
|
||||
|
||||
public static Output Connect()
|
||||
{
|
||||
return new Output();
|
||||
}
|
||||
|
||||
private void Cancel(object? sender, ConsoleCancelEventArgs args)
|
||||
{
|
||||
args.Cancel = true;
|
||||
_cts.Cancel();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stdout.BaseStream.Flush();
|
||||
Stdout.Dispose();
|
||||
Stderr.BaseStream.Flush();
|
||||
Stderr.Dispose();
|
||||
Stdin.Dispose();
|
||||
Console.CancelKeyPress -= Cancel;
|
||||
_cts.Dispose();
|
||||
}
|
||||
}
|
||||
26
src/process.dummy.app/OutputTarget.cs
Normal file
26
src/process.dummy.app/OutputTarget.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
[Flags]
|
||||
internal enum OutputTarget
|
||||
{
|
||||
StdOut = 1,
|
||||
StdErr = 2,
|
||||
All = StdOut | StdErr
|
||||
}
|
||||
|
||||
internal static class OutputTargetExtensions
|
||||
{
|
||||
public static IEnumerable<StreamWriter> GetWriters(this Output output, OutputTarget target)
|
||||
{
|
||||
if (target.HasFlag(OutputTarget.StdOut))
|
||||
{
|
||||
yield return output.Stdout;
|
||||
}
|
||||
|
||||
if (target.HasFlag(OutputTarget.StdErr))
|
||||
{
|
||||
yield return output.Stderr;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/process.dummy.app/Program.cs
Normal file
45
src/process.dummy.app/Program.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Geekeey.Process.Testing.Fixture;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
private static readonly string? FileExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "exe" : null;
|
||||
|
||||
#pragma warning disable IL3000 // only for testing where we don't run in single files!
|
||||
private static readonly string AssemblyPath = Assembly.GetExecutingAssembly().Location;
|
||||
#pragma warning restore IL3000
|
||||
|
||||
public static string FilePath { get; } = Path.ChangeExtension(AssemblyPath, FileExtension);
|
||||
|
||||
private static Task<int> Main(string[] args)
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION", "false");
|
||||
var app = new CommandApp();
|
||||
app.Configure(Configuration);
|
||||
return app.RunAsync(args);
|
||||
|
||||
static void Configuration(IConfigurator configuration)
|
||||
{
|
||||
configuration.AddCommand<EchoCommand>("echo");
|
||||
configuration.AddCommand<EchoStdinCommand>("echo-stdin");
|
||||
configuration.AddCommand<EnvironmentCommand>("env");
|
||||
configuration.AddCommand<WorkingDirectoryCommand>("cwd");
|
||||
configuration.AddCommand<WorkingDirectoryCommand>("cwd");
|
||||
configuration.AddCommand<ExitCommand>("exit");
|
||||
configuration.AddCommand<LengthCommand>("length");
|
||||
configuration.AddCommand<SleepCommand>("sleep");
|
||||
configuration.AddBranch("generate", static generate =>
|
||||
{
|
||||
generate.AddCommand<GenerateBlobCommand>("blob");
|
||||
generate.AddCommand<GenerateClobCommand>("clob");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/process.dummy.app/_commands/EchoCommand.cs
Normal file
25
src/process.dummy.app/_commands/EchoCommand.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
internal sealed class EchoCommand : AsyncOutputCommand<EchoCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : OutputCommandSettings
|
||||
{
|
||||
[CommandOption("--separator <char>")] public string Separator { get; init; } = " ";
|
||||
[CommandArgument(0, "[line]")] public string[] Items { get; init; } = [];
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
using var output = Output.Connect();
|
||||
|
||||
foreach (var writer in output.GetWriters(settings.Target))
|
||||
{
|
||||
await writer.WriteLineAsync(string.Join(settings.Separator, settings.Items));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
41
src/process.dummy.app/_commands/EchoStdinCommand.cs
Normal file
41
src/process.dummy.app/_commands/EchoStdinCommand.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Buffers;
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
internal sealed class EchoStdinCommand : AsyncOutputCommand<EchoStdinCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : OutputCommandSettings
|
||||
{
|
||||
[CommandOption("--length")] public long Length { get; init; } = long.MaxValue;
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
using var output = Output.Connect();
|
||||
using var buffer = MemoryPool<byte>.Shared.Rent(81920);
|
||||
|
||||
var count = 0L;
|
||||
while (count < settings.Length)
|
||||
{
|
||||
var bytesWanted = (int)Math.Min(buffer.Memory.Length, settings.Length - count);
|
||||
|
||||
var bytesRead = await output.Stdin.BaseStream.ReadAsync(buffer.Memory[..bytesWanted], cancellationToken);
|
||||
if (bytesRead <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var writer in output.GetWriters(settings.Target))
|
||||
{
|
||||
await writer.BaseStream.WriteAsync(buffer.Memory[..bytesRead], cancellationToken);
|
||||
}
|
||||
|
||||
count += bytesRead;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
29
src/process.dummy.app/_commands/EnvironmentCommand.cs
Normal file
29
src/process.dummy.app/_commands/EnvironmentCommand.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
internal sealed class EnvironmentCommand : AsyncOutputCommand<EnvironmentCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : OutputCommandSettings
|
||||
{
|
||||
[CommandArgument(0, "<ARGUMENT>")] public string[] Variables { get; init; } = [];
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
using var output = Output.Connect();
|
||||
|
||||
foreach (var name in settings.Variables)
|
||||
{
|
||||
var value = Environment.GetEnvironmentVariable(name) ?? string.Empty;
|
||||
|
||||
foreach (var writer in output.GetWriters(settings.Target))
|
||||
{
|
||||
await writer.WriteLineAsync(value);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
21
src/process.dummy.app/_commands/ExitCommand.cs
Normal file
21
src/process.dummy.app/_commands/ExitCommand.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
internal sealed class ExitCommand : AsyncCommand<ExitCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(1, "<code>")] public int Code { get; init; }
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
using var output = Output.Connect();
|
||||
|
||||
await output.Stderr.WriteLineAsync($"Exit code set to {settings.Code}");
|
||||
|
||||
return settings.Code;
|
||||
}
|
||||
}
|
||||
40
src/process.dummy.app/_commands/GenerateBlobCommand.cs
Normal file
40
src/process.dummy.app/_commands/GenerateBlobCommand.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Buffers;
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
internal sealed class GenerateBlobCommand : AsyncOutputCommand<GenerateBlobCommand.Settings>
|
||||
{
|
||||
private readonly Random _random = new(1234567);
|
||||
|
||||
public sealed class Settings : OutputCommandSettings
|
||||
{
|
||||
[CommandOption("--length")] public long Length { get; init; } = 100_000;
|
||||
[CommandOption("--buffer")] public int BufferSize { get; init; } = 1024;
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
using var output = Output.Connect();
|
||||
|
||||
using var bytes = MemoryPool<byte>.Shared.Rent(settings.BufferSize);
|
||||
|
||||
var total = 0L;
|
||||
while (total < settings.Length)
|
||||
{
|
||||
_random.NextBytes(bytes.Memory.Span);
|
||||
|
||||
var count = (int)Math.Min(bytes.Memory.Length, settings.Length - total);
|
||||
foreach (var writer in output.GetWriters(settings.Target))
|
||||
{
|
||||
await writer.BaseStream.WriteAsync(bytes.Memory[..count], cancellationToken);
|
||||
}
|
||||
|
||||
total += count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
42
src/process.dummy.app/_commands/GenerateClobCommand.cs
Normal file
42
src/process.dummy.app/_commands/GenerateClobCommand.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Text;
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
internal sealed class GenerateClobCommand : AsyncOutputCommand<GenerateClobCommand.Settings>
|
||||
{
|
||||
private readonly Random _random = new(1234567);
|
||||
private readonly char[] _chars = [.. Enumerable.Range(32, 94).Select(i => (char)i)];
|
||||
|
||||
public sealed class Settings : OutputCommandSettings
|
||||
{
|
||||
[CommandOption("--length")] public int Length { get; init; } = 100_000;
|
||||
[CommandOption("--lines")] public int LinesCount { get; init; } = 1;
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
using var output = Output.Connect();
|
||||
|
||||
var buffer = new StringBuilder(settings.Length);
|
||||
|
||||
for (var line = 0; line < settings.LinesCount; line++)
|
||||
{
|
||||
buffer.Clear();
|
||||
|
||||
for (var i = 0; i < settings.Length; i++)
|
||||
{
|
||||
buffer.Append(_chars[_random.Next(0, _chars.Length)]);
|
||||
}
|
||||
|
||||
foreach (var writer in output.GetWriters(settings.Target))
|
||||
{
|
||||
await writer.WriteLineAsync(buffer.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
40
src/process.dummy.app/_commands/LengthCommand.cs
Normal file
40
src/process.dummy.app/_commands/LengthCommand.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Buffers;
|
||||
using System.Globalization;
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
internal sealed class LengthCommand : AsyncOutputCommand<LengthCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : OutputCommandSettings
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
using var output = Output.Connect();
|
||||
|
||||
using var buffer = MemoryPool<byte>.Shared.Rent(81920);
|
||||
|
||||
var count = 0L;
|
||||
while (true)
|
||||
{
|
||||
var bytesRead = await output.Stdin.BaseStream.ReadAsync(buffer.Memory, cancellationToken);
|
||||
if (bytesRead <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
count += bytesRead;
|
||||
}
|
||||
|
||||
foreach (var writer in output.GetWriters(settings.Target))
|
||||
{
|
||||
await writer.WriteLineAsync(count.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
34
src/process.dummy.app/_commands/SleepCommand.cs
Normal file
34
src/process.dummy.app/_commands/SleepCommand.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
internal sealed class SleepCommand : AsyncCommand<SleepCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(0, "[duration]")] public TimeSpan Duration { get; init; } = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
using var output = Output.Connect();
|
||||
|
||||
try
|
||||
{
|
||||
await Console.Out.WriteLineAsync($"Sleeping for {settings.Duration}...");
|
||||
await Console.Out.FlushAsync(CancellationToken.None);
|
||||
|
||||
await Task.Delay(settings.Duration, output.CancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
await Console.Out.WriteLineAsync("Canceled.");
|
||||
await Console.Out.FlushAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
await Console.Out.WriteLineAsync("Done.");
|
||||
await Console.Out.FlushAsync(CancellationToken.None);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
23
src/process.dummy.app/_commands/WorkingDirectoryCommand.cs
Normal file
23
src/process.dummy.app/_commands/WorkingDirectoryCommand.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
internal sealed class WorkingDirectoryCommand : AsyncOutputCommand<WorkingDirectoryCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : OutputCommandSettings
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
using var output = Output.Connect();
|
||||
|
||||
foreach (var writer in output.GetWriters(settings.Target))
|
||||
{
|
||||
await writer.WriteLineAsync(Directory.GetCurrentDirectory());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
181
src/process.tests/CancellationTests.cs
Normal file
181
src/process.tests/CancellationTests.cs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Text;
|
||||
|
||||
using Geekeey.Process.Buffered;
|
||||
|
||||
namespace Geekeey.Process.Tests;
|
||||
|
||||
internal sealed class CancellationTests
|
||||
{
|
||||
private static Action<string> NotifyOnStart(out TaskCompletionSource tcs)
|
||||
{
|
||||
// run the continuation async on the thread pool to allow the io reader to complete
|
||||
var source = tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
return line =>
|
||||
{
|
||||
if (line.Contains("Sleeping for", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
source.TrySetResult();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_and_cancel_it_immediately()
|
||||
{
|
||||
// Arrange
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
var stdout = new StringBuilder();
|
||||
|
||||
var target = PipeTarget.Merge(
|
||||
PipeTarget.ToDelegate(NotifyOnStart(out var tcs)),
|
||||
PipeTarget.ToStringBuilder(stdout)
|
||||
);
|
||||
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments(["sleep", "00:00:30"]) |
|
||||
target;
|
||||
|
||||
// Act
|
||||
var task = cmd.ExecuteAsync(cts.Token);
|
||||
await tcs.Task;
|
||||
await cts.CancelAsync();
|
||||
|
||||
// Assert
|
||||
await Assert.That(async () => await task).Throws<OperationCanceledException>();
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(ProcessTree.HasExited(task.ProcessId)).IsTrue();
|
||||
await Assert.That(stdout.ToString()).Contains("Sleeping for");
|
||||
await Assert.That(stdout.ToString()).DoesNotContain("Done.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_and_kill_it_immediately()
|
||||
{
|
||||
// Arrange
|
||||
var stdout = new StringBuilder();
|
||||
|
||||
var target = PipeTarget.Merge(
|
||||
PipeTarget.ToDelegate(NotifyOnStart(out var tcs)),
|
||||
PipeTarget.ToStringBuilder(stdout)
|
||||
);
|
||||
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments(["sleep", "00:00:30"]) |
|
||||
target;
|
||||
|
||||
// Act
|
||||
var task = cmd.ExecuteAsync();
|
||||
await tcs.Task;
|
||||
task.Kill();
|
||||
|
||||
// Assert
|
||||
await Assert.That(async () => await task).Throws<CommandExecutionException>();
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(ProcessTree.HasExited(task.ProcessId)).IsTrue();
|
||||
await Assert.That(stdout.ToString()).Contains("Sleeping for");
|
||||
await Assert.That(stdout.ToString()).DoesNotContain("Done.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_with_buffering_and_kill_it_immediately()
|
||||
{
|
||||
// Arrange
|
||||
var stdout = new StringBuilder();
|
||||
|
||||
var target = PipeTarget.Merge(
|
||||
PipeTarget.ToDelegate(NotifyOnStart(out var tcs)),
|
||||
PipeTarget.ToStringBuilder(stdout)
|
||||
);
|
||||
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments(["sleep", "00:00:30"]) |
|
||||
target;
|
||||
|
||||
// Act
|
||||
var task = cmd.ExecuteBufferedAsync();
|
||||
await tcs.Task;
|
||||
task.Kill();
|
||||
|
||||
// Assert
|
||||
await Assert.That(async () => await task).Throws<CommandExecutionException>();
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(ProcessTree.HasExited(task.ProcessId)).IsTrue();
|
||||
await Assert.That(stdout.ToString()).Contains("Sleeping for");
|
||||
await Assert.That(stdout.ToString()).DoesNotContain("Done.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_and_interrupt_it_immediately()
|
||||
{
|
||||
// Arrange
|
||||
var stdout = new StringBuilder();
|
||||
|
||||
var target = PipeTarget.Merge(
|
||||
PipeTarget.ToDelegate(NotifyOnStart(out var tcs)),
|
||||
PipeTarget.ToStringBuilder(stdout)
|
||||
);
|
||||
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments(["sleep", "00:00:30"]) |
|
||||
target;
|
||||
|
||||
// Act
|
||||
var task = cmd.ExecuteAsync();
|
||||
await tcs.Task;
|
||||
task.Interrupt();
|
||||
|
||||
// Assert
|
||||
await Assert.That(async () => await task).ThrowsNothing();
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(ProcessTree.HasExited(task.ProcessId)).IsTrue();
|
||||
await Assert.That(stdout.ToString()).Contains("Sleeping for");
|
||||
await Assert.That(stdout.ToString()).Contains("Done.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_with_buffering_and_interrupt_it_immediately()
|
||||
{
|
||||
// Arrange
|
||||
var stdout = new StringBuilder();
|
||||
|
||||
var target = PipeTarget.Merge(
|
||||
PipeTarget.ToDelegate(NotifyOnStart(out var tcs)),
|
||||
PipeTarget.ToStringBuilder(stdout)
|
||||
);
|
||||
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments(["sleep", "00:00:30"]) |
|
||||
target;
|
||||
|
||||
// Act
|
||||
var task = cmd.ExecuteBufferedAsync();
|
||||
await tcs.Task;
|
||||
task.Interrupt();
|
||||
|
||||
// Assert
|
||||
await Assert.That(async () => await task).ThrowsNothing();
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(ProcessTree.HasExited(task.ProcessId)).IsTrue();
|
||||
await Assert.That(stdout.ToString()).Contains("Sleeping for");
|
||||
await Assert.That(stdout.ToString()).Contains("Done.");
|
||||
}
|
||||
}
|
||||
}
|
||||
273
src/process.tests/CommandTests.cs
Normal file
273
src/process.tests/CommandTests.cs
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
namespace Geekeey.Process.Tests;
|
||||
|
||||
internal sealed class CommandTests
|
||||
{
|
||||
[Test]
|
||||
public async Task I_can_create_a_command_with_the_default_configuration()
|
||||
{
|
||||
var cmd = new Command("foo");
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(cmd.TargetFilePath).IsEqualTo("foo");
|
||||
await Assert.That(cmd.Arguments).IsEmpty();
|
||||
await Assert.That(cmd.WorkingDirPath).IsEqualTo(Directory.GetCurrentDirectory());
|
||||
await Assert.That(cmd.Environment).IsEmpty();
|
||||
await Assert.That(cmd.Validation).HasFlag(ValidationMode.ZeroExitCode);
|
||||
await Assert.That(cmd.StandardInputPipe).IsEqualTo(PipeSource.Null);
|
||||
await Assert.That(cmd.StandardOutputPipe).IsEqualTo(PipeTarget.Null);
|
||||
await Assert.That(cmd.StandardErrorPipe).IsEqualTo(PipeTarget.Null);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_target_file()
|
||||
{
|
||||
var cmd = new Command("foo");
|
||||
var modified = cmd.WithTargetFile("bar");
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo("bar");
|
||||
await Assert.That(modified.Arguments).IsEqualTo(cmd.Arguments);
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo(cmd.WorkingDirPath);
|
||||
await Assert.That(modified.Environment).IsEqualTo(cmd.Environment);
|
||||
await Assert.That(modified.Validation).IsEqualTo(cmd.Validation);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(cmd.StandardInputPipe);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(cmd.StandardOutputPipe);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(cmd.StandardErrorPipe);
|
||||
await Assert.That(cmd.TargetFilePath).IsNotEqualTo("bar");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_command_line_arguments()
|
||||
{
|
||||
var cmd = new Command("foo").WithArguments("xxx");
|
||||
var modified = cmd.WithArguments("abc def");
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo(cmd.TargetFilePath);
|
||||
await Assert.That(modified.Arguments).IsEqualTo("abc def");
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo(cmd.WorkingDirPath);
|
||||
await Assert.That(modified.Environment).IsEqualTo(cmd.Environment);
|
||||
await Assert.That(modified.Validation).IsEqualTo(cmd.Validation);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(cmd.StandardInputPipe);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(cmd.StandardOutputPipe);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(cmd.StandardErrorPipe);
|
||||
await Assert.That(cmd.Arguments).IsNotEqualTo("abc def");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_command_line_arguments_by_passing_an_array()
|
||||
{
|
||||
var cmd = new Command("foo").WithArguments("xxx");
|
||||
var modified = cmd.WithArguments(["abc", "def"]);
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo(cmd.TargetFilePath);
|
||||
await Assert.That(modified.Arguments).IsEqualTo("abc def");
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo(cmd.WorkingDirPath);
|
||||
await Assert.That(modified.Environment).IsEqualTo(cmd.Environment);
|
||||
await Assert.That(modified.Validation).IsEqualTo(cmd.Validation);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(cmd.StandardInputPipe);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(cmd.StandardOutputPipe);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(cmd.StandardErrorPipe);
|
||||
await Assert.That(cmd.Arguments).IsNotEqualTo("abc def");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_command_line_arguments_using_a_builder()
|
||||
{
|
||||
var cmd = new Command("foo").WithArguments("xxx");
|
||||
var modified = cmd.WithArguments(args => args
|
||||
.Add("-a")
|
||||
.Add("foo bar")
|
||||
.Add("\"foo\\\\bar\"")
|
||||
.Add(3.14)
|
||||
.Add(["foo", "bar"])
|
||||
.Add([-10, 12.12]));
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo(cmd.TargetFilePath);
|
||||
await Assert.That(modified.Arguments).IsEqualTo("-a \"foo bar\" \"\\\"foo\\\\bar\\\"\" 3.14 foo bar -10 12.12");
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo(cmd.WorkingDirPath);
|
||||
await Assert.That(modified.Environment).IsEqualTo(cmd.Environment);
|
||||
await Assert.That(modified.Validation).IsEqualTo(cmd.Validation);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(cmd.StandardInputPipe);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(cmd.StandardOutputPipe);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(cmd.StandardErrorPipe);
|
||||
await Assert.That(cmd.Arguments).IsNotEqualTo("-a \"foo bar\" \"\\\"foo\\\\bar\\\"\" 3.14 foo bar -10 12.12");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_working_directory()
|
||||
{
|
||||
var cmd = new Command("foo").WithWorkingDirectory("xxx");
|
||||
var modified = cmd.WithWorkingDirectory("new");
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo(cmd.TargetFilePath);
|
||||
await Assert.That(modified.Arguments).IsEqualTo(cmd.Arguments);
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo("new");
|
||||
await Assert.That(modified.Environment).IsEqualTo(cmd.Environment);
|
||||
await Assert.That(modified.Validation).IsEqualTo(cmd.Validation);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(cmd.StandardInputPipe);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(cmd.StandardOutputPipe);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(cmd.StandardErrorPipe);
|
||||
await Assert.That(cmd.WorkingDirPath).IsNotEqualTo("new");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_environment_variables()
|
||||
{
|
||||
var cmd = new Command("foo").WithEnvironment(e => e.Set("xxx", "xxx"));
|
||||
var vars = new Dictionary<string, string?>
|
||||
{
|
||||
["name"] = "value",
|
||||
["key"] = "door",
|
||||
};
|
||||
var modified = cmd.WithEnvironment(vars);
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo(cmd.TargetFilePath);
|
||||
await Assert.That(modified.Arguments).IsEqualTo(cmd.Arguments);
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo(cmd.WorkingDirPath);
|
||||
await Assert.That(modified.Environment).IsEqualTo(vars);
|
||||
await Assert.That(modified.Validation).IsEqualTo(cmd.Validation);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(cmd.StandardInputPipe);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(cmd.StandardOutputPipe);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(cmd.StandardErrorPipe);
|
||||
await Assert.That(cmd.Environment).IsNotEqualTo(vars);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_environment_variables_using_a_builder()
|
||||
{
|
||||
var cmd = new Command("foo").WithEnvironment(e => e.Set("xxx", "xxx"));
|
||||
var modified = cmd.WithEnvironment(env => env
|
||||
.Set("name", "value")
|
||||
.Set("key", "door")
|
||||
.Set(new Dictionary<string, string?>
|
||||
{
|
||||
["zzz"] = "yyy",
|
||||
["aaa"] = "bbb",
|
||||
}));
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
var vars = new Dictionary<string, string?>
|
||||
{
|
||||
["name"] = "value",
|
||||
["key"] = "door",
|
||||
["zzz"] = "yyy",
|
||||
["aaa"] = "bbb",
|
||||
};
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo(cmd.TargetFilePath);
|
||||
await Assert.That(modified.Arguments).IsEqualTo(cmd.Arguments);
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo(cmd.WorkingDirPath);
|
||||
await Assert.That(modified.Environment).IsEquivalentTo(vars);
|
||||
await Assert.That(modified.Validation).IsEqualTo(cmd.Validation);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(cmd.StandardInputPipe);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(cmd.StandardOutputPipe);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(cmd.StandardErrorPipe);
|
||||
await Assert.That(cmd.Environment).IsNotEqualTo(vars);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_result_validation_strategy()
|
||||
{
|
||||
var cmd = new Command("foo").WithExitValidation(ValidationMode.ZeroExitCode);
|
||||
var modified = cmd.WithExitValidation(ValidationMode.None);
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo(cmd.TargetFilePath);
|
||||
await Assert.That(modified.Arguments).IsEqualTo(cmd.Arguments);
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo(cmd.WorkingDirPath);
|
||||
await Assert.That(modified.Environment).IsEqualTo(cmd.Environment);
|
||||
await Assert.That(modified.Validation).IsEqualTo(ValidationMode.None);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(cmd.StandardInputPipe);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(cmd.StandardOutputPipe);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(cmd.StandardErrorPipe);
|
||||
await Assert.That(cmd.Validation).IsNotEqualTo(ValidationMode.None);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_stdin_pipe()
|
||||
{
|
||||
var cmd = new Command("foo").WithStandardInputPipe(PipeSource.Null);
|
||||
var pipeSource = PipeSource.FromStream(Stream.Null);
|
||||
var modified = cmd.WithStandardInputPipe(pipeSource);
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo(cmd.TargetFilePath);
|
||||
await Assert.That(modified.Arguments).IsEqualTo(cmd.Arguments);
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo(cmd.WorkingDirPath);
|
||||
await Assert.That(modified.Environment).IsEqualTo(cmd.Environment);
|
||||
await Assert.That(modified.Validation).IsEqualTo(cmd.Validation);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(pipeSource);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(cmd.StandardOutputPipe);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(cmd.StandardErrorPipe);
|
||||
await Assert.That(cmd.StandardInputPipe).IsNotEqualTo(pipeSource);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_stdout_pipe()
|
||||
{
|
||||
var cmd = new Command("foo").WithStandardOutputPipe(PipeTarget.Null);
|
||||
var pipeTarget = PipeTarget.ToStream(Stream.Null);
|
||||
var modified = cmd.WithStandardOutputPipe(pipeTarget);
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo(cmd.TargetFilePath);
|
||||
await Assert.That(modified.Arguments).IsEqualTo(cmd.Arguments);
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo(cmd.WorkingDirPath);
|
||||
await Assert.That(modified.Environment).IsEqualTo(cmd.Environment);
|
||||
await Assert.That(modified.Validation).IsEqualTo(cmd.Validation);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(cmd.StandardInputPipe);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(pipeTarget);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(cmd.StandardErrorPipe);
|
||||
await Assert.That(cmd.StandardOutputPipe).IsNotEqualTo(pipeTarget);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_configure_the_stderr_pipe()
|
||||
{
|
||||
var cmd = new Command("foo").WithStandardErrorPipe(PipeTarget.Null);
|
||||
var pipeTarget = PipeTarget.ToStream(Stream.Null);
|
||||
var modified = cmd.WithStandardErrorPipe(pipeTarget);
|
||||
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(modified.TargetFilePath).IsEqualTo(cmd.TargetFilePath);
|
||||
await Assert.That(modified.Arguments).IsEqualTo(cmd.Arguments);
|
||||
await Assert.That(modified.WorkingDirPath).IsEqualTo(cmd.WorkingDirPath);
|
||||
await Assert.That(modified.Environment).IsEqualTo(cmd.Environment);
|
||||
await Assert.That(modified.Validation).IsEqualTo(cmd.Validation);
|
||||
await Assert.That(modified.StandardInputPipe).IsEqualTo(cmd.StandardInputPipe);
|
||||
await Assert.That(modified.StandardOutputPipe).IsEqualTo(cmd.StandardOutputPipe);
|
||||
await Assert.That(modified.StandardErrorPipe).IsEqualTo(pipeTarget);
|
||||
await Assert.That(cmd.StandardErrorPipe).IsNotEqualTo(pipeTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
139
src/process.tests/ExecuteTests.cs
Normal file
139
src/process.tests/ExecuteTests.cs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using Geekeey.Process.Buffered;
|
||||
|
||||
namespace Geekeey.Process.Tests;
|
||||
|
||||
internal sealed class ExecuteTests
|
||||
{
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_and_get_the_exit_code_and_execution_time()
|
||||
{
|
||||
// Arrange
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments(["echo"]);
|
||||
|
||||
// Act
|
||||
var result = await cmd.ExecuteAsync();
|
||||
|
||||
await Assert.That(result.ExitCode).IsZero();
|
||||
|
||||
// Assert
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(result.ExitCode).IsZero();
|
||||
await Assert.That(result.IsSuccess).IsTrue();
|
||||
await Assert.That(result.RunTime).IsGreaterThan(TimeSpan.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_and_get_the_associated_process_id()
|
||||
{
|
||||
// Arrange
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments(["echo"]);
|
||||
|
||||
// Act
|
||||
var task = cmd.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
await Assert.That(task.ProcessId).IsNotZero();
|
||||
|
||||
await task;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_with_a_configured_awaiter()
|
||||
{
|
||||
// Arrange
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments(["echo"]);
|
||||
|
||||
// Act + Assert
|
||||
await cmd.ExecuteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_try_to_execute_a_command_and_get_an_error_if_the_target_file_does_not_exist()
|
||||
{
|
||||
// Arrange
|
||||
var cmd = new Command("some_exe_with_does_not_exits");
|
||||
|
||||
// Act + Assert
|
||||
await Assert.That(() => cmd.ExecuteAsync()).Throws<InvalidOperationException>()
|
||||
.WithInnerException();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_with_a_custom_working_directory()
|
||||
{
|
||||
// Arrange
|
||||
using var dir = TestTempDirectory.Create();
|
||||
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments("cwd")
|
||||
.WithWorkingDirectory(dir.Path);
|
||||
|
||||
// Act
|
||||
var result = await cmd.ExecuteBufferedAsync();
|
||||
|
||||
await Assert.That(result.ExitCode).IsZero();
|
||||
|
||||
// Assert
|
||||
var lines = result.StandardOutput.Split(Environment.NewLine);
|
||||
await Assert.That(lines).Contains(dir.Path);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_with_additional_environment_variables()
|
||||
{
|
||||
// Arrange
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments(["env", "foo", "bar"])
|
||||
.WithEnvironment(env => env
|
||||
.Set("foo", "hello")
|
||||
.Set("bar", "world"));
|
||||
|
||||
// Act
|
||||
var result = await cmd.ExecuteBufferedAsync();
|
||||
|
||||
await Assert.That(result.ExitCode).IsZero();
|
||||
|
||||
// Assert
|
||||
var lines = result.StandardOutput.Split(Environment.NewLine);
|
||||
await Assert.That(lines).Contains("hello");
|
||||
await Assert.That(lines).Contains("world");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_with_some_environment_variables_overwritten()
|
||||
{
|
||||
// Arrange
|
||||
var key = Guid.NewGuid();
|
||||
var variableToKeep = $"GKY_TEST_KEEP_{key}";
|
||||
var variableToOverwrite = $"GKY_TEST_OVERWRITE_{key}";
|
||||
var variableToUnset = $"GKY_TEST_UNSET_{key}";
|
||||
|
||||
using var a = TestEnvironment.Create(variableToKeep, "keep");
|
||||
using var b = TestEnvironment.Create(variableToOverwrite, "overwrite");
|
||||
using var c = TestEnvironment.Create(variableToUnset, "unset");
|
||||
|
||||
var cmd = new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments(["env", variableToKeep, variableToOverwrite, variableToUnset])
|
||||
.WithEnvironment(env => env
|
||||
.Set(variableToOverwrite, "overwritten")
|
||||
.Set(variableToUnset, null));
|
||||
|
||||
// Act
|
||||
var result = await cmd.ExecuteBufferedAsync();
|
||||
|
||||
await Assert.That(result.ExitCode).IsZero();
|
||||
|
||||
// Assert
|
||||
var lines = result.StandardOutput.Split(Environment.NewLine);
|
||||
await Assert.That(lines).Contains("keep");
|
||||
await Assert.That(lines).Contains("overwritten");
|
||||
}
|
||||
}
|
||||
22
src/process.tests/Geekeey.Process.Tests.csproj
Normal file
22
src/process.tests/Geekeey.Process.Tests.csproj
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Geekeey.Process</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TUnit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\process\Geekeey.Process.csproj" />
|
||||
<ProjectReference Include="..\process.dummy.app\Geekeey.Process.Dummy.App.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
81
src/process.tests/LineBreakTests.cs
Normal file
81
src/process.tests/LineBreakTests.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
namespace Geekeey.Process.Tests;
|
||||
|
||||
internal sealed class LineBreakTests
|
||||
{
|
||||
private static Command Echo()
|
||||
{
|
||||
return new Command(Testing.Fixture.Program.FilePath)
|
||||
.WithArguments("echo-stdin");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_and_split_the_stdout_by_newline()
|
||||
{
|
||||
// Arrange
|
||||
const string data = "Foo\nBar\nBaz";
|
||||
|
||||
var stdOutLines = new List<string>();
|
||||
|
||||
var cmd = data | Echo() | stdOutLines.Add;
|
||||
|
||||
// Act
|
||||
await cmd.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
await Assert.That(stdOutLines).IsEquivalentTo(["Foo", "Bar", "Baz"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_and_split_the_stdout_by_caret_return()
|
||||
{
|
||||
// Arrange
|
||||
const string data = "Foo\rBar\rBaz";
|
||||
|
||||
var stdOutLines = new List<string>();
|
||||
|
||||
var cmd = data | Echo() | stdOutLines.Add;
|
||||
|
||||
// Act
|
||||
await cmd.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
await Assert.That(stdOutLines).IsEquivalentTo(["Foo", "Bar", "Baz"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_and_split_the_stdout_by_caret_return_followed_by_newline()
|
||||
{
|
||||
// Arrange
|
||||
const string data = "Foo\r\nBar\r\nBaz";
|
||||
|
||||
var stdOutLines = new List<string>();
|
||||
|
||||
var cmd = data | Echo() | stdOutLines.Add;
|
||||
|
||||
// Act
|
||||
await cmd.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
await Assert.That(stdOutLines).IsEquivalentTo(["Foo", "Bar", "Baz"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_and_split_the_stdout_by_newline_while_including_empty_lines()
|
||||
{
|
||||
// Arrange
|
||||
const string data = "Foo\r\rBar\n\nBaz";
|
||||
|
||||
var stdOutLines = new List<string>();
|
||||
|
||||
var cmd = data | Echo() | stdOutLines.Add;
|
||||
|
||||
// Act
|
||||
await cmd.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
await Assert.That(stdOutLines).IsEquivalentTo(["Foo", "", "Bar", "", "Baz"]);
|
||||
}
|
||||
}
|
||||
50
src/process.tests/PathResolutionTests.cs
Normal file
50
src/process.tests/PathResolutionTests.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using Geekeey.Process.Buffered;
|
||||
|
||||
namespace Geekeey.Process.Tests;
|
||||
|
||||
internal sealed class PathResolutionTests
|
||||
{
|
||||
[Test]
|
||||
public async Task I_can_execute_a_command_on_an_executable_using_its_short_name()
|
||||
{
|
||||
// Arrange
|
||||
var cmd = new Command("dotnet")
|
||||
.WithArguments("--version");
|
||||
|
||||
// Act
|
||||
var result = await cmd.ExecuteBufferedAsync();
|
||||
|
||||
// Assert
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(result.ExitCode).IsEqualTo(0);
|
||||
await Assert.That(result.StandardOutput.Trim()).Matches(@"^\d+\.\d+\.\d+$");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Platform(PlatformAttribute.Windows)]
|
||||
public async Task I_can_execute_a_command_on_a_script_using_its_short_name()
|
||||
{
|
||||
// Arrange
|
||||
using var dir = TestTempDirectory.Create();
|
||||
await File.WriteAllTextAsync(Path.Combine(dir.Path, "script.cmd"), "@echo hi");
|
||||
|
||||
using var _1 = TestEnvironment.ExtendPath(dir.Path);
|
||||
var cmd = new Command("script");
|
||||
|
||||
// Act
|
||||
var result = await cmd.ExecuteBufferedAsync();
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
using (Assert.Multiple())
|
||||
{
|
||||
await Assert.That(result.ExitCode).IsEqualTo(0);
|
||||
await Assert.That(result.StandardOutput.Trim()).IsEqualTo("hi");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue