Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proc.StartLongRunning allows you to yield after waiting for started. #16

Merged
merged 1 commit into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/Proc/ObservableProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,12 @@
public virtual IDisposable SubscribeLinesAndCharacters(
Action<LineOut> onNext, Action<Exception> onError,
Action<CharactersOut> onNextCharacters,
Action<Exception> onExceptionCharacters
) =>
Action<Exception> onExceptionCharacters,
Action? onCompleted = null

Check warning on line 147 in src/Proc/ObservableProcess.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 147 in src/Proc/ObservableProcess.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 147 in src/Proc/ObservableProcess.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 147 in src/Proc/ObservableProcess.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 147 in src/Proc/ObservableProcess.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 147 in src/Proc/ObservableProcess.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
) =>
Subscribe(
Observer.Create(onNext, onError, delegate { }),
Observer.Create(onNextCharacters, onExceptionCharacters, delegate { })
Observer.Create(onNext, onError, onCompleted ?? delegate { }),
Observer.Create(onNextCharacters, onExceptionCharacters, onCompleted ?? delegate { })
);

public virtual IDisposable SubscribeLines(Action<LineOut> onNext) =>
Expand Down
43 changes: 26 additions & 17 deletions src/Proc/Proc.StartLongRunning.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@ internal LongRunningApplicationSubscription(ObservableProcess process, Composite

private IDisposable Subscription { get; }

public ObservableProcess Process { get; }
private ObservableProcess Process { get; }

public bool Running { get; internal set; }

internal ManualResetEvent WaitHandle { get; } = new(false);

/// <inheritdoc cref="ObservableProcessBase{TConsoleOut}.SendControlC(int)"/>>
public bool SendControlC(int processId) => Process.SendControlC(processId);

/// <inheritdoc cref="ObservableProcessBase{TConsoleOut}.SendControlC()"/>>
public void SendControlC() => Process.SendControlC();

public void Dispose()
Expand Down Expand Up @@ -52,48 +59,50 @@ public static partial class Proc
/// <returns>The exit code and whether the process completed</returns>
public static LongRunningApplicationSubscription StartLongRunning(LongRunningArguments arguments, TimeSpan waitForStartedConfirmation, IConsoleOutWriter consoleOutWriter = null)
{
var started = false;
var confirmWaitHandle = new ManualResetEvent(false);
var composite = new CompositeDisposable();
var process = new ObservableProcess(arguments);
var subscription = new LongRunningApplicationSubscription(process, composite);
consoleOutWriter ??= new ConsoleOutColorWriter();

var startedConfirmation = arguments.StartedConfirmationHandler ?? (_ => true);

if (arguments.StartedConfirmationHandler != null && arguments.StopBufferingAfterStarted)
arguments.KeepBufferingLines = _ => !started;
arguments.KeepBufferingLines = _ => !subscription.Running;

Exception seenException = null;
composite.Add(process);
composite.Add(process.SubscribeLinesAndCharacters(
l =>
{
if (startedConfirmation(l))
{
confirmWaitHandle.Set();
started = true;
}
if (!startedConfirmation(l)) return;
subscription.Running = true;
subscription.WaitHandle.Set();
},
e =>
{
seenException = e;
confirmWaitHandle.Set();
subscription.Running = false;
subscription.WaitHandle.Set();
},
l => consoleOutWriter.Write(l),
l => consoleOutWriter.Write(l)
)
l => consoleOutWriter.Write(l),
onCompleted: () =>
{
subscription.Running = false;
subscription.WaitHandle.Set();
})
);

if (seenException != null) ExceptionDispatchInfo.Capture(seenException).Throw();
if (arguments.StartedConfirmationHandler == null)
{
confirmWaitHandle.Set();
started = true;
subscription.Running = true;
subscription.WaitHandle.Set();
}
else
{
var completed = confirmWaitHandle.WaitOne(waitForStartedConfirmation);
if (completed) return new(process, composite);
var completed = subscription.WaitHandle.WaitOne(waitForStartedConfirmation);
if (completed) return subscription;
var pwd = arguments.WorkingDirectory;
var args = arguments.Args.NaivelyQuoteArguments();
var printBinary = arguments.OnlyPrintBinaryInExceptionMessage
Expand All @@ -102,7 +111,7 @@ public static LongRunningApplicationSubscription StartLongRunning(LongRunningArg
throw new ProcExecException($"Could not yield started confirmation after {waitForStartedConfirmation} while running {printBinary}");
}

return new(process, composite);
return subscription;
}
}
}
16 changes: 3 additions & 13 deletions tests/Proc.Tests/LineOutputTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentAssertions;
using ProcNet.Std;
using Xunit;
using Xunit.Abstractions;

namespace ProcNet.Tests
{
Expand All @@ -19,7 +19,7 @@ public void OverwriteLines()
}


public class LineOutputTestCases : TestsBase
public class LineOutputTestCases(ITestOutputHelper output) : TestsBase
{
private static readonly string _expected = @"
Windows IP Configuration
Expand Down Expand Up @@ -99,7 +99,7 @@ public void SubscribeLinesSeesAllLines()
[Fact]
public void ConsoleWriterSeesAllLines()
{
var writer = new TestConsoleOutWriter();
var writer = new TestConsoleOutWriter(output);
var args = TestCaseArguments("MoreText");
var result = Proc.Start(args, WaitTimeout, writer);
result.ExitCode.Should().HaveValue();
Expand All @@ -110,15 +110,5 @@ public void ConsoleWriterSeesAllLines()
lines[i].Should().Be(_expectedLines[i], i.ToString());
}

public class TestConsoleOutWriter : IConsoleOutWriter
{
private readonly StringBuilder _sb = new StringBuilder();
public string[] Lines => _sb.ToString().Replace("\r\n", "\n").Split(new [] {"\n"}, StringSplitOptions.None);
public string Text => _sb.ToString();

public void Write(Exception e) => throw e;

public void Write(ConsoleOut consoleOut) => consoleOut.CharsOrString(c=>_sb.Append(new string(c)), s=>_sb.AppendLine(s));
}
}
}
28 changes: 18 additions & 10 deletions tests/Proc.Tests/LongRunningTests.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using ProcNet.Std;
using FluentAssertions;
using Xunit;
using Xunit.Abstractions;

namespace ProcNet.Tests
{
public class LongRunningTests : TestsBase
public class LongRunningTests(ITestOutputHelper output) : TestsBase
{
[Fact]
public async Task LongRunningShouldSeeAllOutput()
{
var args = LongRunningTestCaseArguments("LongRunning");
args.StartedConfirmationHandler = l => l.Line == "Started!";

var outputWriter = new LineOutputTestCases.TestConsoleOutWriter();
using (var result = Proc.StartLongRunning(args, WaitTimeout, outputWriter))
var outputWriter = new TestConsoleOutWriter(output);

using (var process = Proc.StartLongRunning(args, WaitTimeout, outputWriter))
{
process.Running.Should().BeTrue();
await Task.Delay(TimeSpan.FromSeconds(2));
process.Running.Should().BeFalse();
}

var lines = outputWriter.Lines;
lines.Length.Should().BeGreaterThan(0);
Expand All @@ -35,11 +38,12 @@ public async Task LongRunningShouldStopBufferingOutputWhenAsked()
args.StartedConfirmationHandler = l => l.Line == "Started!";
args.StopBufferingAfterStarted = true;

var outputWriter = new LineOutputTestCases.TestConsoleOutWriter();
var outputWriter = new TestConsoleOutWriter(output);
var sw = Stopwatch.StartNew();

using (var result = Proc.StartLongRunning(args, WaitTimeout, outputWriter))
using (var process = Proc.StartLongRunning(args, WaitTimeout, outputWriter))
{
process.Running.Should().BeTrue();
sw.Elapsed.Should().BeGreaterThan(TimeSpan.FromSeconds(1));
var lines = outputWriter.Lines;
lines.Length.Should().BeGreaterThan(0);
Expand All @@ -49,6 +53,7 @@ public async Task LongRunningShouldStopBufferingOutputWhenAsked()
await Task.Delay(TimeSpan.FromSeconds(2));
lines.Should().NotContain(s => s.StartsWith("Data after startup:"));
}

// we dispose before the program's completion
sw.Elapsed.Should().BeLessThan(TimeSpan.FromSeconds(20));

Expand All @@ -58,10 +63,13 @@ public async Task LongRunningShouldStopBufferingOutputWhenAsked()
public async Task LongRunningWithoutConfirmationHandler()
{
var args = LongRunningTestCaseArguments("LongRunning");
var outputWriter = new LineOutputTestCases.TestConsoleOutWriter();
var outputWriter = new TestConsoleOutWriter(output);

using (var result = Proc.StartLongRunning(args, WaitTimeout, outputWriter))
using (var process = Proc.StartLongRunning(args, WaitTimeout, outputWriter))
{
process.Running.Should().BeTrue();
await Task.Delay(TimeSpan.FromSeconds(2));
}

var lines = outputWriter.Lines;
lines.Should().Contain(s => s.StartsWith("Starting up:"));
Expand Down
19 changes: 19 additions & 0 deletions tests/Proc.Tests/TestConsoleOutWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Text;
using ProcNet.Std;
using Xunit.Abstractions;

public class TestConsoleOutWriter(ITestOutputHelper output) : IConsoleOutWriter
{
private readonly StringBuilder _sb = new();
public string[] Lines => _sb.ToString().Replace("\r\n", "\n").Split(new [] {"\n"}, StringSplitOptions.None);
public string Text => _sb.ToString();

public void Write(Exception e) => throw e;

public void Write(ConsoleOut consoleOut)
{
consoleOut.CharsOrString(c => _sb.Append(new string(c)), s => _sb.AppendLine(s));
consoleOut.CharsOrString(c => output.WriteLine(new string(c)), output.WriteLine);
}
}
Loading