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(mutators): Add default parameter mutator #3024

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,27 @@ public static void NamedParameters()
Console.WriteLine(format: "{0:f}", arg0: 6.02214179e23);
Console.WriteLine(arg0: 6.02214179e23, format: "{0:f}");
}

// default parameters
public static void DefaultParameters()
{
ExampleDefaultParameterMethod("Hello");
ExampleDefaultParameterMethod("Hello", "World");
ExampleDefaultParameterMethod("Hello", "World", "Foo");
ExampleDefaultParameterMethod("Hello", "World", "Foo", "Bar");
}

// default named parameters
public static void DefaultNamedParameters()
{
ExampleDefaultParameterMethod(required1: "Hello");
ExampleDefaultParameterMethod(optional3: "Hello", required1: "World");
ExampleDefaultParameterMethod(optional2: "Hello", required1: "World", optional1: "Foo");
ExampleDefaultParameterMethod(optional3: "Hello", optional2: "World", required1: "Foo", optional1: "Bar");
}

private static void ExampleDefaultParameterMethod(string required1, string optional1 = "optional1",
string optional2 = "optional2", string optional3 = "optional3")
{
}
}
4 changes: 3 additions & 1 deletion src/Stryker.Abstractions/Mutator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ public enum Mutator
[MutatorDescription("String Method")]
StringMethod,
[MutatorDescription("Conditional operators")]
Conditional
Conditional,
[MutatorDescription("Default parameter")]
DefaultParameter
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public void SomeLinq()
};
var codeInjection = new CodeInjection();
var placer = new MutantPlacer(codeInjection);
var mutator = new CsharpMutantOrchestrator(placer, options: options);
var mutator = new CSharpMutantOrchestrator(placer, options: options);
var helpers = new List<SyntaxTree>();
foreach (var (name, code) in codeInjection.MutantHelpers)
{
Expand Down Expand Up @@ -196,7 +196,7 @@ private void RefreshAccountNumber()
};
var codeInjection = new CodeInjection();
var placer = new MutantPlacer(codeInjection);
var mutator = new CsharpMutantOrchestrator(placer, options: options);
var mutator = new CSharpMutantOrchestrator(placer, options: options);
var helpers = new List<SyntaxTree>();
foreach (var (name, code) in codeInjection.MutantHelpers)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
namespace Stryker.Core.UnitTest.Mutants;

[TestClass]
public class CsharpMutantOrchestratorTests : MutantOrchestratorTestsBase
public class CSharpMutantOrchestratorTests : MutantOrchestratorTestsBase
{
[TestMethod]
public void ShouldNotMutateEmptyInterfaces()
Expand Down Expand Up @@ -89,7 +89,7 @@ public void ShouldMutateBlockStatements()
MutationLevel = MutationLevel.Complete,
OptimizationMode = OptimizationModes.CoverageBasedTest,
};
Target = new CsharpMutantOrchestrator(new MutantPlacer(Injector), options: options);
Target = new CSharpMutantOrchestrator(new MutantPlacer(Injector), options: options);

var source = @"private void Move()
{
Expand Down Expand Up @@ -939,7 +939,7 @@ public void ShouldNotMutateIfDisabledByComment()
source = @"public void SomeMethod() {
var x = 0;
{
// Stryker disable all
// Stryker disable all
x++;
}
x/=2;
Expand All @@ -961,15 +961,15 @@ public void ShouldNotMutateIfDisabledByMultilineComment()
{
var source = @"public void SomeMethod() {
var x = 0;
if (condition && other) /* Stryker disable once all */ {
if (condition && other) /* Stryker disable once all */ {
x++;
x*=2;
}
x/=2;
}";
var expected = @"public void SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{
var x = 0;
if ((StrykerNamespace.MutantControl.IsActive(2)?!(condition && other):(StrykerNamespace.MutantControl.IsActive(1)?condition || other:condition && other))) /* Stryker disable once all */ {
if ((StrykerNamespace.MutantControl.IsActive(2)?!(condition && other):(StrykerNamespace.MutantControl.IsActive(1)?condition || other:condition && other))) /* Stryker disable once all */ {
x++;
x*=2;
}
Expand Down Expand Up @@ -1008,7 +1008,7 @@ public void ShouldNotMutateIfDisabledByMultilineComment()
@"if ((StrykerNamespace.MutantControl.IsActive(1)?!(cond):cond)) // Stryker disable once all
x++;")]
[DataRow("if (/* Stryker disable once all*/cond) x++;", "if (/* Stryker disable once all*/cond) if(StrykerNamespace.MutantControl.IsActive(2)){;}else{if(StrykerNamespace.MutantControl.IsActive(3)){x--;}else{x++;}}")]

public void ShouldNotMutateDependingOnWhereMultilineCommentIs(string source, string expected)
{
// must call reset as MsTest reuse test instance on/datarow
Expand Down Expand Up @@ -1933,9 +1933,9 @@ public void ShouldIncrementMutantCountUniquely()
};

var firstOrchestrator =
new CsharpMutantOrchestrator(new MutantPlacer(Injector), options: strykerOptions);
new CSharpMutantOrchestrator(new MutantPlacer(Injector), options: strykerOptions);
var secondOrchestrator =
new CsharpMutantOrchestrator(new MutantPlacer(Injector), options: strykerOptions);
new CSharpMutantOrchestrator(new MutantPlacer(Injector), options: strykerOptions);
var node = SyntaxFactory.ParseExpression("1 == 1") as BinaryExpressionSyntax;

var firstMutant = firstOrchestrator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ namespace Stryker.Core.UnitTest.Mutants;
/// </summary>
public class MutantOrchestratorTestsBase : TestBase
{
protected CsharpMutantOrchestrator Target;
protected CSharpMutantOrchestrator Target;
protected CodeInjection Injector = new();

public MutantOrchestratorTestsBase() => Target = new CsharpMutantOrchestrator(new MutantPlacer(Injector), options: new StrykerOptions
public MutantOrchestratorTestsBase() => Target = new CSharpMutantOrchestrator(new MutantPlacer(Injector), options: new StrykerOptions
{
MutationLevel = MutationLevel.Complete,
OptimizationMode = OptimizationModes.CoverageBasedTest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ public void ShouldRollBackFailedConstructor()
var source = @"class Test {
static TestClass()=> Value-='a';}";

var orchestrator = new CsharpMutantOrchestrator(placer, options: new StrykerOptions
var orchestrator = new CSharpMutantOrchestrator(placer, options: new StrykerOptions
{
OptimizationMode = OptimizationModes.CoverageBasedTest,
MutationLevel = MutationLevel.Complete
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Shouldly;
using Stryker.Abstractions;
using Stryker.Abstractions.Mutants;
using Stryker.Abstractions.Mutators;
using Stryker.Core.Mutants;
using Stryker.Core.Mutators;

namespace Stryker.Core.UnitTest.Mutators;

[TestClass]
public class DefaultParameterMutatorTests
{
[TestMethod]
public void ShouldBeMutationLevelComplete()
{
var options = new StrykerOptions();
var orchestratorMock = new Mock<ICSharpMutantOrchestrator>();
var target = new DefaultParameterMutator(orchestratorMock.Object, options);
target.MutationLevel.ShouldBe(MutationLevel.Complete);
}

[TestMethod]
public void ShouldMutateDefaultParameter()
{
var source = @"
using System;

class Program
{
void Method()
{
DefaultParameterMethod();
}

void DefaultParameterMethod(string parameter = ""default"")
{
Console.WriteLine(parameter);
}
}"";
";
var syntaxTree = CSharpSyntaxTree.ParseText(source);
GetMutations(syntaxTree).ShouldHaveSingleItem();
}

[TestMethod]
public void ShouldNotMutateExplicitlyUsedDefaultParameter()
{
var source = @"
using System;

class Program
{
void Method()
{
DefaultParameterMethod(""hello world"");
}

void DefaultParameterMethod(string parameter = ""default"")
{
Console.WriteLine(parameter);
}
}"";
";
var syntaxTree = CSharpSyntaxTree.ParseText(source);
GetMutations(syntaxTree).ShouldBeEmpty();
}

[TestMethod]
public void ShouldNotMutateExplicitlyUsedNamedDefaultParameter()
{
var source = @"
using System;

class Program
{
void Method()
{
DefaultParameterMethod(parameter2: ""hello world"");
}

void DefaultParameterMethod(string parameter1 = ""default1"", string parameter2 = ""default2"", string parameter3 = ""default3"")
{
Console.WriteLine(parameter);
}
}"";
";
var syntaxTree = CSharpSyntaxTree.ParseText(source);
var mutations = GetMutations(syntaxTree);

mutations.Count().ShouldBe(2);
mutations.ShouldNotContain(x => x.DisplayName.Contains("parameter2"));
}

[TestMethod]
public void ShouldMutateImplicitlyUsedDefaultParameter()
{
var source = @"
using System;

class Program
{
void Method()
{
DefaultParameterMethod(""Hello world"");
}

void DefaultParameterMethod(string parameter1 = ""default"", string parameter2 = ""default2"", string parameter3 = ""default3"")
{
Console.WriteLine(parameter);
}
}"";
";
var syntaxTree = CSharpSyntaxTree.ParseText(source);

var mutations = GetMutations(syntaxTree);

mutations.Count().ShouldBe(2);
mutations.ShouldNotContain(x => x.DisplayName.Contains("parameter1"));
}

private IEnumerable<Mutation> GetMutations(SyntaxTree tree)
{
var invocations = tree
.GetRoot()
.DescendantNodes()
.OfType<InvocationExpressionSyntax>();
var options = new StrykerOptions()
{
MutationLevel = MutationLevel.Complete
};
var orchestratorMock = new Mock<ICSharpMutantOrchestrator>();
orchestratorMock.SetupGet(m => m.Mutators).Returns(new List<IMutator> { new StringMutator() });

var target = new DefaultParameterMutator(orchestratorMock.Object, options);

var semanticModel = GetSemanticModel(tree);

return invocations.SelectMany(invocation => target.ApplyMutations(invocation, semanticModel));
}

private static SemanticModel GetSemanticModel(SyntaxTree tree)
{
var compilation = CSharpCompilation.Create("Test")
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(tree);
return compilation.GetSemanticModel(tree);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void ShouldValidateExcludedMutation()

var ex = Should.Throw<InputException>(() => target.Validate<Mutator>());

ex.Message.ShouldBe($"Invalid excluded mutation (gibberish). The excluded mutations options are [Statement, Arithmetic, Block, Equality, Boolean, Logical, Assignment, Unary, Update, Checked, Linq, String, Bitwise, Initializer, Regex, NullCoalescing, Math, StringMethod, Conditional]");
ex.Message.ShouldBe($"Invalid excluded mutation (gibberish). The excluded mutations options are [Statement, Arithmetic, Block, Equality, Boolean, Logical, Assignment, Unary, Update, Checked, Linq, String, Bitwise, Initializer, Regex, NullCoalescing, Math, StringMethod, Conditional, DefaultParameter]");
}

[TestMethod]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,29 @@

namespace Stryker.Core.Mutants;

/// <inheritdoc/>
public class CsharpMutantOrchestrator : BaseMutantOrchestrator<SyntaxTree, SemanticModel>
/// <inheritdoc cref="Stryker.Core.Mutants.BaseMutantOrchestrator{T,TY}" />
public class CSharpMutantOrchestrator : BaseMutantOrchestrator<SyntaxTree, SemanticModel>, ICSharpMutantOrchestrator
{
private static readonly TypeBasedStrategy<SyntaxNode, INodeOrchestrator> specificOrchestrator =
new();

private ILogger Logger { get; }

static CsharpMutantOrchestrator() =>
public IEnumerable<IMutator> Mutators { get; }

static CSharpMutantOrchestrator() =>
// declare node specific orchestrators. Note that order is relevant, they should be declared from more specific to more generic one
specificOrchestrator.RegisterHandlers(BuildOrchestratorList());

/// <summary>
/// <param name="mutators">The mutators that should be active during the mutation process</param>
/// </summary>
public CsharpMutantOrchestrator(MutantPlacer placer, IEnumerable<IMutator> mutators = null, IStrykerOptions options = null) : base(options)
public CSharpMutantOrchestrator(MutantPlacer placer, IEnumerable<IMutator> mutators = null, IStrykerOptions options = null) : base(options)
{
Placer = placer;
Mutators = mutators ?? DefaultMutatorList();
Mutants = new Collection<IMutant>();
Logger = ApplicationLogging.LoggerFactory.CreateLogger<CsharpMutantOrchestrator>();
Logger = ApplicationLogging.LoggerFactory.CreateLogger<CSharpMutantOrchestrator>();
}

private static List<INodeOrchestrator> BuildOrchestratorList() =>
Expand Down Expand Up @@ -91,11 +93,12 @@ private static List<INodeOrchestrator> BuildOrchestratorList() =>
new SyntaxNodeOrchestrator()
];

private static List<IMutator> DefaultMutatorList() =>
private List<IMutator> DefaultMutatorList() =>
[
new BinaryExpressionMutator(),
new BlockMutator(),
new BooleanMutator(),
new DefaultParameterMutator(this, Options),
new ConditionalExpressionMutator(),
new AssignmentExpressionMutator(),
new PrefixUnaryMutator(),
Expand All @@ -118,8 +121,6 @@ private static List<IMutator> DefaultMutatorList() =>
new StringMethodMutator()
];

private IEnumerable<IMutator> Mutators { get; }

public MutantPlacer Placer { get; }

/// <summary>
Expand All @@ -132,7 +133,7 @@ public override SyntaxTree Mutate(SyntaxTree input, SemanticModel semanticModel)
// search for node specific handler
input.WithRootAndOptions(GetHandler(input.GetRoot()).Mutate(input.GetRoot(), semanticModel, new MutationContext(this)), input.Options);

internal INodeOrchestrator GetHandler(SyntaxNode currentNode) => specificOrchestrator.FindHandler(currentNode);
internal static INodeOrchestrator GetHandler(SyntaxNode currentNode) => specificOrchestrator.FindHandler(currentNode);

internal IEnumerable<Mutant> GenerateMutationsForNode(SyntaxNode current, SemanticModel semanticModel, MutationContext context)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Collections.Generic;
using Stryker.Abstractions.Mutators;

namespace Stryker.Core.Mutants;

public interface ICSharpMutantOrchestrator
{
IEnumerable<IMutator> Mutators { get; }
}
Loading
Loading