Skip to content

[EXPERIMENTAL] Autogenerated unittests - evaluating usability #863

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 5 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
4 changes: 3 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
<PackageVersion Include="Microsoft.Build.Tasks.Core" Version="$(MSBuildPackageVersion)" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="$(MSBuildPackageVersion)" />
<PackageVersion Include="Microsoft.IO.Redist" Version="6.1.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<!-- never update Cecil as this is the version required by Squirrel -->
<PackageVersion Include="Mono.Cecil" Version="0.9.6.1" />
<PackageVersion Include="Moq" Version="4.18.1" />
<PackageVersion Include="MSBuild.StructuredLogger" Version="2.2.386" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.141" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
Expand All @@ -48,4 +50,4 @@
<PackageVersion Include="xunit" Version="2.9.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
</Project>
</Project>
6 changes: 6 additions & 0 deletions MSBuildStructuredLog.sln
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredLogger.Utils", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredLogViewer", "src\StructuredLogViewer\StructuredLogViewer.csproj", "{C2E67DD4-F4F7-4CDE-A51A-6D46049A0CCA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StructuredLogger.Utils.UnitTests", "src\StructuredLogger.Utils.UnitTests\StructuredLogger.Utils.UnitTests.csproj", "{73CAE4BE-967F-1C5C-A8A3-756108565B53}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -74,6 +76,10 @@ Global
{C2E67DD4-F4F7-4CDE-A51A-6D46049A0CCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2E67DD4-F4F7-4CDE-A51A-6D46049A0CCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2E67DD4-F4F7-4CDE-A51A-6D46049A0CCA}.Release|Any CPU.Build.0 = Release|Any CPU
{73CAE4BE-967F-1C5C-A8A3-756108565B53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{73CAE4BE-967F-1C5C-A8A3-756108565B53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{73CAE4BE-967F-1C5C-A8A3-756108565B53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{73CAE4BE-967F-1C5C-A8A3-756108565B53}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
131 changes: 131 additions & 0 deletions src/StructuredLogger.Tests/Analyzers/ImportTreeAnalyzerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// using System;
// using Moq;
// using Xunit;
// using Microsoft.Build.Logging.StructuredLogger;
// using Microsoft.Build.Framework;
//
// namespace Microsoft.Build.Logging.StructuredLogger.UnitTests
// {
// /// <summary>
// /// A fake implementation of StringCache for testing purposes.
// /// </summary>
// internal class FakeStringCache : StringCache
// {
// /// <summary>
// /// Returns the provided string unchanged.
// /// </summary>
// /// <param name="text">The text to intern.</param>
// /// <returns>The same text.</returns>
// // public override string SoftIntern(string text) [Error] (19-32)CS0506 'FakeStringCache.SoftIntern(string)': cannot override inherited member 'StringCache.SoftIntern(string)' because it is not marked virtual, abstract, or override
// // {
// // return text;
// // }
//
// /// <summary>
// /// A no-op implementation for interning.
// /// </summary>
// /// <param name="text">The text to intern.</param>
// // public override void Intern(string text) [Error] (28-30)CS0506 'FakeStringCache.Intern(string)': cannot override inherited member 'StringCache.Intern(string)' because it is not marked virtual, abstract, or override
// // {
// // // no-op for testing
// // }
// }
//
// /// <summary>
// /// A fake implementation of ProjectImportedEventArgs for testing the fallback branch.
// /// </summary>
// internal class FakeProjectImportedEventArgs : ProjectImportedEventArgs
// {
// // public override string Message { get; } [Error] (39-32)CS8080 Auto-implemented properties must override all accessors of the overridden property.
// // public override string ProjectFile { get; } [Error] (40-32)CS0506 'FakeProjectImportedEventArgs.ProjectFile': cannot override inherited member 'BuildMessageEventArgs.ProjectFile' because it is not marked virtual, abstract, or override
// // public override string ImportedProjectFile { get; } [Error] (41-32)CS0506 'FakeProjectImportedEventArgs.ImportedProjectFile': cannot override inherited member 'ProjectImportedEventArgs.ImportedProjectFile' because it is not marked virtual, abstract, or override
// // public override int LineNumber { get; } [Error] (42-29)CS0506 'FakeProjectImportedEventArgs.LineNumber': cannot override inherited member 'BuildMessageEventArgs.LineNumber' because it is not marked virtual, abstract, or override
// // public override int ColumnNumber { get; } [Error] (43-29)CS0506 'FakeProjectImportedEventArgs.ColumnNumber': cannot override inherited member 'BuildMessageEventArgs.ColumnNumber' because it is not marked virtual, abstract, or override
//
// /// <summary>
// /// Initializes a new instance of the <see cref="FakeProjectImportedEventArgs"/> class.
// /// </summary>
// /// <param name="message">The event message.</param>
// /// <param name="projectFile">The project file path.</param>
// /// <param name="importedProjectFile">The imported project file path.</param>
// /// <param name="lineNumber">The line number.</param>
// /// <param name="columnNumber">The column number.</param>
// public FakeProjectImportedEventArgs(string message, string projectFile, string importedProjectFile, int lineNumber, int columnNumber)
// {
// Message = message;
// ProjectFile = projectFile;
// ImportedProjectFile = importedProjectFile;
// LineNumber = lineNumber;
// ColumnNumber = columnNumber;
// }
// }
//
// /// <summary>
// /// Unit tests for the <see cref="ImportTreeAnalyzer"/> class.
// /// </summary>
// public class ImportTreeAnalyzerTests
// {
// private readonly FakeStringCache _stringCache;
// private readonly ImportTreeAnalyzer _analyzer;
//
// /// <summary>
// /// Initializes a new instance of the <see cref="ImportTreeAnalyzerTests"/> class.
// /// </summary>
// public ImportTreeAnalyzerTests()
// {
// _stringCache = new FakeStringCache();
// _analyzer = new ImportTreeAnalyzer(_stringCache);
// }
//
// /// <summary>
// /// Tests that the constructor of ImportTreeAnalyzer creates a valid instance.
// /// </summary>
// [Fact]
// public void Constructor_CreatesInstance_ReturnsNotNull()
// {
// // Act and Assert are performed in the test constructor initialization.
// Assert.NotNull(_analyzer);
// }
//
// /// <summary>
// /// Tests the static TryGetImportOrNoImport method with a non-matching text input.
// /// Expecting a null result when the input does not match expected patterns.
// /// </summary>
// [Fact]
// public void TryGetImportOrNoImport_Static_NonMatchingText_ReturnsNull()
// {
// // Arrange
// string nonMatchingText = "This is an unrelated message.";
//
// // Act
// var result = ImportTreeAnalyzer.TryGetImportOrNoImport(nonMatchingText, _stringCache);
//
// // Assert
// Assert.Null(result);
// }
//
// /// <summary>
// /// Tests the instance TryGetImportOrNoImport method in its fallback scenario.
// /// Given a ProjectImportedEventArgs with a non-matching Message and assuming the static
// /// Reflector methods yield no arguments, the fallback branch should call the static overload
// /// and return null.
// /// </summary>
// [Fact]
// public void TryGetImportOrNoImport_Instance_FallbackWithNonMatchingMessage_ReturnsNull()
// {
// // Arrange
// // Create a fake ProjectImportedEventArgs with non-matching message.
// var fakeArgs = new FakeProjectImportedEventArgs("This is an unrelated message.",
// "ContainingProject.proj",
// "ImportedProject.proj",
// 0,
// 0);
//
// // Act
// var result = _analyzer.TryGetImportOrNoImport(fakeArgs);
//
// // Assert
// Assert.Null(result);
// }
// }
// }
195 changes: 195 additions & 0 deletions src/StructuredLogger.Tests/Analyzers/MessageTaskAnalyzerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Logging.StructuredLogger;
using Xunit;

namespace Microsoft.Build.Logging.StructuredLogger.UnitTests
{
/// <summary>
/// Minimal stub implementation of Task for unit testing.
/// </summary>
public class Task
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is useless, as it doesn't extend/inherit tested code. It actually yields the tests incompilable

{
/// <summary>
/// Gets or sets the collection of child nodes.
/// </summary>
public List<object> Children { get; set; } = new List<object>();

/// <summary>
/// Gets or sets the name of the task.
/// </summary>
public string Name { get; set; }
}

/// <summary>
/// Minimal stub implementation of Message for unit testing.
/// </summary>
public class Message
{
/// <summary>
/// Gets or sets the shortened text of the message.
/// </summary>
public string ShortenedText { get; set; }
}

/// <summary>
/// Unit tests for the <see cref="MessageTaskAnalyzer"/> class.
/// </summary>
public class MessageTaskAnalyzerTests
{
private readonly string _expectedPrefix = "Message: ";

/// <summary>
/// Tests that Analyze throws a NullReferenceException when the task parameter is null.
/// </summary>
// [Fact] [Error] (53-85)CS1503 Argument 1: cannot convert from 'Microsoft.Build.Logging.StructuredLogger.UnitTests.Task' to 'Microsoft.Build.Logging.StructuredLogger.Task'
// public void Analyze_NullTask_ThrowsNullReferenceException()
// {
// // Arrange
// Task task = null;
//
// // Act & Assert
// Assert.Throws<NullReferenceException>(() => MessageTaskAnalyzer.Analyze(task));
// }

/// <summary>
/// Tests that Analyze does not modify the task name when there are no Message children.
/// </summary>
// [Fact] [Error] (64-24)CS0144 Cannot create an instance of the abstract type or interface 'Task' [Error] (66-17)CS0229 Ambiguity between 'Task.Name' and 'Task.Name' [Error] (75-41)CS1503 Argument 1: cannot convert from 'Microsoft.Build.Logging.StructuredLogger.UnitTests.Task' to 'Microsoft.Build.Logging.StructuredLogger.Task' [Error] (78-45)CS0229 Ambiguity between 'Task.Name' and 'Task.Name'
// public void Analyze_TaskWithNoMessageChild_TaskNameRemainsUnchanged()
// {
// // Arrange
// var originalName = "OriginalTaskName";
// var task = new Task
// {
// Name = originalName,
// Children = new List<object>
// {
// // Add an object that is NOT of type Message.
// new object()
// }
// };
//
// // Act
// MessageTaskAnalyzer.Analyze(task);
//
// // Assert
// Assert.Equal(originalName, task.Name);
// }

/// <summary>
/// Tests that Analyze sets the task name correctly when a Message child with non-null ShortenedText is present.
/// </summary>
// [Fact] [Error] (90-24)CS0144 Cannot create an instance of the abstract type or interface 'Task' [Error] (92-17)CS0229 Ambiguity between 'Task.Name' and 'Task.Name' [Error] (97-41)CS1503 Argument 1: cannot convert from 'Microsoft.Build.Logging.StructuredLogger.UnitTests.Task' to 'Microsoft.Build.Logging.StructuredLogger.Task' [Error] (100-64)CS0229 Ambiguity between 'Task.Name' and 'Task.Name'
// public void Analyze_TaskWithValidMessageChild_SetsTaskName()
// {
// // Arrange
// var shortenedText = "TestMessage";
// var message = new Message { ShortenedText = shortenedText };
// var task = new Task
// {
// Name = "InitialName",
// Children = new List<object> { message }
// };
//
// // Act
// MessageTaskAnalyzer.Analyze(task);
//
// // Assert
// Assert.Equal(_expectedPrefix + shortenedText, task.Name);
// }

/// <summary>
/// Tests that Analyze does not change the task name when the Message child's ShortenedText is null.
/// </summary>
// [Fact] [Error] (112-24)CS0144 Cannot create an instance of the abstract type or interface 'Task' [Error] (114-17)CS0229 Ambiguity between 'Task.Name' and 'Task.Name' [Error] (119-41)CS1503 Argument 1: cannot convert from 'Microsoft.Build.Logging.StructuredLogger.UnitTests.Task' to 'Microsoft.Build.Logging.StructuredLogger.Task' [Error] (122-45)CS0229 Ambiguity between 'Task.Name' and 'Task.Name'
// public void Analyze_MessageChildWithNullShortenedText_TaskNameRemainsUnchanged()
// {
// // Arrange
// var originalName = "InitialName";
// var message = new Message { ShortenedText = null };
// var task = new Task
// {
// Name = originalName,
// Children = new List<object> { message }
// };
//
// // Act
// MessageTaskAnalyzer.Analyze(task);
//
// // Assert
// Assert.Equal(originalName, task.Name);
// }

/// <summary>
/// Tests that Analyze uses the first Message child with non-null ShortenedText when multiple Message children are present.
/// </summary>
// [Fact] [Error] (135-24)CS0144 Cannot create an instance of the abstract type or interface 'Task' [Error] (137-17)CS0229 Ambiguity between 'Task.Name' and 'Task.Name' [Error] (142-41)CS1503 Argument 1: cannot convert from 'Microsoft.Build.Logging.StructuredLogger.UnitTests.Task' to 'Microsoft.Build.Logging.StructuredLogger.Task' [Error] (145-77)CS0229 Ambiguity between 'Task.Name' and 'Task.Name'
// public void Analyze_TaskWithMultipleMessageChildren_UsesFirstValidMessage()
// {
// // Arrange
// var validMessage = new Message { ShortenedText = "ValidMessage" };
// var invalidMessage = new Message { ShortenedText = null };
// // Arrange children in such order that the first Message encountered has valid ShortenedText.
// var task = new Task
// {
// Name = "InitialName",
// Children = new List<object> { validMessage, invalidMessage }
// };
//
// // Act
// MessageTaskAnalyzer.Analyze(task);
//
// // Assert
// Assert.Equal(_expectedPrefix + validMessage.ShortenedText, task.Name);
// }

/// <summary>
/// Tests that Analyze does not update the task name when the first Message child has null ShortenedText even if subsequent Message children are valid.
/// </summary>
// [Fact] [Error] (158-24)CS0144 Cannot create an instance of the abstract type or interface 'Task' [Error] (160-17)CS0229 Ambiguity between 'Task.Name' and 'Task.Name' [Error] (165-41)CS1503 Argument 1: cannot convert from 'Microsoft.Build.Logging.StructuredLogger.UnitTests.Task' to 'Microsoft.Build.Logging.StructuredLogger.Task' [Error] (169-45)CS0229 Ambiguity between 'Task.Name' and 'Task.Name'
// public void Analyze_FirstMessageChildHasNullShortenedText_IgnoresSubsequentValidMessages()
// {
// // Arrange
// var firstMessage = new Message { ShortenedText = null };
// var secondMessage = new Message { ShortenedText = "SecondMessage" };
// var originalName = "InitialName";
// var task = new Task
// {
// Name = originalName,
// Children = new List<object> { firstMessage, secondMessage }
// };
//
// // Act
// MessageTaskAnalyzer.Analyze(task);
//
// // Assert
// // Since OfType<Message>() picks the first Message (firstMessage) which has null ShortenedText, the task name should remain unchanged.
// Assert.Equal(originalName, task.Name);
// }
}
}

namespace Microsoft.Build.Logging.StructuredLogger
{
/// <summary>
/// Analysis methods for tasks.
/// </summary>
public class MessageTaskAnalyzer
{
/// <summary>
/// Analyzes the specified task. If the task has a child of type Message with a non-null ShortenedText,
/// the task's Name is updated to "Message: " followed by the ShortenedText.
/// </summary>
/// <param name="task">The task to analyze.</param>
public static void Analyze(Task task)
{
var message = task.Children.OfType<Message>().FirstOrDefault();
if (message?.ShortenedText != null)
{
task.Name = "Message: " + message.ShortenedText;
}
}
}
}
Loading