diff --git a/CliWrap.FSharp.sln b/CliWrap.FSharp.sln
index 882bfbf..7b2893e 100644
--- a/CliWrap.FSharp.sln
+++ b/CliWrap.FSharp.sln
@@ -1,4 +1,4 @@
-
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
@@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{BE2AB152
global.json = global.json
.config\dotnet-tools.json = .config\dotnet-tools.json
Directory.Build.props = Directory.Build.props
+ scripts\update-vendored-code.sh = scripts\update-vendored-code.sh
+ patches\0001-CliWrap.Tests.Dummy.csproj.patch = patches\0001-CliWrap.Tests.Dummy.csproj.patch
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github", "github", "{FE346152-5716-4224-9B25-C6B3815C70CA}"
@@ -40,9 +42,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{BC5059
.gitmodules = .gitmodules
EndProjectSection
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "vendor", "vendor", "{711E438A-D184-4513-921C-C4D7AE85E2B9}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CliWrap.Tests.Dummy", "src\CliWrap.Tests.Dummy\CliWrap.Tests.Dummy.csproj", "{26F29434-8760-4387-BF01-4FB1FA65426E}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CliWrap.Tests.Dummy", "vendor\CliWrap\CliWrap.Tests.Dummy\CliWrap.Tests.Dummy.csproj", "{C06FC79C-B308-4078-8C36-AB2F42C583E0}"
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CliWrap.FSharp.Tests.Dummy", "src\CliWrap.FSharp.Tests.Dummy\CliWrap.FSharp.Tests.Dummy.fsproj", "{47479E30-792D-4B63-A1A9-D5981968D21E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -61,14 +63,19 @@ Global
{12BFD7AA-776D-4B58-8AC3-241AF39EED84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12BFD7AA-776D-4B58-8AC3-241AF39EED84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12BFD7AA-776D-4B58-8AC3-241AF39EED84}.Release|Any CPU.Build.0 = Release|Any CPU
- {C06FC79C-B308-4078-8C36-AB2F42C583E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C06FC79C-B308-4078-8C36-AB2F42C583E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C06FC79C-B308-4078-8C36-AB2F42C583E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C06FC79C-B308-4078-8C36-AB2F42C583E0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {26F29434-8760-4387-BF01-4FB1FA65426E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {26F29434-8760-4387-BF01-4FB1FA65426E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {26F29434-8760-4387-BF01-4FB1FA65426E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {26F29434-8760-4387-BF01-4FB1FA65426E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {47479E30-792D-4B63-A1A9-D5981968D21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {47479E30-792D-4B63-A1A9-D5981968D21E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {47479E30-792D-4B63-A1A9-D5981968D21E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {47479E30-792D-4B63-A1A9-D5981968D21E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2E2FBB43-30C0-45A1-8DAC-D7A48411EC7A} = {DE85DD40-1128-4648-A2EB-9A1FE73C28A4}
{12BFD7AA-776D-4B58-8AC3-241AF39EED84} = {DE85DD40-1128-4648-A2EB-9A1FE73C28A4}
- {C06FC79C-B308-4078-8C36-AB2F42C583E0} = {711E438A-D184-4513-921C-C4D7AE85E2B9}
+ {26F29434-8760-4387-BF01-4FB1FA65426E} = {DE85DD40-1128-4648-A2EB-9A1FE73C28A4}
+ {47479E30-792D-4B63-A1A9-D5981968D21E} = {DE85DD40-1128-4648-A2EB-9A1FE73C28A4}
EndGlobalSection
EndGlobal
diff --git a/patches/0001-CliWrap.Tests.Dummy.csproj.patch b/patches/0001-CliWrap.Tests.Dummy.csproj.patch
new file mode 100644
index 0000000..c488e50
--- /dev/null
+++ b/patches/0001-CliWrap.Tests.Dummy.csproj.patch
@@ -0,0 +1,13 @@
+5,6c5,6
+< net8.0
+< $(TargetFrameworks);net48
+---
+> net8.0
+> false
+11d10
+<
+16c15
+<
+\ No newline at end of file
+---
+>
diff --git a/scripts/update-vendored-code.sh b/scripts/update-vendored-code.sh
new file mode 100755
index 0000000..43ee47e
--- /dev/null
+++ b/scripts/update-vendored-code.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+set -eum
+
+root="$(git rev-parse --show-toplevel)"
+patch="$root/patches/0001-CliWrap.Tests.Dummy.csproj.patch"
+vendorDir="$root/vendor/CliWrap"
+targetDir="$root/src/CliWrap.Tests.Dummy"
+
+git submodule foreach --recursive git clean -xfd
+rm -rf "$targetDir" || echo 'Target is clean'
+
+[ '--clean' == "${1:-}" ] && exit 0
+
+cp -r "$vendorDir/CliWrap.Tests.Dummy" "$targetDir"
+patch "$targetDir/CliWrap.Tests.Dummy.csproj" <"$patch"
diff --git a/src/CliWrap.FSharp.Tests.Dummy/CliWrap.FSharp.Tests.Dummy.fsproj b/src/CliWrap.FSharp.Tests.Dummy/CliWrap.FSharp.Tests.Dummy.fsproj
new file mode 100644
index 0000000..fb60bdd
--- /dev/null
+++ b/src/CliWrap.FSharp.Tests.Dummy/CliWrap.FSharp.Tests.Dummy.fsproj
@@ -0,0 +1,17 @@
+
+
+
+ Exe
+ net8.0
+ UnMango.CliWrap.FSharp.Tests.Dummy
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CliWrap.FSharp.Tests.Dummy/Program.fs b/src/CliWrap.FSharp.Tests.Dummy/Program.fs
new file mode 100644
index 0000000..32c4fa0
--- /dev/null
+++ b/src/CliWrap.FSharp.Tests.Dummy/Program.fs
@@ -0,0 +1,81 @@
+namespace UnMango.CliWrap.FSharp.Tests.Dummy
+
+open System
+open System.Buffers
+open System.CommandLine
+open System.CommandLine.Invocation
+open System.IO
+open System.Reflection
+open System.Runtime.InteropServices
+open System.Threading.Tasks
+
+module Opt =
+ let add o (r: Command) =
+ r.AddOption(o)
+ r
+
+module Cmd =
+ let add c (r: Command) =
+ r.AddCommand(c)
+ r
+
+ let handler (f: InvocationContext -> Task) (c: Command) =
+ c.SetHandler(f)
+ c
+
+module Commands =
+ let streams =
+ function
+ | "stdout" -> [ Console.OpenStandardOutput() ]
+ | "stderr" -> [ Console.OpenStandardError() ]
+ | "all" -> [ Console.OpenStandardOutput(); Console.OpenStandardError() ]
+ | _ -> failwith "unsupported target"
+
+ let rec generateBytes (rand: Random) (buf: IMemoryOwner) (streams: Stream list) len =
+ function
+ | total when total >= len -> ()
+ | total ->
+ rand.NextBytes(buf.Memory.Span)
+ let wanted = Math.Min(int64 buf.Memory.Length, len - total)
+
+ for stream in streams do
+ stream.Write(buf.Memory.Slice(0, int wanted).Span)
+
+ generateBytes rand buf streams len (total + wanted)
+
+ let generate =
+ let targetOpt = Option("--target", (fun () -> "stdout"))
+ let lengthOpt = Option("--length", (fun () -> 100_000L))
+ let bufferOpt = Option("--buffer", (fun () -> 1024))
+ let rand = Random(1234567)
+
+ Command("generate")
+ |> Cmd.add (
+ Command("binary")
+ |> Opt.add targetOpt
+ |> Opt.add lengthOpt
+ |> Opt.add bufferOpt
+ |> Cmd.handler (fun c -> task {
+ let streams = c.ParseResult.GetValueForOption(targetOpt) |> streams
+ let len = c.ParseResult.GetValueForOption(lengthOpt)
+ let size = c.ParseResult.GetValueForOption(bufferOpt)
+ use buf = MemoryPool.Shared.Rent(size)
+ generateBytes rand buf streams len 0L
+ })
+ )
+
+type Program =
+ static member FilePath =
+ Path.ChangeExtension(
+ Assembly.GetExecutingAssembly().Location,
+ if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
+ "exe"
+ else
+ null
+ )
+
+module Program =
+ let command = RootCommand("Dummy program for testing") |> Cmd.add Commands.generate
+
+ []
+ let main args = command.Invoke args
diff --git a/src/CliWrap.FSharp.Tests.Dummy/packages.lock.json b/src/CliWrap.FSharp.Tests.Dummy/packages.lock.json
new file mode 100644
index 0000000..f9c2be0
--- /dev/null
+++ b/src/CliWrap.FSharp.Tests.Dummy/packages.lock.json
@@ -0,0 +1,19 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net8.0": {
+ "FSharp.Core": {
+ "type": "Direct",
+ "requested": "[8.0.300, )",
+ "resolved": "8.0.300",
+ "contentHash": "Jv44fV7TNglyMku89lQcA4Q6mFKLyHb2bs1Yb72nvSVc+cHplEnoZ4XQUaaTLJGUTx/iMqcrkYGtaLzkkIhpaA=="
+ },
+ "System.CommandLine": {
+ "type": "Direct",
+ "requested": "[2.0.0-beta4.22272.1, )",
+ "resolved": "2.0.0-beta4.22272.1",
+ "contentHash": "1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg=="
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CliWrap.FSharp.Tests/CliTests.fs b/src/CliWrap.FSharp.Tests/CliTests.fs
index 80068d8..5591d20 100644
--- a/src/CliWrap.FSharp.Tests/CliTests.fs
+++ b/src/CliWrap.FSharp.Tests/CliTests.fs
@@ -5,7 +5,6 @@ open System.Linq
open System.Text
open System.Threading
open CliWrap
-open CliWrap.Tests
open Xunit
open FsCheck
open FsCheck.Xunit
@@ -40,7 +39,7 @@ let ``Should create command the long way``
[]
let ``Should configure a single argument`` (arg: NonNull) =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithArguments(arg.Get)
let actual = cmd |> Cli.arg arg.Get
@@ -49,7 +48,7 @@ let ``Should configure a single argument`` (arg: NonNull) =
[]
let ``Should configure arguments`` (arg: NonNull) =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithArguments([ arg.Get ])
let actual = cmd |> Cli.args [ arg.Get ]
@@ -58,7 +57,7 @@ let ``Should configure arguments`` (arg: NonNull) =
[]
let ``Should configure arguments with escape`` (arg: NonNull) escape =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithArguments([ arg.Get ], escape)
let actual = cmd |> Cli.argse [ arg.Get ] escape
@@ -67,7 +66,7 @@ let ``Should configure arguments with escape`` (arg: NonNull) escape =
[]
let ``Should configure arguments with builder`` (arg: NonNull) =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithArguments(fun b -> b.Add(arg.Get) |> ignore)
let actual = cmd |> Cli.argsf _.Add(arg.Get)
@@ -76,7 +75,7 @@ let ``Should configure arguments with builder`` (arg: NonNull) =
[]
let ``Should configure credentials`` (arg: NonNull) =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithCredentials(Credentials(arg.Get))
let actual = cmd |> Cli.creds (Credentials(arg.Get))
@@ -85,7 +84,7 @@ let ``Should configure credentials`` (arg: NonNull) =
[]
let ``Should configure credentials with builder`` (arg: NonNull) =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithCredentials(fun b -> b.SetUserName(arg.Get) |> ignore)
let actual = cmd |> Cli.credsf _.SetUserName(arg.Get)
@@ -94,7 +93,7 @@ let ``Should configure credentials with builder`` (arg: NonNull) =
[]
let ``Should configure environment variables`` (key: NonNull) (value: NonNull) =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected =
cmd.WithEnvironmentVariables((dict [ key.Get, value.Get ]).AsReadOnly())
@@ -104,7 +103,7 @@ let ``Should configure environment variables`` (key: NonNull) (value: No
[]
let ``Should configure environment variables with builder`` (key: NonNull) (value: NonNull) =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected =
cmd.WithEnvironmentVariables(fun b -> b.Set(key.Get, value.Get) |> ignore)
@@ -114,7 +113,7 @@ let ``Should configure environment variables with builder`` (key: NonNull]
let ``Should execute asynchronously`` () = task {
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let! expected = cmd.ExecuteAsync()
let! actual = cmd |> Cli.exec
@@ -125,7 +124,7 @@ let ``Should execute asynchronously`` () = task {
[]
let ``Should execute asynchronously with cancellation`` () = task {
use cts = new CancellationTokenSource()
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let! expected = cmd.ExecuteAsync(cts.Token)
let! actual = cmd |> Cli.Task.exec cts.Token
@@ -137,7 +136,7 @@ let ``Should execute asynchronously with cancellation`` () = task {
let ``Should execute asynchronously with forceful and graceful tokens`` () = task {
use forceful = new CancellationTokenSource()
use graceful = new CancellationTokenSource()
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let! expected = cmd.ExecuteAsync(forceful.Token, graceful.Token)
let! actual = cmd |> Cli.Task.execf forceful.Token graceful.Token
@@ -148,7 +147,7 @@ let ``Should execute asynchronously with forceful and graceful tokens`` () = tas
[]
let ``Should configure stdin`` (value: NonNull) =
let input = PipeSource.FromString value.Get
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithStandardInputPipe(input)
let actual = cmd |> Cli.stdin input
@@ -158,7 +157,7 @@ let ``Should configure stdin`` (value: NonNull) =
[]
let ``Should configure stdout`` () =
let pipe = PipeTarget.ToStringBuilder(StringBuilder())
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithStandardOutputPipe(pipe)
let actual = cmd |> Cli.stdout pipe
@@ -168,7 +167,7 @@ let ``Should configure stdout`` () =
[]
let ``Should configure stderr`` () =
let pipe = PipeTarget.ToStringBuilder(StringBuilder())
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithStandardErrorPipe(pipe)
let actual = cmd |> Cli.stderr pipe
@@ -177,7 +176,7 @@ let ``Should configure stderr`` () =
[]
let ``Should configure target file`` (file: NonNull) =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithTargetFile(file.Get)
let actual = cmd |> Cli.target file.Get
@@ -186,7 +185,7 @@ let ``Should configure target file`` (file: NonNull) =
[]
let ``Should configure validation`` () =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithValidation(CommandResultValidation.None)
let actual = cmd |> Cli.validation CommandResultValidation.None
@@ -195,7 +194,7 @@ let ``Should configure validation`` () =
[]
let ``Should configure working directory`` (dir: NonNull) =
- let cmd = Command(Dummy.Program.FilePath)
+ let cmd = Command(Tests.Dummy.Program.FilePath)
let expected = cmd.WithWorkingDirectory(dir.Get)
let actual = cmd |> Cli.workDir dir.Get
@@ -204,7 +203,7 @@ let ``Should configure working directory`` (dir: NonNull) =
[]
let ``Should convert to string`` (arg: NonNull) =
- let cmd = Command(Dummy.Program.FilePath).WithArguments([ arg.Get ])
+ let cmd = Command(Tests.Dummy.Program.FilePath).WithArguments([ arg.Get ])
let expected = cmd.ToString()
let actual = cmd |> Cli.toString
diff --git a/src/CliWrap.FSharp.Tests/CliWrap.FSharp.Tests.fsproj b/src/CliWrap.FSharp.Tests/CliWrap.FSharp.Tests.fsproj
index 60bf36e..8bb7c93 100644
--- a/src/CliWrap.FSharp.Tests/CliWrap.FSharp.Tests.fsproj
+++ b/src/CliWrap.FSharp.Tests/CliWrap.FSharp.Tests.fsproj
@@ -25,7 +25,7 @@
-
+
diff --git a/src/CliWrap.FSharp.Tests/CommandBuilderTests.fs b/src/CliWrap.FSharp.Tests/CommandBuilderTests.fs
index 21aa4af..7e9289f 100644
--- a/src/CliWrap.FSharp.Tests/CommandBuilderTests.fs
+++ b/src/CliWrap.FSharp.Tests/CommandBuilderTests.fs
@@ -7,7 +7,6 @@ open System.Text
open System.Threading
open CliWrap
open CliWrap.Buffered
-open CliWrap.Tests
open Xunit
open FsCheck
open FsCheck.Xunit
@@ -22,22 +21,22 @@ let ``Should configure target file path`` target =
[]
let ``Should configure args`` (a: NonNull list) =
let input = a |> List.map _.Get
- let expected = Command(Dummy.Program.FilePath).WithArguments(input)
- let actual = command Dummy.Program.FilePath { args input }
+ let expected = Command(Tests.Dummy.Program.FilePath).WithArguments(input)
+ let actual = command Tests.Dummy.Program.FilePath { args input }
actual.Arguments = expected.Arguments
[]
let ``Should configure string args`` (a: NonNull) =
- let expected = Command(Dummy.Program.FilePath).WithArguments(a.Get)
- let actual = command Dummy.Program.FilePath { args a.Get }
+ let expected = Command(Tests.Dummy.Program.FilePath).WithArguments(a.Get)
+ let actual = command Tests.Dummy.Program.FilePath { args a.Get }
actual.Arguments = expected.Arguments
[]
let ``Should execute CommandTask asynchronously`` () = task {
- let! expected = Command(Dummy.Program.FilePath).ExecuteAsync()
+ let! expected = Command(Tests.Dummy.Program.FilePath).ExecuteAsync()
let! actual =
- command Dummy.Program.FilePath {
+ command Tests.Dummy.Program.FilePath {
exec
async
}
@@ -48,38 +47,38 @@ let ``Should execute CommandTask asynchronously`` () = task {
[]
let ``Should execute asynchronously`` () = task {
- let! expected = Command(Dummy.Program.FilePath).ExecuteAsync()
- let! actual = command Dummy.Program.FilePath { async } |> Async.StartAsTask
+ let! expected = Command(Tests.Dummy.Program.FilePath).ExecuteAsync()
+ let! actual = command Tests.Dummy.Program.FilePath { async } |> Async.StartAsTask
Assert.Equal(expected.ExitCode, actual.ExitCode)
}
[]
let ``Should execute asynchronously with cancellation`` () = task {
use cts = new CancellationTokenSource()
- let! expected = Command(Dummy.Program.FilePath).ExecuteAsync(cts.Token)
- let! actual = command Dummy.Program.FilePath { async cts.Token } |> Async.StartAsTask
+ let! expected = Command(Tests.Dummy.Program.FilePath).ExecuteAsync(cts.Token)
+ let! actual = command Tests.Dummy.Program.FilePath { async cts.Token } |> Async.StartAsTask
Assert.Equal(expected.ExitCode, actual.ExitCode)
}
[]
let ``Should execute asynchronously as task`` () = task {
- let! expected = Command(Dummy.Program.FilePath).ExecuteAsync()
- let! actual = command Dummy.Program.FilePath { exec }
+ let! expected = Command(Tests.Dummy.Program.FilePath).ExecuteAsync()
+ let! actual = command Tests.Dummy.Program.FilePath { exec }
Assert.Equal(expected.ExitCode, actual.ExitCode)
}
[]
let ``Should execute buffered`` () = task {
- let! expected = Command(Dummy.Program.FilePath).ExecuteBufferedAsync()
- let! actual = command Dummy.Program.FilePath { buffered }
+ let! expected = Command(Tests.Dummy.Program.FilePath).ExecuteBufferedAsync()
+ let! actual = command Tests.Dummy.Program.FilePath { buffered }
Assert.Equal(expected.ExitCode, actual.ExitCode)
}
[]
let ``Should execute buffered with encoding`` () = task {
let encoding = Encoding.UTF8
- let! expected = Command(Dummy.Program.FilePath).ExecuteBufferedAsync(encoding)
- let! actual = command Dummy.Program.FilePath { buffered encoding }
+ let! expected = Command(Tests.Dummy.Program.FilePath).ExecuteBufferedAsync(encoding)
+ let! actual = command Tests.Dummy.Program.FilePath { buffered encoding }
Assert.Equal(expected.ExitCode, actual.ExitCode)
}
@@ -87,26 +86,26 @@ let ``Should execute buffered with encoding`` () = task {
let ``Should execute buffered with encoding and cancellation`` () = task {
let encoding = Encoding.UTF8
use cts = new CancellationTokenSource()
- let! expected = Command(Dummy.Program.FilePath).ExecuteBufferedAsync(encoding, cts.Token)
- let! actual = command Dummy.Program.FilePath { buffered encoding cts.Token }
+ let! expected = Command(Tests.Dummy.Program.FilePath).ExecuteBufferedAsync(encoding, cts.Token)
+ let! actual = command Tests.Dummy.Program.FilePath { buffered encoding cts.Token }
Assert.Equal(expected.ExitCode, actual.ExitCode)
}
[]
let ``Should configure environment variables`` var =
let expected =
- Command(Dummy.Program.FilePath)
+ Command(Tests.Dummy.Program.FilePath)
.WithEnvironmentVariables((dict [ var ]).AsReadOnly())
- let actual = command Dummy.Program.FilePath { env [ var ] }
+ let actual = command Tests.Dummy.Program.FilePath { env [ var ] }
actual.EnvironmentVariables.SequenceEqual(expected.EnvironmentVariables)
[]
let ``Should configure stdin`` () = task {
let input = PipeSource.FromString "testing"
- let expected = Command(Dummy.Program.FilePath).WithStandardInputPipe(input)
+ let expected = Command(Tests.Dummy.Program.FilePath).WithStandardInputPipe(input)
- let actual = command Dummy.Program.FilePath { stdin input }
+ let actual = command Tests.Dummy.Program.FilePath { stdin input }
let a, b = new MemoryStream(), new MemoryStream()
do! expected.StandardInputPipe.CopyToAsync(a)
@@ -120,13 +119,13 @@ let ``Should configure stdout`` () = task {
let a, b = StringBuilder(), StringBuilder()
let! _ =
- Command(Dummy.Program.FilePath)
- .WithArguments([ "generate binary"; "--target"; "all" ])
+ Command(Tests.Dummy.Program.FilePath)
+ .WithArguments([ "generate"; "binary"; "--target"; "all" ])
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(a))
.ExecuteAsync()
- let! _ = command Dummy.Program.FilePath {
- args [ "generate binary"; "--target"; "all" ]
+ let! _ = command Tests.Dummy.Program.FilePath {
+ args [ "generate"; "binary"; "--target"; "all" ]
stdout (PipeTarget.ToStringBuilder(b))
exec
}
@@ -139,13 +138,13 @@ let ``Should configure stderr`` () = task {
let a, b = StringBuilder(), StringBuilder()
let! _ =
- Command(Dummy.Program.FilePath)
- .WithArguments([ "generate binary"; "--target"; "all" ])
+ Command(Tests.Dummy.Program.FilePath)
+ .WithArguments([ "generate"; "binary"; "--target"; "all" ])
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(a))
.ExecuteAsync()
- let! _ = command Dummy.Program.FilePath {
- args [ "generate binary"; "--target"; "all" ]
+ let! _ = command Tests.Dummy.Program.FilePath {
+ args [ "generate"; "binary"; "--target"; "all" ]
validation CommandResultValidation.None
stderr (PipeTarget.ToStringBuilder(b))
exec
@@ -156,6 +155,6 @@ let ``Should configure stderr`` () = task {
[]
let ``Should configure working directory`` directory =
- let expected = Command(Dummy.Program.FilePath).WithWorkingDirectory(directory)
- let actual = command Dummy.Program.FilePath { workingDirectory directory }
+ let expected = Command(Tests.Dummy.Program.FilePath).WithWorkingDirectory(directory)
+ let actual = command Tests.Dummy.Program.FilePath { workingDirectory directory }
actual.WorkingDirPath = expected.WorkingDirPath
diff --git a/src/CliWrap.FSharp.Tests/packages.lock.json b/src/CliWrap.FSharp.Tests/packages.lock.json
index f6e1762..3e80df5 100644
--- a/src/CliWrap.FSharp.Tests/packages.lock.json
+++ b/src/CliWrap.FSharp.Tests/packages.lock.json
@@ -29,9 +29,9 @@
},
"FSharp.Core": {
"type": "Direct",
- "requested": "[8.0.200, )",
- "resolved": "8.0.200",
- "contentHash": "qnxoF3Fu0HzfOeYdrwmQOsLP1v+OtOMSIYkNVUwf6nGqWzL03Hh4r6VFCvCb54jlsgtt3WADVYkKkrgdeY5kiQ=="
+ "requested": "[8.0.300, )",
+ "resolved": "8.0.300",
+ "contentHash": "Jv44fV7TNglyMku89lQcA4Q6mFKLyHb2bs1Yb72nvSVc+cHplEnoZ4XQUaaTLJGUTx/iMqcrkYGtaLzkkIhpaA=="
},
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
@@ -60,11 +60,6 @@
"resolved": "2.5.7",
"contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w=="
},
- "CliFx": {
- "type": "Transitive",
- "resolved": "2.3.5",
- "contentHash": "WaZDt7FC1ViEzS5m7w7lOWT/aluZ28fFkPcRdkQ+7DowqjTmr5YZswNIDqSuBDaeGXPe8VzTZKMltKrXhIOt9Q=="
- },
"CliWrap": {
"type": "Transitive",
"resolved": "3.6.6",
@@ -97,10 +92,10 @@
"resolved": "13.0.1",
"contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
},
- "System.Memory": {
+ "System.CommandLine": {
"type": "Transitive",
- "resolved": "4.5.5",
- "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
+ "resolved": "2.0.0-beta4.22272.1",
+ "contentHash": "1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg=="
},
"System.Reflection.Metadata": {
"type": "Transitive",
@@ -147,18 +142,18 @@
"xunit.extensibility.core": "[2.7.0]"
}
},
- "cliwrap.tests.dummy": {
+ "cliwrap.fsharp.tests.dummy": {
"type": "Project",
"dependencies": {
- "CliFx": "[2.3.5, )",
- "System.Memory": "[4.5.5, )"
+ "FSharp.Core": "[8.0.300, )",
+ "System.CommandLine": "[2.0.0-beta4.22272.1, )"
}
},
"UnMango.CliWrap.FSharp": {
"type": "Project",
"dependencies": {
"CliWrap": "[3.6.6, )",
- "FSharp.Core": "[8.0.200, )"
+ "FSharp.Core": "[8.0.300, )"
}
}
}
diff --git a/src/CliWrap.FSharp/packages.lock.json b/src/CliWrap.FSharp/packages.lock.json
index 3d666e3..78ad91a 100644
--- a/src/CliWrap.FSharp/packages.lock.json
+++ b/src/CliWrap.FSharp/packages.lock.json
@@ -10,9 +10,9 @@
},
"FSharp.Core": {
"type": "Direct",
- "requested": "[8.0.200, )",
- "resolved": "8.0.200",
- "contentHash": "qnxoF3Fu0HzfOeYdrwmQOsLP1v+OtOMSIYkNVUwf6nGqWzL03Hh4r6VFCvCb54jlsgtt3WADVYkKkrgdeY5kiQ=="
+ "requested": "[8.0.300, )",
+ "resolved": "8.0.300",
+ "contentHash": "Jv44fV7TNglyMku89lQcA4Q6mFKLyHb2bs1Yb72nvSVc+cHplEnoZ4XQUaaTLJGUTx/iMqcrkYGtaLzkkIhpaA=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
diff --git a/src/CliWrap.Tests.Dummy/CliWrap.Tests.Dummy.csproj b/src/CliWrap.Tests.Dummy/CliWrap.Tests.Dummy.csproj
new file mode 100644
index 0000000..f7e2fa7
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/CliWrap.Tests.Dummy.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net8.0
+ false
+
+
+
+
+
+
+
+
+
diff --git a/src/CliWrap.Tests.Dummy/Commands/EchoCommand.cs b/src/CliWrap.Tests.Dummy/Commands/EchoCommand.cs
new file mode 100644
index 0000000..97aaced
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Commands/EchoCommand.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using CliFx;
+using CliFx.Attributes;
+using CliFx.Infrastructure;
+using CliWrap.Tests.Dummy.Commands.Shared;
+
+namespace CliWrap.Tests.Dummy.Commands;
+
+[Command("echo")]
+public class EchoCommand : ICommand
+{
+ [CommandParameter(0)]
+ public required IReadOnlyList Items { get; init; }
+
+ [CommandOption("target")]
+ public OutputTarget Target { get; init; } = OutputTarget.StdOut;
+
+ [CommandOption("separator")]
+ public string Separator { get; init; } = " ";
+
+ public async ValueTask ExecuteAsync(IConsole console)
+ {
+ foreach (var writer in console.GetWriters(Target))
+ await writer.WriteLineAsync(string.Join(Separator, Items));
+ }
+}
diff --git a/src/CliWrap.Tests.Dummy/Commands/EchoStdInCommand.cs b/src/CliWrap.Tests.Dummy/Commands/EchoStdInCommand.cs
new file mode 100644
index 0000000..54459b6
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Commands/EchoStdInCommand.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Buffers;
+using System.Threading.Tasks;
+using CliFx;
+using CliFx.Attributes;
+using CliFx.Infrastructure;
+using CliWrap.Tests.Dummy.Commands.Shared;
+
+namespace CliWrap.Tests.Dummy.Commands;
+
+[Command("echo stdin")]
+public class EchoStdInCommand : ICommand
+{
+ [CommandOption("target")]
+ public OutputTarget Target { get; init; } = OutputTarget.StdOut;
+
+ [CommandOption("length")]
+ public long Length { get; init; } = long.MaxValue;
+
+ public async ValueTask ExecuteAsync(IConsole console)
+ {
+ using var buffer = MemoryPool.Shared.Rent(81920);
+
+ var totalBytesRead = 0L;
+ while (totalBytesRead < Length)
+ {
+ var bytesWanted = (int)Math.Min(buffer.Memory.Length, Length - totalBytesRead);
+
+ var bytesRead = await console.Input.BaseStream.ReadAsync(buffer.Memory[..bytesWanted]);
+ if (bytesRead <= 0)
+ break;
+
+ foreach (var writer in console.GetWriters(Target))
+ await writer.BaseStream.WriteAsync(buffer.Memory[..bytesRead]);
+
+ totalBytesRead += bytesRead;
+ }
+ }
+}
diff --git a/src/CliWrap.Tests.Dummy/Commands/EnvironmentCommand.cs b/src/CliWrap.Tests.Dummy/Commands/EnvironmentCommand.cs
new file mode 100644
index 0000000..5fef363
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Commands/EnvironmentCommand.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using CliFx;
+using CliFx.Attributes;
+using CliFx.Infrastructure;
+
+namespace CliWrap.Tests.Dummy.Commands;
+
+[Command("env")]
+public class EnvironmentCommand : ICommand
+{
+ [CommandParameter(0)]
+ public IReadOnlyList Names { get; init; } = Array.Empty();
+
+ public async ValueTask ExecuteAsync(IConsole console)
+ {
+ foreach (var name in Names)
+ await console.Output.WriteLineAsync(Environment.GetEnvironmentVariable(name));
+ }
+}
diff --git a/src/CliWrap.Tests.Dummy/Commands/ExitCommand.cs b/src/CliWrap.Tests.Dummy/Commands/ExitCommand.cs
new file mode 100644
index 0000000..a2a5076
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Commands/ExitCommand.cs
@@ -0,0 +1,22 @@
+using System.Threading.Tasks;
+using CliFx;
+using CliFx.Attributes;
+using CliFx.Exceptions;
+using CliFx.Infrastructure;
+
+namespace CliWrap.Tests.Dummy.Commands;
+
+[Command("exit")]
+public class ExitCommand : ICommand
+{
+ [CommandParameter(0)]
+ public int ExitCode { get; init; }
+
+ public ValueTask ExecuteAsync(IConsole console)
+ {
+ if (ExitCode != 0)
+ throw new CommandException($"Exit code set to {ExitCode}", ExitCode);
+
+ return default;
+ }
+}
diff --git a/src/CliWrap.Tests.Dummy/Commands/GenerateBinaryCommand.cs b/src/CliWrap.Tests.Dummy/Commands/GenerateBinaryCommand.cs
new file mode 100644
index 0000000..20c6200
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Commands/GenerateBinaryCommand.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Buffers;
+using System.Threading.Tasks;
+using CliFx;
+using CliFx.Attributes;
+using CliFx.Infrastructure;
+using CliWrap.Tests.Dummy.Commands.Shared;
+
+namespace CliWrap.Tests.Dummy.Commands;
+
+[Command("generate binary")]
+public class GenerateBinaryCommand : ICommand
+{
+ // Tests rely on the random seed being fixed
+ private readonly Random _random = new(1234567);
+
+ [CommandOption("target")]
+ public OutputTarget Target { get; init; } = OutputTarget.StdOut;
+
+ [CommandOption("length")]
+ public long Length { get; init; } = 100_000;
+
+ [CommandOption("buffer")]
+ public int BufferSize { get; init; } = 1024;
+
+ public async ValueTask ExecuteAsync(IConsole console)
+ {
+ using var buffer = MemoryPool.Shared.Rent(BufferSize);
+
+ var totalBytesGenerated = 0L;
+ while (totalBytesGenerated < Length)
+ {
+ _random.NextBytes(buffer.Memory.Span);
+
+ var bytesWanted = (int)Math.Min(buffer.Memory.Length, Length - totalBytesGenerated);
+
+ foreach (var writer in console.GetWriters(Target))
+ await writer.BaseStream.WriteAsync(buffer.Memory[..bytesWanted]);
+
+ totalBytesGenerated += bytesWanted;
+ }
+ }
+}
diff --git a/src/CliWrap.Tests.Dummy/Commands/GenerateTextCommand.cs b/src/CliWrap.Tests.Dummy/Commands/GenerateTextCommand.cs
new file mode 100644
index 0000000..603c211
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Commands/GenerateTextCommand.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using CliFx;
+using CliFx.Attributes;
+using CliFx.Infrastructure;
+using CliWrap.Tests.Dummy.Commands.Shared;
+
+namespace CliWrap.Tests.Dummy.Commands;
+
+[Command("generate text")]
+public class GenerateTextCommand : ICommand
+{
+ // Tests rely on the random seed being fixed
+ private readonly Random _random = new(1234567);
+ private readonly char[] _allowedChars = Enumerable.Range(32, 94).Select(i => (char)i).ToArray();
+
+ [CommandOption("target")]
+ public OutputTarget Target { get; init; } = OutputTarget.StdOut;
+
+ [CommandOption("length")]
+ public int Length { get; init; } = 100_000;
+
+ [CommandOption("lines")]
+ public int LinesCount { get; init; } = 1;
+
+ public async ValueTask ExecuteAsync(IConsole console)
+ {
+ for (var line = 0; line < LinesCount; line++)
+ {
+ var buffer = new StringBuilder(Length);
+
+ for (var i = 0; i < Length; i++)
+ {
+ buffer.Append(_allowedChars[_random.Next(0, _allowedChars.Length)]);
+ }
+
+ foreach (var writer in console.GetWriters(Target))
+ await writer.WriteLineAsync(buffer.ToString());
+ }
+ }
+}
diff --git a/src/CliWrap.Tests.Dummy/Commands/LengthStdInCommand.cs b/src/CliWrap.Tests.Dummy/Commands/LengthStdInCommand.cs
new file mode 100644
index 0000000..22eb14b
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Commands/LengthStdInCommand.cs
@@ -0,0 +1,29 @@
+using System.Buffers;
+using System.Globalization;
+using System.Threading.Tasks;
+using CliFx;
+using CliFx.Attributes;
+using CliFx.Infrastructure;
+
+namespace CliWrap.Tests.Dummy.Commands;
+
+[Command("length stdin")]
+public class LengthStdInCommand : ICommand
+{
+ public async ValueTask ExecuteAsync(IConsole console)
+ {
+ using var buffer = MemoryPool.Shared.Rent(81920);
+
+ var totalBytesRead = 0L;
+ while (true)
+ {
+ var bytesRead = await console.Input.BaseStream.ReadAsync(buffer.Memory);
+ if (bytesRead <= 0)
+ break;
+
+ totalBytesRead += bytesRead;
+ }
+
+ await console.Output.WriteLineAsync(totalBytesRead.ToString(CultureInfo.InvariantCulture));
+ }
+}
diff --git a/src/CliWrap.Tests.Dummy/Commands/Shared/OutputTarget.cs b/src/CliWrap.Tests.Dummy/Commands/Shared/OutputTarget.cs
new file mode 100644
index 0000000..47dfe65
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Commands/Shared/OutputTarget.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using CliFx.Infrastructure;
+
+namespace CliWrap.Tests.Dummy.Commands.Shared;
+
+[Flags]
+public enum OutputTarget
+{
+ StdOut = 1,
+ StdErr = 2,
+ All = StdOut | StdErr
+}
+
+internal static class OutputTargetExtensions
+{
+ public static IEnumerable GetWriters(this IConsole console, OutputTarget target)
+ {
+ if (target.HasFlag(OutputTarget.StdOut))
+ yield return console.Output;
+
+ if (target.HasFlag(OutputTarget.StdErr))
+ yield return console.Error;
+ }
+}
diff --git a/src/CliWrap.Tests.Dummy/Commands/SleepCommand.cs b/src/CliWrap.Tests.Dummy/Commands/SleepCommand.cs
new file mode 100644
index 0000000..64bdc3d
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Commands/SleepCommand.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Threading.Tasks;
+using CliFx;
+using CliFx.Attributes;
+using CliFx.Infrastructure;
+
+namespace CliWrap.Tests.Dummy.Commands;
+
+[Command("sleep")]
+public class SleepCommand : ICommand
+{
+ [CommandParameter(0)]
+ public TimeSpan Duration { get; init; } = TimeSpan.FromSeconds(1);
+
+ public async ValueTask ExecuteAsync(IConsole console)
+ {
+ var cancellationToken = console.RegisterCancellationHandler();
+
+ try
+ {
+ await console.Output.WriteLineAsync($"Sleeping for {Duration}...");
+ await Task.Delay(Duration, cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ await console.Output.WriteLineAsync("Canceled.");
+ return;
+ }
+
+ await console.Output.WriteLineAsync("Done.");
+ }
+}
diff --git a/src/CliWrap.Tests.Dummy/Commands/WorkingDirectoryCommand.cs b/src/CliWrap.Tests.Dummy/Commands/WorkingDirectoryCommand.cs
new file mode 100644
index 0000000..8bf9524
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Commands/WorkingDirectoryCommand.cs
@@ -0,0 +1,14 @@
+using System.IO;
+using System.Threading.Tasks;
+using CliFx;
+using CliFx.Attributes;
+using CliFx.Infrastructure;
+
+namespace CliWrap.Tests.Dummy.Commands;
+
+[Command("cwd")]
+public class WorkingDirectoryCommand : ICommand
+{
+ public async ValueTask ExecuteAsync(IConsole console) =>
+ await console.Output.WriteLineAsync(Directory.GetCurrentDirectory());
+}
diff --git a/src/CliWrap.Tests.Dummy/Program.cs b/src/CliWrap.Tests.Dummy/Program.cs
new file mode 100644
index 0000000..76faa28
--- /dev/null
+++ b/src/CliWrap.Tests.Dummy/Program.cs
@@ -0,0 +1,32 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using CliFx;
+
+namespace CliWrap.Tests.Dummy;
+
+public static class Program
+{
+ // Path to the apphost
+ public static string FilePath { get; } =
+ Path.ChangeExtension(
+ Assembly.GetExecutingAssembly().Location,
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "exe" : null
+ );
+
+ public static async Task Main(string[] args)
+ {
+ // Make sure color codes are not produced because we rely on the output in tests
+ Environment.SetEnvironmentVariable(
+ "DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION",
+ "false"
+ );
+
+ return await new CliApplicationBuilder()
+ .AddCommandsFromThisAssembly()
+ .Build()
+ .RunAsync(args);
+ }
+}