Skip to content

Commit

Permalink
fix: protect directives during mutation + refactoring of mutators
Browse files Browse the repository at this point in the history
  • Loading branch information
dupdob committed Nov 21, 2024
1 parent 06d1655 commit c6e5ac8
Show file tree
Hide file tree
Showing 37 changed files with 291 additions and 275 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

Console.WriteLine("this is a global statement");
37 changes: 35 additions & 2 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,52 @@ public static void ShouldBeSemantically(this SyntaxTree actual, SyntaxTree expec

if (!isSame)
{
// find the different
var actualLines = actual.ToString().Split(Environment.NewLine);
var diff = ScanDiff(actual.GetRoot(), expected.GetRoot());

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

}

// 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++)
{
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}");
continue;
}
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}");
}
}
}

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]))
{
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using Stryker.Abstractions;
using Stryker.Abstractions.Mutants;
using Stryker.Abstractions.Mutators;
using Stryker.Abstractions.Options;
using Stryker.Configuration;
Expand Down Expand Up @@ -942,8 +939,6 @@ public void ShouldNotMutateTopLevelStatementsIfDisabledByComment()

}

[TestMethod]

[TestMethod]
public void ShouldMutateChainedMutations()
{
Expand Down Expand Up @@ -1598,6 +1593,7 @@ static string Value(string text) =>
ShouldMutateSourceInClassToExpected(source, expected);
}


[TestMethod]
public void ShouldMutateSubStringMethod()
{
Expand Down Expand Up @@ -1625,10 +1621,10 @@ public void ShouldMutateChainedStringMethods()
"""
static char Value =>
(StrykerNamespace.MutantControl.IsActive(0)?'\0':
(StrykerNamespace.MutantControl.IsActive(1)?"".ElementAt(2):
(StrykerNamespace.MutantControl.IsActive(2)?"test ".ToUpper().Trim().PadRight(2).Substring(2).ElementAt(2):
(StrykerNamespace.MutantControl.IsActive(3)?"".PadLeft(2).Substring(2).ElementAt(2):
(StrykerNamespace.MutantControl.IsActive(4)?"test ".ToLower().Trim().PadLeft(2).Substring(2).ElementAt(2):
(StrykerNamespace.MutantControl.IsActive(1)?"".ElementAt(2):
(StrykerNamespace.MutantControl.IsActive(3)?"".PadLeft(2).Substring(2).ElementAt(2):
(StrykerNamespace.MutantControl.IsActive(5)?"":"test ").ToUpper().Trim().PadLeft(2).Substring(2).ElementAt(2))))));
""";

Expand Down Expand Up @@ -1666,7 +1662,7 @@ public void ShouldMutateCollectionExpressionSpanProperty()
var source = "static ReadOnlySpan<int> Value => [1, 2, 3];";

var expected =
"static ReadOnlySpan<int> Value => (StrykerNamespace.MutantControl.IsActive(0)?[]:[1,2,3]);";
"static ReadOnlySpan<int> Value => (StrykerNamespace.MutantControl.IsActive(0)?(ReadOnlySpan<int>)[]:[1,2,3]);";
ShouldMutateSourceInClassToExpected(source, expected);
}

Expand All @@ -1690,7 +1686,7 @@ public void M() {
int[] bcd = [1, .. abc, 3];
} else {
int[] abc = { 5, 5 };
int[] bcd = (StrykerNamespace.MutantControl.IsActive(2)?[]:[1, .. abc, 3]);
int[] bcd = (StrykerNamespace.MutantControl.IsActive(2)?(int[])[]:[1, .. abc, 3]);
}
}
}
Expand Down Expand Up @@ -1719,7 +1715,7 @@ public void M() {
} else {
int[] abc = {5, 5};
var bcd = (int[])(
StrykerNamespace.MutantControl.IsActive(2) ? [] : [ 1, ..abc, 3 ]);
StrykerNamespace.MutantControl.IsActive(2) ? (int[])[] : [ 1, ..abc, 3 ]);
}
}
}
Expand All @@ -1743,7 +1739,7 @@ public void M() {
if (StrykerNamespace.MutantControl.IsActive(0)) {
} else {
// Stryker disable String : Not mutation under test
Span<string> weekDays = (StrykerNamespace.MutantControl.IsActive(1) ? [] : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]);
Span<string> weekDays = (StrykerNamespace.MutantControl.IsActive(1) ? (Span<string>)[] : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]);
}
}
""";
Expand Down Expand Up @@ -1773,14 +1769,12 @@ public void Example()
"""
// Initialize private field:
// Stryker disable String : Not mutation under test
private static readonly ImmutableArray<string> _months =
StrykerNamespace.MutantContext.TrackValue(
() =>(StrykerNamespace.MutantControl.IsActive(0) ? [] : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]));
private static readonly ImmutableArray<string> _months =StrykerNamespace.MutantContext.TrackValue(() =>(StrykerNamespace.MutantControl.IsActive(0) ? (ImmutableArray<string>)[] : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]));
// property with expression body:
public IEnumerable<int> MaxDays =>
(StrykerNamespace.MutantControl.IsActive(13)
? []
? (IEnumerable<int>)[]
: [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]);
public int Sum(IEnumerable<int> values) =>
Expand All @@ -1790,8 +1784,7 @@ public void Example() {
if (StrykerNamespace.MutantControl.IsActive(15)) {
} else {
// As a parameter:
int sum = Sum(
(StrykerNamespace.MutantControl.IsActive(16) ? [] : [ 1, 2, 3, 4, 5 ]));
int sum = Sum((StrykerNamespace.MutantControl.IsActive(16)?(IEnumerable<int>)[]:[1, 2, 3, 4, 5]));
}
}
""";
Expand Down Expand Up @@ -1834,7 +1827,7 @@ public void M() {
string oxygen = "O";
string fluorine = "F";
string neon = "Ne";
string[] elements = (StrykerNamespace.MutantControl.IsActive(11) ? [] : [
string[] elements = (StrykerNamespace.MutantControl.IsActive(11) ? (string[])[] : [
hydrogen, helium, lithium, beryllium, boron, carbon, nitrogen, oxygen,
fluorine, neon
]);
Expand Down Expand Up @@ -1863,14 +1856,14 @@ public void M() {
// Stryker disable String : Not mutation under test
if (StrykerNamespace.MutantControl.IsActive(0)) {
} else {
string[] vowels = (StrykerNamespace.MutantControl.IsActive(1) ? [] : ["a", "e", "i", "o", "u"]);
string[] consonants = (StrykerNamespace.MutantControl.IsActive(7) ? [] : [
string[] vowels = (StrykerNamespace.MutantControl.IsActive(1) ? (string[])[] : ["a", "e", "i", "o", "u"]);
string[] consonants = (StrykerNamespace.MutantControl.IsActive(7) ? (string[])[] : [
"b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "z"
]);
string[] alphabet =
(StrykerNamespace.MutantControl.IsActive(28)
? []
? (string[])[]
: [..vowels, ..consonants, "y"]);
}
}
Expand All @@ -1896,7 +1889,7 @@ public void M() {
if (StrykerNamespace.MutantControl.IsActive(1)) {
;
} else {
Iter((StrykerNamespace.MutantControl.IsActive(2) ? [] : [ 1 ]));
Iter((StrykerNamespace.MutantControl.IsActive(2)?(IList<int>)[]:[1]));
}
}
}
Expand All @@ -1911,7 +1904,7 @@ public void ShouldMutateNestedImplicitCollectionExpression()
var source = "static int[][] Value => [[1, 2], [3]];";

var expected =
"static int[][] Value => (StrykerNamespace.MutantControl.IsActive(0)?[]:[(StrykerNamespace.MutantControl.IsActive(1)?[]:[1, 2]), (StrykerNamespace.MutantControl.IsActive(2)?[]:[3])]);";
"static int[][] Value => (StrykerNamespace.MutantControl.IsActive(0)?(int[][])[]:[(StrykerNamespace.MutantControl.IsActive(1)?(int[])[]:[1, 2]), (StrykerNamespace.MutantControl.IsActive(2)?(int[])[]:[3])]);";
ShouldMutateSourceInClassToExpected(source, expected);
}

Expand All @@ -1921,7 +1914,30 @@ public void ShouldMutateNestedExplicitCollectionExpression()
var source = "static int[][] Value => [[1, 2], new int[] { 3 }];";

var expected =
"static int[][] Value => (StrykerNamespace.MutantControl.IsActive(0)?[]:[(StrykerNamespace.MutantControl.IsActive(1)?[]:[1, 2]), (StrykerNamespace.MutantControl.IsActive(2)?new int[] {}:new int[] { 3 })]);";
"static int[][] Value => (StrykerNamespace.MutantControl.IsActive(0)?(int[][])[]:[(StrykerNamespace.MutantControl.IsActive(1)?(int[])[]:[1, 2]), (StrykerNamespace.MutantControl.IsActive(2)?new int[] {}:new int[] { 3 })]);";
ShouldMutateSourceInClassToExpected(source, expected);
}

[TestMethod]
public void ShouldProtectDirectives()
{
var source = @"public void SomeMethod() {
var x = 0;
#if !DEBUG
x++;
#endif
}";
var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{
var x = 0;
if(StrykerNamespace.MutantControl.IsActive(1)){;}else{if(StrykerNamespace.MutantControl.IsActive(2)){ #if !DEBUG
x--;
}else{ #if !DEBUG
x++;
}} #endif
}}";

ShouldMutateSourceInClassToExpected(source, expected);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
using Stryker.Core.Mutants;
using Stryker.Core.InjectedHelpers;
using Stryker.Abstractions.Options;
using System.Linq.Expressions;
using Microsoft.CodeAnalysis;
using System.Linq;
using System.Collections.Generic;
using System;

namespace Stryker.Core.UnitTest.Mutants;

Expand All @@ -25,11 +28,12 @@ public class MutantOrchestratorTestsBase : TestBase
protected void ShouldMutateSourceToExpected(string actual, string expected)
{
var syntaxTree = CSharpSyntaxTree.ParseText(actual);

var compilation = CSharpCompilation.Create(null)
.AddSyntaxTrees(syntaxTree);
var model = compilation.GetSemanticModel(syntaxTree);
var actualNode = Target.Mutate(syntaxTree, model);
Type[] typeToLoad = [typeof(object), typeof(List<>), typeof(Enumerable), typeof(Nullable<>)];
MetadataReference[] references = typeToLoad.Select( t=> MetadataReference.CreateFromFile(t.Assembly.Location)).ToArray();
var compilation = CSharpCompilation.Create(null).WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithNullableContextOptions(NullableContextOptions.Enable))
.AddSyntaxTrees(syntaxTree).WithReferences(references);
var actualNode = Target.Mutate(syntaxTree, compilation.GetSemanticModel(syntaxTree));
actual = actualNode.GetRoot().ToFullString();
actual = actual.Replace(Injector.HelperNamespace, "StrykerNamespace");
actualNode = CSharpSyntaxTree.ParseText(actual);
Expand All @@ -40,21 +44,26 @@ protected void ShouldMutateSourceToExpected(string actual, string expected)

protected void ShouldMutateSourceInClassToExpected(string actual, string expected)
{
actual = @"using System;
var initBlock=@"using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
namespace StrykerNet.UnitTest.Mutants.TestResources;
namespace StrykerNet.UnitTest.Mutants.TestResources;";


actual = string.Format(@"{0}
class TestClass
{" + actual + @"}
";
{{
{1}
}}
", initBlock, actual);

expected = @"using System;
using System.Collections.Generic;
using System.Text;
namespace StrykerNet.UnitTest.Mutants.TestResources;
expected = string.Format(@"{0}
class TestClass
{" + expected + @"}
";
{{
{1}
}}
", initBlock, expected);
ShouldMutateSourceToExpected(actual, expected);
}
}
Loading

0 comments on commit c6e5ac8

Please sign in to comment.