File: Scripts\FileHelperTests.cs
Web Access
Project: src\tests\Aspire.Acquisition.Tests\Aspire.Acquisition.Tests.csproj (Aspire.Acquisition.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.Diagnostics;
using System.Runtime.Versioning;
using Microsoft.DotNet.XUnitExtensions;
using Xunit;
 
namespace Aspire.Acquisition.Tests.Scripts;
 
[SkipOnPlatform(TestPlatforms.Windows, "Executable mock scripts are only exec'd directly on Unix")]
[UnsupportedOSPlatform("windows")]
public class FileHelperTests
{
    // Mock executables must be written with FileHelper.WriteExecutableFileAsync so the test host never holds a
    // writable descriptor that a concurrently forked child could inherit (ETXTBSY, see
    // https://github.com/microsoft/aspire/issues/18732). This checks the helper's contract: exact UTF-8 contents
    // without a BOM, owner-only executable mode, and a file that can be exec'd immediately, including when it
    // replaces an existing non-executable file.
    [Fact]
    public async Task WriteExecutableFileAsync_WritesExecutableScriptThatRunsImmediately()
    {
        using var env = new TestEnvironment();
        var path = Path.Combine(env.TempDirectory, "mock tool");
        // Seed the stale file through the helper too, so this test never holds a writable descriptor for a file
        // it later execs. Then clear the execute bit so the assertions below prove the helper sets it again.
        await FileHelper.WriteExecutableFileAsync(path, "stale");
        File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite);
        Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, File.GetUnixFileMode(path));
 
        const string Contents = "#!/bin/sh\nprintf 'ok-\\303\\251 %s' \"$1\"\n";
        await FileHelper.WriteExecutableFileAsync(path, Contents);
 
        Assert.Equal(System.Text.Encoding.UTF8.GetBytes(Contents), await File.ReadAllBytesAsync(path));
        Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute, File.GetUnixFileMode(path));
 
        var startInfo = new ProcessStartInfo(path) { UseShellExecute = false, RedirectStandardOutput = true };
        startInfo.ArgumentList.Add("arg");
        using var process = Process.Start(startInfo)!;
        var output = await process.StandardOutput.ReadToEndAsync();
        await process.WaitForExitAsync();
 
        Assert.Equal(0, process.ExitCode);
        Assert.Equal("ok-é arg", output);
    }
}