Skip to content

Commit

Permalink
chore: keep on refining design
Browse files Browse the repository at this point in the history
  • Loading branch information
dupdob committed Feb 2, 2024
1 parent f7a877e commit 014af1f
Show file tree
Hide file tree
Showing 21 changed files with 389 additions and 67 deletions.
313 changes: 313 additions & 0 deletions docs/technical-reference/Mutation Orchestration Design.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ private void RefreshAccountNumber()
}

var mutant = mutator.Mutate(syntaxTree, null);

helpers.Add(mutant);

var references = new List<string> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Shouldly;
using Stryker.Core.Mutants;
using Stryker.Core.Mutators;
Expand Down Expand Up @@ -502,9 +500,9 @@ string SomeLocalFunction()
public void ShouldMutateConditionalExpressionOnArrayDeclaration()
{
var source =
@"public static IEnumerable<int> Foo() => new int[] { }.ToArray().Any(x => x==1)?.OrderBy(e => e).ToList();";
@"public static IEnumerable<int> Foo() => new int[] { }.ToArray()!.Any(x => x==1)?.OrderBy(e => e).ToList();";
var expected =
@"public static IEnumerable<int> Foo() => (StrykerNamespace.MutantControl.IsActive(2)?new int[] { }.ToArray().Any(x => x==1)?.OrderByDescending(e => e).ToList():(StrykerNamespace.MutantControl.IsActive(0)?new int[] { }.ToArray().All(x => x==1):new int[] { }.ToArray().Any(x => (StrykerNamespace.MutantControl.IsActive(1)?x!=1:x==1)))?.OrderBy(e => e).ToList());";
@"public static IEnumerable<int> Foo() => (StrykerNamespace.MutantControl.IsActive(2)?new int[] { }.ToArray()!.Any(x => x==1)?.OrderByDescending(e => e).ToList():(StrykerNamespace.MutantControl.IsActive(0)?new int[] { }.ToArray()!.All(x => x==1):new int[] { }.ToArray()!.Any(x => (StrykerNamespace.MutantControl.IsActive(1)?x!=1:x==1)))?.OrderBy(e => e).ToList());";

ShouldMutateSourceInClassToExpected(source, expected);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Stryker.Core/Stryker.Core/Helpers/RoslynHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static bool IsAStringExpression(this ExpressionSyntax node) =>
/// </summary>
/// <param name="node">expression to check</param>
/// <returns>true if it contains a declaration</returns>
public static bool ContainsDeclarations(this ExpressionSyntax node) =>
public static bool ContainsDeclarations(this SyntaxNode node) =>
node.ContainsNodeThatVerifies(x =>
x.IsKind(SyntaxKind.DeclarationExpression) || x.IsKind(SyntaxKind.DeclarationPattern));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace Stryker.Core.Mutants;
/// <inheritdoc/>
public class CsharpMutantOrchestrator : BaseMutantOrchestrator<SyntaxTree, SemanticModel>
{
private readonly TypeBasedStrategy<SyntaxNode, INodeMutator> _specificOrchestrator =
private readonly TypeBasedStrategy<SyntaxNode, INodeOrchestrator> _specificOrchestrator =
new();

private ILogger Logger { get; }
Expand All @@ -32,7 +32,11 @@ public CsharpMutantOrchestrator(MutantPlacer placer, IEnumerable<IMutator> mutat
Logger = ApplicationLogging.LoggerFactory.CreateLogger<CsharpMutantOrchestrator>();

// declare node specific orchestrators. Note that order is relevant, they should be declared from more specific to more generic one
_specificOrchestrator.RegisterHandlers(new List<INodeMutator>
_specificOrchestrator.RegisterHandlers(BuildOrchestratorList());
}

private static List<INodeOrchestrator> BuildOrchestratorList() =>
new()
{
// Those node types describe compile time constants and thus cannot be mutated at run time
// attributes
Expand Down Expand Up @@ -69,14 +73,13 @@ public CsharpMutantOrchestrator(MutantPlacer placer, IEnumerable<IMutator> mutat
// ensure declaration are mutated at the block level
new LocalDeclarationOrchestrator(),
new InvocationExpressionOrchestrator(),

new MemberDefinitionOrchestrator<MemberDeclarationSyntax>(),
new MutateAtStatementLevelOrchestrator<AssignmentExpressionSyntax>(),
new BlockOrchestrator(),
new StatementSpecificOrchestrator<StatementSyntax>(),
new ExpressionSpecificOrchestrator<ExpressionSyntax>(),
new SyntaxNodeOrchestrator()
});
}
};

private static List<IMutator> DefaultMutatorList() =>
new()
Expand Down Expand Up @@ -119,7 +122,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 INodeMutator GetHandler(SyntaxNode currentNode) => _specificOrchestrator.FindHandler(currentNode);
internal 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
Expand Up @@ -11,7 +11,7 @@ internal class AccessorSyntaxOrchestrator : BaseFunctionOrchestrator<AccessorDec
{
protected override (BlockSyntax block, ExpressionSyntax expression) GetBodies(AccessorDeclarationSyntax node) => (node.Body, node.ExpressionBody?.Expression);

protected override ParameterListSyntax Parameters(AccessorDeclarationSyntax node) => SyntaxFactory.ParameterList();
protected override ParameterListSyntax ParameterList(AccessorDeclarationSyntax node) => SyntaxFactory.ParameterList();

protected override TypeSyntax ReturnType(AccessorDeclarationSyntax node) => node.ReturnType();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal class AnonymousFunctionExpressionOrchestrator : BaseFunctionOrchestrato
{
protected override (BlockSyntax block, ExpressionSyntax expression) GetBodies(AnonymousFunctionExpressionSyntax node) => (node.Block, node.ExpressionBody);

protected override ParameterListSyntax Parameters(AnonymousFunctionExpressionSyntax node) => node switch {ParenthesizedLambdaExpressionSyntax parenthesizedLambda => parenthesizedLambda.ParameterList,
protected override ParameterListSyntax ParameterList(AnonymousFunctionExpressionSyntax node) => node switch {ParenthesizedLambdaExpressionSyntax parenthesizedLambda => parenthesizedLambda.ParameterList,
SimpleLambdaExpressionSyntax simpleLambda => SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(simpleLambda.Parameter)),
_ => throw new ArgumentOutOfRangeException(nameof(node), node, null)
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -15,7 +14,7 @@ namespace Stryker.Core.Mutants.CsharpNodeOrchestrators;
/// </summary>
/// <typeparam name="T">SyntaxNode type</typeparam>
/// <remarks>This class is helpful because there is no (useful) shared parent class for those syntax construct</remarks>
internal abstract class BaseFunctionOrchestrator<T> : NodeSpecificOrchestrator<T, T>, IInstrumentCode where T : SyntaxNode
internal abstract class BaseFunctionOrchestrator<T> :MemberDefinitionOrchestrator<T>, IInstrumentCode where T : SyntaxNode
{
protected BaseFunctionOrchestrator() => Marker = MutantPlacer.RegisterEngine(this, true);

Expand All @@ -24,20 +23,19 @@ internal abstract class BaseFunctionOrchestrator<T> : NodeSpecificOrchestrator<T
/// <inheritdoc/>
public string InstrumentEngineId => GetType().Name;

/// <inheritdoc/>
protected override MutationContext PrepareContext(T node, MutationContext context) => base.PrepareContext(node, context.Enter(MutationControl.Member));

/// <inheritdoc/>
protected override void RestoreContext(MutationContext context) => base.RestoreContext(context.Leave());

/// <summary>
/// Get the function body (block or expression)
/// </summary>
/// <param name="node"></param>
/// <returns>a tuple with the block body as first item and the expression body as the second. At least one of them is expected to be null.</returns>
protected abstract (BlockSyntax block, ExpressionSyntax expression) GetBodies(T node);

/// <summary>
/// Gets the parameter list of the function
/// </summary>
/// <param name="node">instance of <see cref="T"/></param>
/// <returns>a parameter list</returns>
protected abstract ParameterListSyntax Parameters(T node);
protected abstract ParameterListSyntax ParameterList(T node);

/// <summary>
/// Get the return type
Expand All @@ -51,6 +49,7 @@ internal abstract class BaseFunctionOrchestrator<T> : NodeSpecificOrchestrator<T
/// </summary>
/// <param name="node">instance of <see cref="T"/></param>
/// <param name="blockBody">desired body</param>
/// <param name="expressionBody">desired expression body</param>
/// <returns>an instance of <typeparamref name="T"/> with <paramref name="blockBody"/> body</returns>
protected abstract T SwitchToThisBodies(T node, BlockSyntax blockBody, ExpressionSyntax expressionBody);

Expand All @@ -76,6 +75,7 @@ protected T ConvertToBlockBody(T node, TypeSyntax returnType)
var blockBody = GenerateBlockBody(expression, returnType);
return SwitchToThisBodies(node, blockBody, null).WithAdditionalAnnotations(Marker);
}

/// <inheritdoc/>
public SyntaxNode RemoveInstrumentation(SyntaxNode node)
{
Expand Down Expand Up @@ -105,7 +105,7 @@ protected override T InjectMutations(T sourceNode, T targetNode, SemanticModel s
}
var wasInExpressionForm = GetBodies(sourceNode).expression != null;
var returnType = ReturnType(sourceNode);
var parameters = Parameters(sourceNode).Parameters;
var parameters = ParameterList(sourceNode).Parameters;

// no mutations to inject
if (!context.HasLeftOverMutations)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal class BaseMethodDeclarationOrchestrator<T> : BaseFunctionOrchestrator<T
{
protected override (BlockSyntax block, ExpressionSyntax expression) GetBodies(T node) => (node.Body, node.ExpressionBody?.Expression);

protected override ParameterListSyntax Parameters(T node) => node.ParameterList;
protected override ParameterListSyntax ParameterList(T node) => node.ParameterList;

protected override TypeSyntax ReturnType(T node)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

namespace Stryker.Core.Mutants.CsharpNodeOrchestrators;

internal abstract class NodeOrchestratorBase
internal static class CommentParser
{
private static readonly Regex _pattern = new("^\\s*\\/\\/\\s*Stryker", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(20));
private static readonly Regex _parser = new("^\\s*\\/\\/\\s*Stryker\\s*(disable|restore)\\s*(once|)\\s*([^:]*)\\s*:?(.*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(20));
private static readonly ILogger _logger = ApplicationLogging.LoggerFactory.CreateLogger<NodeOrchestratorBase>();
private static readonly Regex Pattern = new("^\\s*\\/\\/\\s*Stryker", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(20));
private static readonly Regex Parser = new("^\\s*\\/\\/\\s*Stryker\\s*(disable|restore)\\s*(once|)\\s*([^:]*)\\s*:?(.*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(20));
private static readonly ILogger Logger = ApplicationLogging.LoggerFactory.CreateLogger("CommentParser");

public static MutationContext ParseStrykerComment(MutationContext context, Match match, SyntaxNode node)
private static MutationContext ParseStrykerComment(MutationContext context, Match match, SyntaxNode node)
{
const int ModeGroup = 1;
const int OnceGroup = 2;
Expand All @@ -34,7 +34,7 @@ public static MutationContext ParseStrykerComment(MutationContext context, Match
"disable" => true,
_ => false,
};

Mutator[] filteredMutators;
if (match.Groups[MutatorsGroup].Value.ToLower().Trim() == "all")
{
Expand All @@ -52,7 +52,7 @@ public static MutationContext ParseStrykerComment(MutationContext context, Match
}
else
{
_logger.LogError(
Logger.LogError(
$"{labels[i]} not recognized as a mutator at {node.GetLocation().GetMappedLineSpan().StartLinePosition}, {node.SyntaxTree.FilePath}. Legal values are {string.Join(',', Enum.GetValues<Mutator>())}.");
}
}
Expand All @@ -61,27 +61,27 @@ public static MutationContext ParseStrykerComment(MutationContext context, Match
return context.FilterMutators(disable, filteredMutators, match.Groups[OnceGroup].Value.ToLower() == "once", comment);
}

protected static MutationContext ParseNodeComments(SyntaxNode node, MutationContext context)
public static MutationContext ParseNodeComments(SyntaxNode node, MutationContext context)
{
foreach (var commentTrivia in node.GetLeadingTrivia()
.Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia) ||
t.IsKind(SyntaxKind.MultiLineCommentTrivia)).Select(t => t.ToString()))
{
// perform a quick pattern check to see if it is a 'Stryker comment'
if (!_pattern.Match(commentTrivia).Success)
if (!Pattern.Match(commentTrivia).Success)
{
continue;
}

var match = _parser.Match(commentTrivia);
var match = Parser.Match(commentTrivia);
if (match.Success)
{
// this is a Stryker comments, now we parse it
context = ParseStrykerComment(context, match, node);
break;
}

_logger.LogWarning(
Logger.LogWarning(
$"Invalid Stryker comments at {node.GetLocation().GetMappedLineSpan().StartLinePosition}, {node.SyntaxTree.FilePath}.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ namespace Stryker.Core.Mutants.CsharpNodeOrchestrators;

internal class ExpressionBodiedPropertyOrchestrator : BaseFunctionOrchestrator<PropertyDeclarationSyntax>
{
protected override bool CanHandle(PropertyDeclarationSyntax t) => t.ExpressionBody!= null || t.Initializer!=null;
protected override bool CanHandle(PropertyDeclarationSyntax t) => t.ExpressionBody!= null || (t.Initializer!=null && t.IsStatic());

protected override (BlockSyntax block, ExpressionSyntax expression) GetBodies(PropertyDeclarationSyntax node) => (node.GetAccessor()?.Body, node.ExpressionBody?.Expression);

protected override ParameterListSyntax Parameters(PropertyDeclarationSyntax node) => SyntaxFactory.ParameterList();
protected override ParameterListSyntax ParameterList(PropertyDeclarationSyntax node) => SyntaxFactory.ParameterList();

protected override TypeSyntax ReturnType(PropertyDeclarationSyntax node) => node.Type;

Expand All @@ -31,18 +31,17 @@ protected override PropertyDeclarationSyntax SwitchToThisBodies(PropertyDeclarat

protected override PropertyDeclarationSyntax OrchestrateChildrenMutation(PropertyDeclarationSyntax node, SemanticModel semanticModel, MutationContext context)
{
if (!node.IsStatic())
if (node.Initializer == null)
{
return base.OrchestrateChildrenMutation(node, semanticModel, context);
}

var children = node.ReplaceNodes(node.ChildNodes(), (original, _) =>
MutateSingleNode(original, semanticModel, original == node.Initializer ? context.EnterStatic() : context));
if (children.Initializer != null)
{
children = children.WithInitializer(children.Initializer.WithValue(context.PlaceStaticContextMarker(children.Initializer.Value)));
}
return children;
var context1 = original == node.Initializer ? context.EnterStatic() : context;
return context1.FindHandler(original).Mutate(original, semanticModel, context1);
});
return children.WithInitializer(children.Initializer.WithValue(context.PlaceStaticContextMarker(children.Initializer.Value)));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Stryker.Core.Mutants.CsharpNodeOrchestrators;

internal interface INodeMutator : ITypeHandler<SyntaxNode>
internal interface INodeOrchestrator : ITypeHandler<SyntaxNode>
{
SyntaxNode Mutate(SyntaxNode node, SemanticModel semanticModel, MutationContext context);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ protected override MutationContext StoreMutations(InvocationExpressionSyntax nod
IEnumerable<Mutant> mutations,
MutationContext context) =>
// if the expression contains a declaration, it must be controlled at the block level.
context.AddMutations(mutations, node.ContainsDeclarations() ? MutationControl.Block : MutationControl.Expression);
context.AddMutations(mutations, node.ArgumentList.ContainsDeclarations() ? MutationControl.Block : MutationControl.Expression);

protected override MutationContext PrepareContext(InvocationExpressionSyntax node, MutationContext context) =>
// invocation with a member binding expression must be controlled at a higher expression level
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal class LocalFunctionStatementOrchestrator : BaseFunctionOrchestrator<Loc
{
protected override (BlockSyntax block, ExpressionSyntax expression) GetBodies(LocalFunctionStatementSyntax node) => (node.Body, node.ExpressionBody?.Expression);

protected override ParameterListSyntax Parameters(LocalFunctionStatementSyntax node) => node.ParameterList;
protected override ParameterListSyntax ParameterList(LocalFunctionStatementSyntax node) => node.ParameterList;

protected override TypeSyntax ReturnType(LocalFunctionStatementSyntax node)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Stryker.Core.Mutants.CsharpNodeOrchestrators;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.CodeAnalysis;

namespace Stryker.Core.Mutants.CsharpNodeOrchestrators;

/// <summary>
/// Base class for node types (and their children) that are member definitions
/// </summary>
/// <typeparam name="T">Syntax node type. (not restricted to MemberDefinitionSyntax)</typeparam>
internal class MemberDefinitionOrchestrator<T>:NodeSpecificOrchestrator<T, T> where T : SyntaxNode
{
protected override MutationContext PrepareContext(T node, MutationContext context) => base.PrepareContext(node, context.Enter(MutationControl.Member));

protected override void RestoreContext(MutationContext context) => base.RestoreContext(context.Leave());
}
Loading

0 comments on commit 014af1f

Please sign in to comment.