File: Commands\Test\MTP\ValidationUtility.cs
Web Access
Project: ..\..\..\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.CommandLine;
using Microsoft.DotNet.Cli.Extensions;
using Microsoft.DotNet.Cli.Utils;
 
namespace Microsoft.DotNet.Cli.Commands.Test;
 
internal static class ValidationUtility
{
    public static void ValidateMutuallyExclusiveOptions(ParseResult parseResult)
    {
        ValidatePathOptions(parseResult);
        ValidateOptionsIrrelevantToModulesFilter(parseResult);
 
        static void ValidatePathOptions(ParseResult parseResult)
        {
            var count = 0;
            if (parseResult.HasOption(MicrosoftTestingPlatformOptions.TestModulesFilterOption))
                count++;
 
            if (parseResult.HasOption(MicrosoftTestingPlatformOptions.SolutionOption))
                count++;
 
            if (parseResult.HasOption(MicrosoftTestingPlatformOptions.ProjectOption))
                count++;
 
            if (count > 1)
                throw new GracefulException(CliCommandStrings.CmdMultipleBuildPathOptionsErrorDescription);
        }
 
        static void ValidateOptionsIrrelevantToModulesFilter(ParseResult parseResult)
        {
            if (!parseResult.HasOption(MicrosoftTestingPlatformOptions.TestModulesFilterOption))
            {
                return;
            }
 
            if (parseResult.HasOption(CommonOptions.ArchitectureOption) ||
                parseResult.HasOption(MicrosoftTestingPlatformOptions.ConfigurationOption) ||
                parseResult.HasOption(MicrosoftTestingPlatformOptions.FrameworkOption) ||
                parseResult.HasOption(CommonOptions.OperatingSystemOption) ||
                parseResult.HasOption(CommonOptions.RuntimeOptionName))
            {
                throw new GracefulException(CliCommandStrings.CmdOptionCannotBeUsedWithTestModulesDescription);
            }
        }
    }
 
    public static bool ValidateBuildPathOptions(BuildOptions buildPathOptions)
    {
        PathOptions pathOptions = buildPathOptions.PathOptions;
 
        if (!string.IsNullOrEmpty(pathOptions.ProjectPath))
        {
            return ValidateProjectPath(pathOptions.ProjectPath);
        }
 
        if (!string.IsNullOrEmpty(pathOptions.SolutionPath))
        {
            return ValidateSolutionPath(pathOptions.SolutionPath);
        }
 
        return true;
    }
 
    /// <summary>
    /// Validates that arguments requiring specific command-line switches are used correctly for Microsoft.Testing.Platform.
    /// Provides helpful error messages when users provide file/directory arguments without proper switches.
    /// </summary>
    public static void ValidateSolutionOrProjectOrDirectoryOrModulesArePassedCorrectly(ParseResult parseResult)
    {
        if (Environment.GetEnvironmentVariable("DOTNET_TEST_DISABLE_SWITCH_VALIDATION") is "true" or "1")
        {
            // In case there is a valid case, users can opt-out.
            // Note that the validation here is added to have a "better" error message for scenarios that will already fail.
            // So, disabling validation is okay if the user scenario is valid.
            return;
        }
 
        foreach (string token in parseResult.UnmatchedTokens)
        {
            // Check for .sln files
            if ((token.EndsWith(".sln", StringComparison.OrdinalIgnoreCase) ||
                token.EndsWith(".slnx", StringComparison.OrdinalIgnoreCase)) && File.Exists(token))
            {
                throw new GracefulException(CliCommandStrings.TestCommandUseSolution);
            }
            else if ((token.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) ||
                     token.EndsWith(".vbproj", StringComparison.OrdinalIgnoreCase) ||
                     token.EndsWith(".fsproj", StringComparison.OrdinalIgnoreCase)) && File.Exists(token))
            {
                throw new GracefulException(CliCommandStrings.TestCommandUseProject);
            }
            else if ((token.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) ||
                      token.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) &&
                     File.Exists(token))
            {
                throw new GracefulException(CliCommandStrings.TestCommandUseTestModules);
            }
            else if (Directory.Exists(token))
            {
                throw new GracefulException(CliCommandStrings.TestCommandUseDirectoryWithSwitch);
            }
        }
    }
 
    private static bool ValidateSolutionPath(string path)
    {
        // If it's a directory, just check if it exists
        if (Directory.Exists(path))
        {
            return true;
        }
 
        // If it's not a directory, validate as a file path
        if (!CliConstants.SolutionExtensions.Contains(Path.GetExtension(path)))
        {
            Reporter.Error.WriteLine(string.Format(CliCommandStrings.CmdInvalidSolutionFileExtensionErrorDescription, path));
            return false;
        }
 
        return ValidateFilePathExists(path);
    }
 
    private static bool ValidateProjectPath(string path)
    {
        // If it's a directory, just check if it exists
        if (Directory.Exists(path))
        {
            return true;
        }
 
        // If it's not a directory, validate as a file path
        if (!Path.GetExtension(path).EndsWith("proj", StringComparison.OrdinalIgnoreCase))
        {
            Reporter.Error.WriteLine(string.Format(CliCommandStrings.CmdInvalidProjectFileExtensionErrorDescription, path));
            return false;
        }
 
        return ValidateFilePathExists(path);
    }
 
    private static bool ValidateFilePathExists(string filePath)
    {
        if (!File.Exists(filePath))
        {
            Reporter.Error.WriteLine(string.Format(CliCommandStrings.CmdNonExistentFileErrorDescription, Path.GetFullPath(filePath)));
            return false;
        }
 
        return true;
    }
}