Skip to content

Enhance ConsoleAssert.AssertExpectation with detailed wildcard diff output #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions IntelliTect.TestTools.Console.Tests/EnhancedWildcardDiffTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Runtime.InteropServices;

namespace IntelliTect.TestTools.Console.Tests;

[TestClass]
public class EnhancedWildcardDiffTests
{
[TestMethod]
public void ExpectLike_WithEnhancedDiff_ShowsDetailedOutput()
{
// Arrange: Create a test that will fail to see the enhanced output
string expected = @"PING *(* (::1)) 56 data bytes
64 bytes from * (::1): icmp_seq=1 ttl=64 time=* ms
Response time was * ms";

string actualOutput = @"PING localhost (::1) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.018 ms
Extra line that shouldn't be here
Response time was 0.025 ms";

// Act & Assert
Exception exception = Assert.ThrowsException<Exception>(() =>
{
ConsoleAssert.ExpectLike(expected, () =>
{
System.Console.Write(actualOutput);
}, DiffOptions.EnhancedWildcardDiff);
});

// Verify the enhanced output contains line-by-line information
string message = exception.Message;
StringAssert.Contains(message, "Line 1:");
StringAssert.Contains(message, "Line 2:");
StringAssert.Contains(message, "Line 3:");
StringAssert.Contains(message, "✅"); // Should contain success markers
StringAssert.Contains(message, "❌"); // Should contain failure markers
StringAssert.Contains(message, "unexpected extra line"); // Should identify extra lines
StringAssert.Contains(message, "Summary:");
}

[TestMethod]
public void ExpectLike_WithoutEnhancedDiff_ShowsOriginalOutput()
{
// Arrange: Create a test that will fail to see the original output
string expected = @"PING *(* (::1)) 56 data bytes
64 bytes from * (::1): icmp_seq=1 ttl=64 time=* ms";

string actualOutput = @"PING localhost (::1) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.018 ms
Extra line";

// Act & Assert
Exception exception = Assert.ThrowsException<Exception>(() =>
{
ConsoleAssert.ExpectLike(expected, () =>
{
System.Console.Write(actualOutput);
}); // Default behavior, no enhanced diff
});

// Verify the original output format
string message = exception.Message;
StringAssert.Contains(message, "Expected:");
StringAssert.Contains(message, "Actual :");
StringAssert.Contains(message, "-----------------------------------");

// Should NOT contain enhanced diff elements
Assert.IsFalse(message.Contains("Line 1:"));
Assert.IsFalse(message.Contains("✅"));
Assert.IsFalse(message.Contains("❌"));
}

[TestMethod]
public void WildcardDiffAnalyzer_AnalyzeDiff_SimpleMatch()
{
// Arrange
string pattern = "Hello * world";
string actual = "Hello beautiful world";

// Act
var result = WildcardDiffAnalyzer.AnalyzeDiff(pattern, actual);

// Assert
Assert.IsTrue(result.OverallMatch);
Assert.AreEqual(1, result.LineResults.Count);
Assert.IsTrue(result.LineResults[0].IsMatch);
Assert.AreEqual(1, result.LineResults[0].WildcardMatches.Count);
Assert.AreEqual("beautiful", result.LineResults[0].WildcardMatches[0]);
}

[TestMethod]
public void WildcardDiffAnalyzer_AnalyzeDiff_ExtraLines()
{
// Arrange
string pattern = "Line 1\nLine 2";
string actual = "Line 1\nLine 2\nExtra Line 3";

// Act
var result = WildcardDiffAnalyzer.AnalyzeDiff(pattern, actual);

// Assert
Assert.IsFalse(result.OverallMatch);
Assert.AreEqual(3, result.LineResults.Count);
Assert.IsTrue(result.LineResults[0].IsMatch);
Assert.IsTrue(result.LineResults[1].IsMatch);
Assert.IsFalse(result.LineResults[2].IsMatch);
Assert.AreEqual("unexpected extra line", result.LineResults[2].MismatchReason);
}

[TestMethod]
public void WildcardDiffAnalyzer_AnalyzeDiff_MissingLines()
{
// Arrange
string pattern = "Line 1\nLine 2\nLine 3";
string actual = "Line 1\nLine 2";

// Act
var result = WildcardDiffAnalyzer.AnalyzeDiff(pattern, actual);

// Assert
Assert.IsFalse(result.OverallMatch);
Assert.AreEqual(3, result.LineResults.Count);
Assert.IsTrue(result.LineResults[0].IsMatch);
Assert.IsTrue(result.LineResults[1].IsMatch);
Assert.IsFalse(result.LineResults[2].IsMatch);
Assert.AreEqual("missing line", result.LineResults[2].MismatchReason);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ProductName>IntelliTect.TestTools.Console.Tests</ProductName>
</PropertyGroup>

Expand Down
144 changes: 141 additions & 3 deletions IntelliTect.TestTools.Console/ConsoleAssert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,32 @@ private static string Expect(
normalizeOptions, equivalentOperatorErrorMessage);
}

/// <summary>
/// Performs a unit test on a console-based method. A "view" of
/// what a user would see in their console is provided as a string,
/// where their input (including line-breaks) is surrounded by double
/// less-than/greater-than signs, like so: "Input please: &lt;&lt;Input&gt;&gt;"
/// </summary>
/// <param name="expected">Expected "view" to be seen on the console,
/// including both input and output</param>
/// <param name="action">Method to be run</param>
/// <param name="comparisonOperator"></param>
/// <param name="normalizeOptions">Options to normalize input and expected output</param>
/// <param name="equivalentOperatorErrorMessage">A textual description of the message if the result of <paramref name="action"/> does not match the <paramref name="expected"/> value</param>
/// <param name="diffOptions">Options for controlling diff output behavior</param>
private static string Expect(
string expected, Action action, Func<string, string, bool> comparisonOperator,
NormalizeOptions normalizeOptions,
string equivalentOperatorErrorMessage,
DiffOptions diffOptions)
{
(string input, string output) = Parse(expected);

return Execute(input, output, action,
(left, right) => comparisonOperator(left, right),
normalizeOptions, equivalentOperatorErrorMessage, diffOptions);
}

/// <summary>
/// Performs a unit test on a console-based method. A "view" of
/// what a user would see in their console is provided as a string,
Expand Down Expand Up @@ -336,6 +362,32 @@ public static string ExpectLike(string expected,
"The values are not like (using wildcards) each other");
}

/// <summary>
/// Performs a unit test on a console-based method with enhanced wildcard diff output. A "view" of
/// what a user would see in their console is provided as a string,
/// where their input (including line-breaks) is surrounded by double
/// less-than/greater-than signs, like so: "Input please: &lt;&lt;Input&gt;&gt;"
/// </summary>
/// <param name="expected">Expected "view" to be seen on the console,
/// including both input and output</param>
/// <param name="action">Method to be run</param>
/// <param name="diffOptions">Options for controlling diff output behavior</param>
/// <param name="normalizeLineEndings">Options to normalize input and expected output</param>
/// <param name="escapeCharacter">The escape character for the wildcard caracters. Default is '\'.</param>
public static string ExpectLike(string expected,
Action action,
DiffOptions diffOptions,
NormalizeOptions normalizeLineEndings = NormalizeOptions.Default,
char escapeCharacter = '\\')
{
return Expect(expected,
action,
(pattern, output) => output.IsLike(pattern, escapeCharacter),
normalizeLineEndings,
"The values are not like (using wildcards) each other",
diffOptions);
}

/// <summary>
/// Performs a unit test on a console-based method. A "view" of
/// what a user would see in their console is provided as a string,
Expand Down Expand Up @@ -410,6 +462,29 @@ private static string Execute(string givenInput,
return CompareOutput(output, expectedOutput, normalizeOptions, areEquivalentOperator, equivalentOperatorErrorMessage);
}

/// <summary>
/// Executes the unit test while providing console input.
/// </summary>
/// <param name="givenInput">Input which will be given</param>
/// <param name="expectedOutput">The expected output</param>
/// <param name="action">Action to be tested</param>
/// <param name="areEquivalentOperator">delegate for comparing the expected from actual output.</param>
/// <param name="normalizeOptions">Options to normalize input and expected output</param>
/// <param name="equivalentOperatorErrorMessage">A textual description of the message if the <paramref name="areEquivalentOperator"/> returns false</param>
/// <param name="diffOptions">Options for controlling diff output behavior</param>
private static string Execute(string givenInput,
string expectedOutput,
Action action,
Func<string, string, bool> areEquivalentOperator,
NormalizeOptions normalizeOptions,
string equivalentOperatorErrorMessage,
DiffOptions diffOptions)
{
string output = Execute(givenInput, action);

return CompareOutput(output, expectedOutput, normalizeOptions, areEquivalentOperator, equivalentOperatorErrorMessage, diffOptions);
}

/// <summary>
/// Executes the unit test while providing console input.
/// </summary>
Expand Down Expand Up @@ -438,6 +513,17 @@ private static string CompareOutput(
NormalizeOptions normalizeOptions,
Func<string, string, bool> areEquivalentOperator,
string equivalentOperatorErrorMessage)
{
return CompareOutput(output, expectedOutput, normalizeOptions, areEquivalentOperator, equivalentOperatorErrorMessage, DiffOptions.Default);
}

private static string CompareOutput(
string output,
string expectedOutput,
NormalizeOptions normalizeOptions,
Func<string, string, bool> areEquivalentOperator,
string equivalentOperatorErrorMessage,
DiffOptions diffOptions)
{
if ((normalizeOptions & NormalizeOptions.NormalizeLineEndings) != 0)
{
Expand All @@ -451,7 +537,7 @@ private static string CompareOutput(
expectedOutput = StripAnsiEscapeCodes(expectedOutput);
}

AssertExpectation(expectedOutput, output, areEquivalentOperator, equivalentOperatorErrorMessage);
AssertExpectation(expectedOutput, output, areEquivalentOperator, equivalentOperatorErrorMessage, diffOptions);
return output;
}

Expand All @@ -464,11 +550,25 @@ private static string CompareOutput(
/// <param name="equivalentOperatorErrorMessage">A textual description of the message if the <paramref name="areEquivalentOperator"/> returns false</param>
private static void AssertExpectation(string expectedOutput, string output, Func<string, string, bool> areEquivalentOperator,
string equivalentOperatorErrorMessage = null)
{
AssertExpectation(expectedOutput, output, areEquivalentOperator, equivalentOperatorErrorMessage, DiffOptions.Default);
}

/// <summary>
/// Asserts whether the values are equivalent according to the <paramref name="areEquivalentOperator"/>"/>
/// </summary>
/// <param name="expectedOutput">The expected value of the output.</param>
/// <param name="output">The actual value output.</param>
/// <param name="areEquivalentOperator">The operator used to compare equivalency.</param>
/// <param name="equivalentOperatorErrorMessage">A textual description of the message if the <paramref name="areEquivalentOperator"/> returns false</param>
/// <param name="diffOptions">Options for controlling diff output behavior</param>
private static void AssertExpectation(string expectedOutput, string output, Func<string, string, bool> areEquivalentOperator,
string equivalentOperatorErrorMessage, DiffOptions diffOptions)
{
bool failTest = !areEquivalentOperator(expectedOutput, output);
if (failTest)
{
throw new Exception(GetMessageText(expectedOutput, output, equivalentOperatorErrorMessage));
throw new Exception(GetMessageText(expectedOutput, output, equivalentOperatorErrorMessage, diffOptions));
}
}

Expand Down Expand Up @@ -557,6 +657,23 @@ public static async Task<string> ExecuteAsync(string givenInput, Func<Task> acti

private static string GetMessageText(string expectedOutput, string output, string equivalentOperatorErrorMessage = null)
{
return GetMessageText(expectedOutput, output, equivalentOperatorErrorMessage, DiffOptions.Default);
}

private static string GetMessageText(string expectedOutput, string output, string equivalentOperatorErrorMessage, DiffOptions diffOptions)
{
// Check if enhanced wildcard diff is enabled and the error message suggests wildcard usage
bool isWildcardError = equivalentOperatorErrorMessage != null &&
equivalentOperatorErrorMessage.ToLowerInvariant().Contains("wildcard");

if ((diffOptions & DiffOptions.EnhancedWildcardDiff) != 0 && isWildcardError)
{
// Use enhanced wildcard diff output
var diffResult = WildcardDiffAnalyzer.AnalyzeDiff(expectedOutput, output);
return WildcardDiffFormatter.FormatEnhancedDiff(diffResult, equivalentOperatorErrorMessage);
}

// Fall back to original implementation
string result = "";

result += string.Join(Environment.NewLine, $"{equivalentOperatorErrorMessage}:- ",
Expand Down Expand Up @@ -704,6 +821,27 @@ public static Process ExecuteProcess(string expected, string fileName, string ar
/// <param name="workingDirectory">Working directory to start the process in.</param>
public static Process ExecuteProcess(string expected, string fileName, string args,
out string standardOutput, out string standardError, string workingDirectory = null)
{
return ExecuteProcess(expected, fileName, args, out standardOutput, out standardError, workingDirectory, DiffOptions.Default);
}

/// <summary>
/// Performs a unit test on a console-based executable with enhanced diff options. A "view" of
/// what a user would see in their console is provided as a string,
/// where their input (including line-breaks) is surrounded by double
/// less-than/greater-than signs, like so: "Input please: &lt;&lt;Input&gt;&gt;"
/// </summary>
/// <param name="expected">Expected "view" to be seen on the console,
/// including both input and output</param>
/// <param name="fileName">The filename or executable to call to generate output</param>
/// <param name="args">command line arguments</param>
/// <param name="standardOutput">The output from the process to the standard output stream</param>
/// <param name="standardError">The output from the process to the standard error stream</param>
/// <param name="workingDirectory">The working directory to start the process in</param>
/// <param name="diffOptions">Options for controlling diff output behavior</param>
/// <returns>The process that was executed</returns>
public static Process ExecuteProcess(string expected, string fileName, string args,
out string standardOutput, out string standardError, string workingDirectory, DiffOptions diffOptions)
{
ProcessStartInfo processStartInfo = new ProcessStartInfo(fileName, args)
{
Expand All @@ -718,7 +856,7 @@ public static Process ExecuteProcess(string expected, string fileName, string ar
process.WaitForExit();
standardOutput = process.StandardOutput.ReadToEnd();
standardError = process.StandardError.ReadToEnd();
AssertExpectation(expected, standardOutput, (left, right) => LikeOperator(left, right), "The values are not like (using wildcards) each other");
AssertExpectation(expected, standardOutput, (left, right) => LikeOperator(left, right), "The values are not like (using wildcards) each other", diffOptions);
return process;
}
}
19 changes: 19 additions & 0 deletions IntelliTect.TestTools.Console/DiffOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace IntelliTect.TestTools.Console;

/// <summary>
/// Provides options for controlling diff output behavior when wildcard matching fails.
/// </summary>
[Flags]
public enum DiffOptions
{
/// <summary>
/// Use the default diff output format.
/// </summary>
Default = 0,

/// <summary>
/// Enable enhanced line-by-line diff output with wildcard match tracking for wildcard patterns.
/// This provides detailed information about what each wildcard matched and where mismatches occurred.
/// </summary>
EnhancedWildcardDiff = 1,
}
Loading