Skip to content
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

feat: Protect compiler directives during mutation #3116

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7bd59df
misc: split test files
dupdob Nov 8, 2024
7e29031
Merge remote-tracking branch 'origin/master' into improve_mutation_of…
dupdob Nov 8, 2024
19d22aa
Merge remote-tracking branch 'origin/master' into improve_mutation_of…
dupdob Nov 8, 2024
1d24946
Merge remote-tracking branch 'origin/master' into improve_mutation_of…
dupdob Nov 13, 2024
cc16177
feat: move original code trivia around mutation switching control nod…
dupdob Nov 20, 2024
06d1655
misc: remove syntax parsing from ispattermutator, removed switchexpre…
dupdob Nov 20, 2024
ed32262
fix: protect directives during mutation + refactoring of mutators
dupdob Nov 21, 2024
6826228
Merge remote-tracking branch 'origin/master' into improve_mutation_of…
dupdob Nov 21, 2024
1736132
fix: restore parsing of patterns
dupdob Nov 22, 2024
bfec606
fix: new integration tests results
dupdob Nov 22, 2024
80871b8
fix: fix some unit tests
dupdob Nov 22, 2024
95713b8
fix: sonar issues
dupdob Nov 22, 2024
da24233
fix: integration test results
dupdob Nov 22, 2024
df53cc3
Merge remote-tracking branch 'origin/master' into improve_mutation_of…
dupdob Nov 22, 2024
46a0e42
Merge branch 'master' into improve_mutation_of_directives
dupdob Dec 2, 2024
4291361
Merge remote-tracking branch 'origin/master' into improve_mutation_of…
dupdob Dec 3, 2024
53e9bbb
Merge remote-tracking branch 'origin/improve_mutation_of_directives' …
dupdob Dec 3, 2024
23c451c
Merge remote-tracking branch 'origin/master' into improve_mutation_of…
dupdob Dec 22, 2024
6f9c369
misc: address PR comments
dupdob Dec 22, 2024
f7418b4
misc: switch TargetProject to being an executable target
dupdob Dec 26, 2024
60a7a04
Merge remote-tracking branch 'origin/master' into improve_mutation_of…
dupdob Dec 26, 2024
ba24808
misc: fix integration test
dupdob Dec 26, 2024
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
3 changes: 1 addition & 2 deletions docs/technical-reference/Mutation Orchestration Design.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,7 @@ This class is used for several constructs:
- `AttributeListSyntax`: Attributes must be defined at compile time.
- `ParameterListSyntax`: Parameters and default values must be known at compile time.
- `EnumMemberSyntax`: Enumeration value must be known at compile time
- `RecursivePatternSyntax`: Pattern syntax must be known at compile time.
- `UsingDirectiveSyntax`: using directives are fixed and critical for compilation
- `UsingDirectiveSyntax`: using directives are fixed and critical for compilation
- `FieldDeclarationSyntax` (only const fields): cannot modify const fields at run time.
- `LocalDeclarationStatementSyntax` (only const): cannot modify constants at run time.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

Console.WriteLine("this is a global statement");
dupdob marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public async Task CSharp_NetCore_SingleTestProject()

var report = await strykerRunOutput.DeserializeJsonReportAsync();

CheckReportMutants(report, total: 601, ignored: 247, survived: 4, killed: 9, timeout: 2, nocoverage: 308);
CheckReportMutants(report, total: 606, ignored: 252, survived: 4, killed: 9, timeout: 2, nocoverage: 308);
CheckReportTestCounts(report, total: 11);
}

Expand All @@ -101,8 +101,8 @@ public async Task CSharp_NetCore_WithTwoTestProjects()
using var strykerRunOutput = File.OpenRead(latestReport.FullName);

var report = await strykerRunOutput.DeserializeJsonReportAsync();

CheckReportMutants(report, total: 601, ignored: 105, survived: 5, killed: 11, timeout: 2, nocoverage: 447);
CheckReportMutants(report, total: 606, ignored: 105, survived: 5, killed: 11, timeout: 2, nocoverage: 452);
CheckReportTestCounts(report, total: 21);
}

Expand All @@ -121,7 +121,7 @@ public async Task CSharp_NetCore_SolutionRun()

var report = await strykerRunOutput.DeserializeJsonReportAsync();

CheckReportMutants(report, total: 601, ignored: 247, survived: 4, killed: 9, timeout: 2, nocoverage: 308);
CheckReportMutants(report, total: 606, ignored: 252, survived: 4, killed: 9, timeout: 2, nocoverage: 308);
CheckReportTestCounts(report, total: 23);
}

Expand Down
41 changes: 29 additions & 12 deletions src/Stryker.Core/Stryker.Core.UnitTest/AssertExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Shouldly;
using System;
using System.Collections.Generic;
using System.IO.Abstractions.TestingHelpers;
using System.Linq;

Expand Down Expand Up @@ -32,21 +34,36 @@ public static void ShouldBeSemantically(this SyntaxTree actual, SyntaxTree expec

if (!isSame)
{
// find the different
var actualLines = actual.ToString().Split(Environment.NewLine);
var expectedLines = expected.ToString().Split(Environment.NewLine);
for (var i = 0; i < actualLines.Length; i++)
var diff = ScanDiff(actual.GetRoot(), expected.GetRoot());

Console.WriteLine(string.Join(Environment.NewLine, diff));

}
}

private static List<string> ScanDiff(SyntaxNode actual, SyntaxNode expected)
{
var actualChildren = actual.ChildNodes().ToList();
var expectedChildren = expected.ChildNodes().ToList();
var failedStatements = new List<string>();
for (var i = 0; i < actualChildren.Count; i++)
{
if (expectedChildren.Count <= i)
{
failedStatements.Add($"Extra statements: {actualChildren[i]}");
continue;
}
if ((actualChildren[i] is not StatementSyntax) || (actualChildren[i] is BlockSyntax or IfStatementSyntax or ForStatementSyntax or WhileStatementSyntax ))
{
failedStatements.AddRange(ScanDiff(actualChildren[i], expectedChildren[i]));
continue;
}
if (!actualChildren[i].IsEquivalentTo(expectedChildren[i]))
{
if (expectedLines.Length <= i)
{
isSame.ShouldBeTrue($"AST's are not equivalent. Line[{i + 1}]{Environment.NewLine}actual:{actualLines[i]}{Environment.NewLine}expect: nothing{Environment.NewLine}Actual(full):{Environment.NewLine}{actual}{Environment.NewLine}, expected:{Environment.NewLine}{expected}");
}
if (actualLines[i] != expectedLines[i])
{
isSame.ShouldBeTrue($"AST's are not equivalent. Line[{i + 1}]{Environment.NewLine}actual:{actualLines[i]}{Environment.NewLine}expect:{expectedLines[i]}{Environment.NewLine}Actual(full):{Environment.NewLine}{actual}{Environment.NewLine}, expected:{Environment.NewLine}{expected}");
}
failedStatements.Add($"Not equivalent. Actual:{Environment.NewLine}{actualChildren[i]}{Environment.NewLine}Expected:{Environment.NewLine}{expectedChildren[i]}");
}
}
return failedStatements;
}

public static void ShouldBeWithNewlineReplace(this string actual, string expected)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class BuildAnalyzerTestsBase : TestBase
protected internal const string DefaultFramework = "net6.0";
protected readonly MockFileSystem FileSystem = new();
protected string ProjectPath;
private readonly Dictionary<string, Dictionary<string, IAnalyzerResult>> _projectCache = new();
private readonly Dictionary<string, Dictionary<string, IAnalyzerResult>> _projectCache = [];
protected readonly Mock<IBuildalyzerProvider> BuildalyzerProviderMock = new(MockBehavior.Strict);

public BuildAnalyzerTestsBase()
Expand All @@ -41,7 +41,7 @@ protected Mock<IProjectAnalyzer> SourceProjectAnalyzerMock(string csprojPathName
IEnumerable<string> projectReferences = null, string framework = DefaultFramework, Func<bool> success = null)
{
var properties = GetSourceProjectDefaultProperties();
projectReferences ??= new List<string>();
projectReferences ??= [];

return BuildProjectAnalyzerMock(csprojPathName, sourceFiles, properties, projectReferences, [framework], success);
}
Expand All @@ -58,7 +58,7 @@ protected Mock<IProjectAnalyzer> SourceProjectAnalyzerMock(string csprojPathName
IEnumerable<string> projectReferences , IEnumerable<string> frameworks, Func<bool> success = null)
{
var properties = GetSourceProjectDefaultProperties();
projectReferences??= new List<string>();
projectReferences??= [];

return BuildProjectAnalyzerMock(csprojPathName, sourceFiles, properties, projectReferences, frameworks, success);
}
Expand All @@ -81,7 +81,7 @@ public static Dictionary<string, string> GetSourceProjectDefaultProperties()
/// <remarks>the test project references the production code project and contains no source file</remarks>
protected Mock<IProjectAnalyzer> TestProjectAnalyzerMock(string testCsprojPathName, string csProj, IEnumerable<string> frameworks = null, bool success = true)
{
frameworks??=new []{DefaultFramework};
frameworks??=[DefaultFramework];
var properties = new Dictionary<string, string>{ { "IsTestProject", "True" }, { "Language", "C#" } };
var projectReferences = string.IsNullOrEmpty(csProj) ? [] : GetProjectResult(csProj, frameworks.First()).ProjectReferences.Append(csProj).ToList();
return BuildProjectAnalyzerMock(testCsprojPathName, [], properties, projectReferences, frameworks, () => success);
Expand Down Expand Up @@ -110,7 +110,7 @@ private IAnalyzerResult GetProjectResult(string projectFile, string expectedFram
/// <returns>a tuple with the framework kind first and the version next</returns>
protected static (FrameworkKind kind, decimal version) ParseFramework(string framework)
{
FrameworkKind kind;

decimal version;

if (framework.StartsWith("netcoreapp"))
Expand Down Expand Up @@ -153,7 +153,7 @@ protected enum FrameworkKind
/// <returns><paramref name="framework"/> if the framework is among the target, the best match if available, null otherwise.</returns>
protected static string PickCompatibleFramework(string framework, IEnumerable<string> frameworks)
{
var parsed = ParseFramework(framework);
var (kind, version) = ParseFramework(framework);

string bestCandidate = null;
var bestVersion = 1.0m;
Expand All @@ -164,12 +164,12 @@ protected static string PickCompatibleFramework(string framework, IEnumerable<st
return framework;
}
var parsedCandidate = ParseFramework(candidate);
if (parsedCandidate.kind != parsed.kind)
if (parsedCandidate.kind != kind)
{
continue;
}

if (parsedCandidate.version > parsed.version || parsedCandidate.version <= bestVersion)
if (parsedCandidate.version > version || parsedCandidate.version <= bestVersion)
{
continue;
}
Expand Down Expand Up @@ -264,7 +264,7 @@ internal Mock<IProjectAnalyzer> BuildProjectAnalyzerMock(string csprojPathName,
return projectAnalyzerMock;
}

private IAnalyzerResults BuildAnalyzerResultsMock(IDictionary<string, IAnalyzerResult> projectAnalyzerResults)
private static IAnalyzerResults BuildAnalyzerResultsMock(IDictionary<string, IAnalyzerResult> projectAnalyzerResults)
{
var analyzerResults = projectAnalyzerResults.Values.ToList();
var sourceProjectAnalyzerResultsMock = new Mock<IAnalyzerResults>(MockBehavior.Strict);
Expand Down
Loading
Loading