File: DotNet\ProcessInvocationOptionsTests.cs
Web Access
Project: src\tests\Aspire.Cli.Tests\Aspire.Cli.Tests.csproj (Aspire.Cli.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.Reflection;
using Aspire.Cli.DotNet;
using Aspire.Cli.Tests.TestServices;
 
namespace Aspire.Cli.Tests.DotNet;
 
public sealed class ProcessInvocationOptionsTests
{
    [Fact]
    public void Clone_CopiesEveryProperty()
    {
        // Reflection-driven on purpose: adding a property to ProcessInvocationOptions without also
        // forwarding it in Clone() must fail this test rather than silently drop the value on the copy —
        // the exact "silently break" hazard Clone() exists to guard against.
        var original = new ProcessInvocationOptions
        {
            StandardOutputCallback = _ => { },
            StandardErrorCallback = _ => { },
            NoLaunchProfile = true,
            LaunchProfile = "E2E",
            StartDebugSession = true,
            Debug = true,
            SuppressLogging = true,
            // Non-default: this property initializes to true, so false is the "changed" value.
            KillEntireProcessTreeOnCancel = false,
            IsolateConsole = true,
            KillOnParentExit = true,
            Detached = true,
            // Internal properties are included below, so they need non-default values too.
            DetachedUnixLauncherPathOverride = "detached-launcher-override",
            AppHostArgumentStartIndex = 3,
            EnvironmentVariableFilter = _ => false,
            GracefulShutdownSignaler = new RecordingGracefulSignaler(),
            ShutdownService = new TestGracefulShutdownWindow(),
        };
 
        // Compare against a pristine instance so we assert every property was given a value that
        // actually differs from its default; otherwise the copy check below could pass vacuously.
        var defaults = new ProcessInvocationOptions();
        foreach (var property in GetSettableProperties())
        {
            Assert.False(
                Equals(property.GetValue(original), property.GetValue(defaults)),
                $"Property '{property.Name}' was not set to a non-default value in this test; add one so Clone() coverage stays meaningful.");
        }
 
        var clone = original.Clone();
 
        Assert.NotSame(original, clone);
        foreach (var property in GetSettableProperties())
        {
            Assert.Equal(property.GetValue(original), property.GetValue(clone));
        }
    }
 
    private static IEnumerable<PropertyInfo> GetSettableProperties() =>
        typeof(ProcessInvocationOptions)
            // NonPublic is included so internal options — such as the redaction boundary carried for
            // direct AppHost launches — are held to the same Clone() coverage as public ones.
            .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
            .Where(p => p is { CanRead: true, CanWrite: true });
}