File: CommandTests\Test\GivenDotnetTestBuildsAndRunsHelp.cs
Web Access
Project: ..\..\..\test\dotnet.Tests\dotnet.Tests.csproj (dotnet.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.DotNet.Cli.Commands.Test;
using Microsoft.DotNet.Cli.Utils;
using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult;
using ExitCodes = Microsoft.NET.TestFramework.ExitCode;
 
namespace Microsoft.DotNet.Cli.Test.Tests
{
    public class GivenDotnetTestBuildsAndRunsHelp : SdkTest
    {
        public GivenDotnetTestBuildsAndRunsHelp(ITestOutputHelper log) : base(log)
        {
        }
 
        [InlineData(TestingConstants.Debug)]
        [InlineData(TestingConstants.Release)]
        [Theory]
        public void RunHelpOnTestProject_ShouldReturnExitCodeSuccess(string configuration)
        {
            TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectSolutionWithTestsAndArtifacts", Guid.NewGuid().ToString()).WithSource();
 
            CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
                                    .WithWorkingDirectory(testInstance.Path)
                                    .Execute(CliConstants.HelpOptionKey, MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration);
 
            if (!TestContext.IsLocalized())
            {
                Assert.Matches(@"Extension Options:\s+--[\s\S]*", result.StdOut);
                Assert.Matches(@"Options:\s+--[\s\S]*", result.StdOut);
            }
 
            result.ExitCode.Should().Be(ExitCodes.Success);
        }
 
        //  https://github.com/dotnet/sdk/issues/49665
        //  Error output: Failed to load /private/tmp/helix/working/A452091E/p/d/shared/Microsoft.NETCore.App/9.0.0/libhostpolicy.dylib, error: dlopen(/private/tmp/helix/working/A452091E/p/d/shared/Microsoft.NETCore.App/9.0.0/libhostpolicy.dylib, 0x0001): tried: '/private/tmp/helix/working/A452091E/p/d/shared/Microsoft.NETCore.App/9.0.0/libhostpolicy.dylib' (mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64')), '/System/Volumes/Preboot/Cryptexes/OS/private/tmp/helix/working/A452091E/p/d/shared/Microsoft.NETCore.App/9.0.0/libhostpolicy.dylib' (no such file), '/private/tmp/helix/working/A452091E/p/d/shared/Microsoft.NETCore.App/9.0.0/libhostpolicy.dylib' (mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64'))
        [InlineData(TestingConstants.Debug)]
        [InlineData(TestingConstants.Release)]
        [PlatformSpecificTheory(TestPlatforms.Any & ~TestPlatforms.OSX)]
        public void RunHelpOnMultipleTestProjects_ShouldReturnExitCodeSuccess(string configuration)
        {
            TestAsset testInstance = _testAssetsManager.CopyTestAsset("ProjectSolutionForMultipleTFMs", Guid.NewGuid().ToString())
                .WithSource();
            testInstance.WithTargetFramework($"{DotnetVersionHelper.GetPreviousDotnetVersion()}", "TestProject");
 
            CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
                                    .WithWorkingDirectory(testInstance.Path)
                                    .Execute(CliConstants.HelpOptionKey, MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration);
 
            if (!TestContext.IsLocalized())
            {
                Assert.Matches(@"Extension Options:\s+--[\s\S]*", result.StdOut);
                Assert.Matches(@"Options:\s+--[\s\S]*", result.StdOut);
 
                string directorySeparator = PathUtility.GetDirectorySeparatorChar();
                string otherTestProjectPattern = @$"Unavailable extension options:\s+.*{directorySeparator}{ToolsetInfo.CurrentTargetFramework}{directorySeparator}OtherTestProject\.dll.*\s+(--report-trx\s+--report-trx-filename|--report-trx-filename\s+--report-trx)";
 
                Assert.Matches(otherTestProjectPattern, result.StdOut);
            }
 
            result.ExitCode.Should().Be(ExitCodes.Success);
        }
 
        [InlineData(TestingConstants.Debug)]
        [InlineData(TestingConstants.Release)]
        [Theory]
        public void RunHelpCommand_ShouldNotShowDuplicateOptions(string configuration)
        {
            TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectSolutionWithTestsAndArtifacts", Guid.NewGuid().ToString()).WithSource();
 
            CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
                                    .WithWorkingDirectory(testInstance.Path)
                                    .Execute(CliConstants.HelpOptionKey, MicrosoftTestingPlatformOptions.ConfigurationOption.Name, configuration);
 
            // Parse the help output to extract option names
            var helpOutput = result.StdOut;
 
            // Check for specific options we care about
            string outputOptionName = MicrosoftTestingPlatformOptions.OutputOption.Name; // --output
            string noAnsiOptionName = MicrosoftTestingPlatformOptions.NoAnsiOption.Name; // --no-ansi
 
            // Count occurrences of each option in the help output
            int outputOptionCount = CountOptionOccurrences(helpOutput!, outputOptionName);
            int noAnsiOptionCount = CountOptionOccurrences(helpOutput!, noAnsiOptionName);
 
            // Assert that each option appears only once
            outputOptionCount.Should().Be(1, $"Option '{outputOptionName}' should not appear more than once in help output");
            noAnsiOptionCount.Should().Be(1, $"Option '{noAnsiOptionName}' should not appear more than once in help output");
 
            result.ExitCode.Should().Be(ExitCodes.Success);
        }
 
        private static int CountOptionOccurrences(string helpOutput, string optionName)
        {
            // Split by lines and look for lines that start with the option (accounting for indentation)
            var lines = helpOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries);
            int count = 0;
 
            foreach (var line in lines)
            {
                var trimmedLine = line.Trim();
                // Look for lines that start with the option name (e.g., "--output" or "--no-ansi")
                if (trimmedLine.StartsWith(optionName, StringComparison.OrdinalIgnoreCase))
                {
                    count++;
                }
            }
 
            return count;
        }
 
 
    }
}