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): Improve and unify string and regex mutations #3107

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions docs/mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ Do you have a suggestion for a (new) mutator? Feel free to create an [issue](htt
| ------------- | ------------- |
| `"foo"` | `""` |
| `""` | `"Stryker was here!"` |
| `"foo"u8` | `""u8` |
| `""u8` | `"Stryker was here!"u8` |
| `$"foo {bar}"` | `$""` |
| `@"foo"` | `@""` |
| `string.Empty` | `"Stryker was here!"` |
Expand Down
3 changes: 2 additions & 1 deletion docs/regex-mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ custom_edit_url: https://github.com/stryker-mutator/stryker-net/edit/master/docs
---

Stryker supports a variety of regular expression mutators, which are listed below. Do you have a suggestion for a (new) mutator? Feel free to create an [issue](https://github.com/stryker-mutator/stryker-net/issues)!
Regex mutations are applied to arguments, fields, and properties that are annotated with `[StringSyntax(StringSyntaxAttribute.Regex)]` on NET7+, and just the Regex constructor on prior targets.

## Common tokens
| Original | Mutated |
Expand Down Expand Up @@ -160,4 +161,4 @@ Change a normal group to a non-capturing group.

| Original | Mutated |
|----------|-----------|
| `(abc)` | `(?:abc)` |
| `(abc)` | `(?:abc)` |
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 JsonReportSerialization.DeserializeJsonReportAsync(strykerRunOutput);

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

Expand Down Expand Up @@ -122,7 +122,7 @@ public async Task CSharp_NetCore_WithTwoTestProjects()

var report = await JsonReportSerialization.DeserializeJsonReportAsync(strykerRunOutput);

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

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

var report = await JsonReportSerialization.DeserializeJsonReportAsync(strykerRunOutput);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1602,7 +1602,7 @@ int TestMethod()
}

[TestMethod]
public void ShouldMutatetringsInSwitchExpression()
public void ShouldMutateStringsInSwitchExpression()
{
var source = @"string TestMethod()
{
Expand Down
102 changes: 0 additions & 102 deletions src/Stryker.Core/Stryker.Core.UnitTest/Mutators/RegexMutatorTest.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using Stryker.Abstractions.Mutators;
using Stryker.Core.Mutators;

namespace Stryker.Core.UnitTest.Mutators.Strings;

[TestClass]
public class RegexMutatorTest : TestBase
{

private static LiteralExpressionSyntax ParseExpression(string text)
{
var objectCreationExpression = SyntaxFactory.ParseExpression(text) as ObjectCreationExpressionSyntax;
return objectCreationExpression?.DescendantNodesAndSelf().OfType<LiteralExpressionSyntax>().FirstOrDefault();
}

[TestMethod]
public void ShouldBeMutationLevelAdvanced()
{
var target = new StringMutator();
target.RegexMutationLevel.ShouldBe(MutationLevel.Advanced);
}

[TestMethod]
[DataRow(MutationLevel.Basic)]
[DataRow(MutationLevel.Standard)]
public void ShouldNotMutateWithLowerMutationLevel(MutationLevel level)
{
var literalExpression = ParseExpression("new Regex(@\"^abc\")");
var target = new StringMutator();

var result = target.ApplyMutations(literalExpression, null, level);

result.ShouldBeEmpty();
}

[TestMethod]
public void ShouldMutateStringLiteralInRegexConstructor()
{
var literalExpression = ParseExpression("new Regex(@\"^abc\")");
var target = new StringMutator();

var result = target.ApplyMutations(literalExpression, null, MutationLevel.Advanced);

var mutation = result.ShouldHaveSingleItem();

mutation.DisplayName.ShouldBe("Regex anchor removal mutation");
var replacement = mutation.ReplacementNode.ShouldBeOfType<LiteralExpressionSyntax>();
replacement.Token.ValueText.ShouldBe("abc");
}

[TestMethod]
public void ShouldMutateStringLiteralInRegexConstructorWithFullName()
{
var literalExpression = ParseExpression("new System.Text.RegularExpressions.Regex(@\"^abc\")");
var target = new StringMutator();

var result = target.ApplyMutations(literalExpression, null, MutationLevel.Advanced);

var mutation = result.ShouldHaveSingleItem();

mutation.DisplayName.ShouldBe("Regex anchor removal mutation");
var replacement = mutation.ReplacementNode.ShouldBeOfType<LiteralExpressionSyntax>();
replacement.Token.ValueText.ShouldBe("abc");
}

[TestMethod]
public void ShouldNotMutateStringLiteralInOtherConstructor()
{
var literalExpression = ParseExpression("new Other(@\"^abc\")");
var target = new StringMutator();
var result = target.ApplyMutations(literalExpression, null, MutationLevel.Advanced);

result.Where(a => a.Type == Mutator.Regex).ShouldBeEmpty();
}

[TestMethod]
public void ShouldNotMutateAtLowerMutationLevel()
{
var literalExpression = ParseExpression("new Other(@\"^abc\")");
var target = new StringMutator();
var result = target.ApplyMutations(literalExpression, null, MutationLevel.Standard);

result.Where(a => a.Type == Mutator.Regex).ShouldBeEmpty();
}

[TestMethod]
public void ShouldMutateStringLiteralMultipleTimes()
{
var literalExpression = ParseExpression("new Regex(@\"^abc$\")");
var target = new StringMutator();

var result = target.ApplyMutations(literalExpression, null, MutationLevel.Advanced);

result.Count().ShouldBe(2);
result.ShouldAllBe(mutant => mutant.DisplayName == "Regex anchor removal mutation");
var first = result.First().ReplacementNode.ShouldBeOfType<LiteralExpressionSyntax>();
var last = result.Last().ReplacementNode.ShouldBeOfType<LiteralExpressionSyntax>();
first.Token.ValueText.ShouldBe("abc$");
last.Token.ValueText.ShouldBe("^abc");
}

[TestMethod]
public void ShouldMutateStringLiteralAsNamedArgumentPatternInRegexConstructor()
{
var literalExpression = ParseExpression("new Regex(options: RegexOptions.None, pattern: @\"^abc\")");
var target = new StringMutator();

var result = target.ApplyMutations(literalExpression, null, MutationLevel.Advanced);

var mutation = result.ShouldHaveSingleItem();

mutation.DisplayName.ShouldBe("Regex anchor removal mutation");
var replacement = mutation.ReplacementNode.ShouldBeOfType<LiteralExpressionSyntax>();
replacement.Token.ValueText.ShouldBe("abc");
}
}
Loading
Loading