File: Helpers\FixtureCli.cs
Web Access
Project: src\tests\Aspire.Tray.Tests\Aspire.Tray.Tests.csproj (Aspire.Tray.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
 
namespace Aspire.Tray.Tests.Helpers;
 
internal sealed class FixtureCli : IDisposable
{
    private readonly DirectoryInfo _directory;
    public string Path { get; }
 
    public FixtureCli(string body)
    {
        if (OperatingSystem.IsWindows())
        {
            throw new PlatformNotSupportedException("This fixture requires /bin/sh.");
        }
 
        _directory = Directory.CreateTempSubdirectory("aspire-tray-cli-");
        Path = System.IO.Path.Combine(_directory.FullName, "aspire");
        // Quote $0 so fixture paths with spaces remain a single filename. Record argv one
        // argument per line, independently of shell metacharacters in an AppHost path.
        File.WriteAllText(Path, "#!/bin/sh\nprintf '%s\\n' \"$$\" > \"$0.pid\"\nprintf '%s\\n' \"$@\" > \"$0.args\"\n" + body + "\n");
        File.SetUnixFileMode(Path, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
    }
 
    public string[] ReadArguments() => File.ReadAllLines(Path + ".args");
 
    public Task<int> WaitForPidAsync(CancellationToken cancellationToken)
        => WaitForPidFileAsync(Path + ".pid", cancellationToken);
 
    public Task<int> WaitForDetachedPidAsync(CancellationToken cancellationToken)
        => WaitForPidFileAsync(Path + ".detached.pid", cancellationToken);
 
    private static async Task<int> WaitForPidFileAsync(string path, CancellationToken cancellationToken)
    {
        using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(10));
        using var lifetime = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, deadline.Token);
        while (true)
        {
            if (File.Exists(path)
                && int.TryParse(await File.ReadAllTextAsync(path, lifetime.Token).ConfigureAwait(false),
                    NumberStyles.Integer, CultureInfo.InvariantCulture, out var pid))
            {
                return pid;
            }
            await Task.Delay(25, lifetime.Token).ConfigureAwait(false);
        }
    }
 
    public void Dispose() => _directory.Delete(recursive: true);
}