File: Assertions\CommandResultAssertions.cs
Web Access
Project: ..\..\..\test\Microsoft.NET.TestFramework\Microsoft.NET.TestFramework.csproj (Microsoft.NET.TestFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.IO.Compression;
using System.Text.RegularExpressions;
using Microsoft.DotNet.Cli.Utils;
 
namespace Microsoft.NET.TestFramework.Assertions
{
    public class CommandResultAssertions
    {
        private CommandResult _commandResult;
 
        public CommandResultAssertions(CommandResult commandResult)
        {
            _commandResult = commandResult;
        }
 
        public AndConstraint<CommandResultAssertions> ExitWith(int expectedExitCode)
        {
            _commandResult.ExitCode.Should().Be(expectedExitCode, AppendDiagnosticsTo($"Expected command to exit with {expectedExitCode} but it did not."));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> Pass()
        {
            _commandResult.ExitCode.Should().Be(0, AppendDiagnosticsTo("Expected command to pass but it did not."));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> Fail()
        {
            _commandResult.ExitCode.Should().NotBe(0, AppendDiagnosticsTo("Expected command to fail but it did not."));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdOut()
        {
            _commandResult.StdOut.Should().NotBeNullOrEmpty(AppendDiagnosticsTo("Command did not output anything to stdout"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdOut(string expectedOutput)
        {
            _commandResult.StdOut.Should().NotBeNull()
                .And.Be(expectedOutput, AppendDiagnosticsTo($"Command did not output with Expected Output. Expected: {expectedOutput}"),StringComparison.Ordinal);
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdOutContaining(string pattern)
        {
            _commandResult.StdOut.Should().NotBeNull()
                .And.Contain(pattern, AppendDiagnosticsTo($"The command output did not contain expected result: {pattern}{Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdOutContaining(Func<string?, bool> predicate, string description = "")
        {
            _commandResult.StdOut.Should().NotBeNull();
            predicate(_commandResult.StdOut).Should().BeTrue(AppendDiagnosticsTo($"The command output did not contain expected result: {description} {Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> NotHaveStdOutContaining(string pattern, string[]? ignoredPatterns = null)
        {
            _commandResult.StdOut.Should().NotBeNull();
 
            string filteredStdOut = _commandResult.StdOut ?? string.Empty;
            if (ignoredPatterns != null && ignoredPatterns.Length > 0)
            {
                foreach (var ignoredPattern in ignoredPatterns)
                {
                    filteredStdOut = string.Join(Environment.NewLine, filteredStdOut
                        .Split(new[] { Environment.NewLine }, StringSplitOptions.None)
                        .Where(line => !line.Contains(ignoredPattern)));
                }
            }
 
            // Perform the assertion on the filtered output
            filteredStdOut.Should().NotContain(pattern, AppendDiagnosticsTo($"The command output contained a result it should not have contained: {pattern}{Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdOutContainingIgnoreSpaces(string pattern)
        {
            _commandResult.StdOut.Should().NotBeNull();
            string commandResultNoSpaces = _commandResult.StdOut.Replace(" ", "");
            commandResultNoSpaces.Should().Contain(pattern, AppendDiagnosticsTo($"The command output did not contain expected result: {pattern}{Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdOutContainingIgnoreCase(string pattern)
        {
            _commandResult.StdOut.Should().NotBeNull()
                .And.ContainEquivalentOf(pattern, AppendDiagnosticsTo($"The command output did not contain expected result (ignoring case): {pattern}{Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdOutMatching(string pattern, RegexOptions options = RegexOptions.None)
        {
            _commandResult.StdOut.Should().NotBeNull();
            Regex.Match(_commandResult.StdOut ?? string.Empty, pattern, options).Success.Should().BeTrue(AppendDiagnosticsTo($"Matching the command output failed. Pattern: {pattern}{Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> NotHaveStdOutMatching(string pattern, RegexOptions options = RegexOptions.None)
        {
            _commandResult.StdOut.Should().NotBeNull();
            Regex.Match(_commandResult.StdOut ?? string.Empty, pattern, options).Success.Should().BeFalse(AppendDiagnosticsTo($"The command output matched a pattern it should not have. Pattern: {pattern}{Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdErr()
        {
            _commandResult.StdErr.Should().NotBeNullOrEmpty(AppendDiagnosticsTo("Command did not output anything to StdErr."));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdErr(string expectedOutput)
        {
            _commandResult.StdErr.Should().NotBeNull()
                .And.Be(expectedOutput, AppendDiagnosticsTo($"Command did not output the expected output to StdErr.{Environment.NewLine}Expected: {expectedOutput}{Environment.NewLine}Actual:   {_commandResult.StdErr}"), StringComparison.Ordinal);
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdErrContaining(string pattern)
        {
            _commandResult.StdErr.Should().NotBeNull()
                .And.Contain(pattern, AppendDiagnosticsTo($"The command error output did not contain expected result: {pattern}{Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdErrContainingOnce(string pattern)
        {
            var lines = _commandResult.StdErr?.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
            var matchingLines = lines?.Where(line => line.Contains(pattern)).Count();
            matchingLines.Should().NotBe(0, AppendDiagnosticsTo($"The command error output did not contain expected result: {pattern}{Environment.NewLine}"))
                .And.Be(1, AppendDiagnosticsTo($"The command error output was expected to contain the pattern '{pattern}' once, but found it {matchingLines} times.{Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
 
        public AndConstraint<CommandResultAssertions> NotHaveStdErrContaining(string pattern)
        {
            _commandResult.StdErr.Should().NotBeNull()
                .And.NotContain(pattern, AppendDiagnosticsTo($"The command error output contained a result it should not have contained: {pattern}{Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveStdErrMatching(string pattern, RegexOptions options = RegexOptions.None)
        {
            _commandResult.StdErr.Should().NotBeNull();
            Regex.Match(_commandResult.StdErr ?? string.Empty, pattern, options).Success.Should().BeTrue(AppendDiagnosticsTo($"Matching the command error output failed. Pattern: {pattern}{Environment.NewLine}"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> NotHaveStdOut()
        {
            _commandResult.StdOut.Should().BeNullOrEmpty(AppendDiagnosticsTo($"Expected command to not output to stdout but it was not:"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> NotHaveStdErr()
        {
            _commandResult.StdErr.Should().BeNullOrEmpty(AppendDiagnosticsTo("Expected command to not output to stderr but it was not:"));
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        private string AppendDiagnosticsTo(string s)
        {
            return s + $"{Environment.NewLine}" +
                       $"Working Directory: {_commandResult.StartInfo?.WorkingDirectory}{Environment.NewLine}" +
                       $"File Name: {_commandResult.StartInfo?.FileName}{Environment.NewLine}" +
                       $"Arguments: {_commandResult.StartInfo?.Arguments}{Environment.NewLine}" +
                       $"Exit Code: {_commandResult.ExitCode}{Environment.NewLine}" +
                       $"StdOut:{Environment.NewLine}{_commandResult.StdOut}{Environment.NewLine}" +
                       $"StdErr:{Environment.NewLine}{_commandResult.StdErr}{Environment.NewLine}"; ;
        }
 
        public AndConstraint<CommandResultAssertions> HaveSkippedProjectCompilation(string skippedProject, string frameworkFullName)
        {
            _commandResult.StdOut.Should().Contain($"Project {skippedProject} ({frameworkFullName}) was previously compiled. Skipping compilation.");
 
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> HaveCompiledProject(string compiledProject, string frameworkFullName)
        {
            _commandResult.StdOut.Should().Contain($"Project {compiledProject} ({frameworkFullName}) will be compiled");
 
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> NuPkgContain(string nupkgPath, params string[] filePaths)
        {
            var unzipped = ReadNuPkg(nupkgPath, filePaths);
 
            foreach (var filePath in filePaths)
            {
                File.Exists(Path.Combine(unzipped, filePath)).Should().BeTrue(AppendDiagnosticsTo($"NuGet Package did not contain file {filePath}."));
            }
 
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> NuPkgContainsPatterns(string nupkgPath, params string[] filePatterns)
        {
            var unzipped = ReadNuPkg(nupkgPath, []);
 
            foreach (var pattern in filePatterns)
            {
                var directory = Path.GetDirectoryName(pattern);
                var path = Path.Combine(unzipped, directory ?? string.Empty);
                var searchPattern = Path.GetFileName(pattern);
 
                var condition = Directory.GetFiles(path, searchPattern).Length < 1;
                condition.Should().BeFalse(AppendDiagnosticsTo($"NuGet Package did not contain file {pattern}."));
            }
 
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> NuPkgDoesNotContainPatterns(string nupkgPath, params string[] filePatterns)
        {
            var unzipped = ReadNuPkg(nupkgPath, []);
 
            foreach (var pattern in filePatterns)
            {
                var directory = Path.GetDirectoryName(pattern);
                var path = Path.Combine(unzipped, directory ?? string.Empty);
                var searchPattern = Path.GetFileName(pattern);
 
                var condition = Directory.Exists(path) && Directory.GetFiles(path, searchPattern).Length > 0;
                condition.Should().BeFalse(AppendDiagnosticsTo($"NuGet Package contains file {pattern}."));
            }
 
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> NuPkgDoesNotContain(string nupkgPath, params string[] filePaths)
        {
            var unzipped = ReadNuPkg(nupkgPath, filePaths);
 
            foreach (var filePath in filePaths)
            {
                File.Exists(Path.Combine(unzipped, filePath)).Should().BeFalse(AppendDiagnosticsTo($"NuGet Package contained file: {filePath}."));
            }
 
            return new AndConstraint<CommandResultAssertions>(this);
 
        }
 
        private string ReadNuPkg(string nupkgPath, params string[] filePaths)
        {
            if (nupkgPath == null)
            {
                throw new ArgumentNullException(nameof(nupkgPath));
            }
 
            if (filePaths == null)
            {
                throw new ArgumentNullException(nameof(filePaths));
            }
 
            new FileInfo(nupkgPath).Should().Exist();
 
            var unzipped = Path.Combine(nupkgPath, "..", Path.GetFileNameWithoutExtension(nupkgPath));
            ZipFile.ExtractToDirectory(nupkgPath, unzipped);
 
            return unzipped;
        }
 
        public AndConstraint<CommandResultAssertions> NuSpecDoesNotContain(string nuspecPath, string expected)
        {
            if (nuspecPath == null)
            {
                throw new ArgumentNullException(nameof(nuspecPath));
            }
 
            if (expected == null)
            {
                throw new ArgumentNullException(nameof(expected));
            }
 
            new FileInfo(nuspecPath).Should().Exist();
            var content = File.ReadAllText(nuspecPath);
 
            content.Should().NotContain(expected, AppendDiagnosticsTo($"NuSpec contains string: {expected}."));
 
            return new AndConstraint<CommandResultAssertions>(this);
        }
 
        public AndConstraint<CommandResultAssertions> NuSpecContain(string nuspecPath, string expected)
        {
            if (nuspecPath == null)
            {
                throw new ArgumentNullException(nameof(nuspecPath));
            }
 
            if (expected == null)
            {
                throw new ArgumentNullException(nameof(expected));
            }
 
            new FileInfo(nuspecPath).Should().Exist();
            var content = File.ReadAllText(nuspecPath);
 
            content.Should().Contain(expected, AppendDiagnosticsTo($"NuSpec does not contain string: {expected}."));
 
            return new AndConstraint<CommandResultAssertions>(this);
        }
    }
}