diff --git a/docs/mutations.md b/docs/mutations.md index 4f20d17aaa..9fda87f165 100644 --- a/docs/mutations.md +++ b/docs/mutations.md @@ -157,7 +157,9 @@ Do you have a suggestion for a (new) mutator? Feel free to create an [issue](htt | Original | Mutated | | ------------- | ------------- | | `"foo"` | `""` | +| `"foo"u8` | `""u8` | | `""` | `"Stryker was here!"` | +| `""u8` | `"Stryker was here!"u8` | | `$"foo {bar}"` | `$""` | | `@"foo"` | `@""` | | `string.Empty` | `"Stryker was here!"` | diff --git a/integrationtest/TargetProjects/NetCoreTestProject.XUnit/NetCoreTestProject.XUnit.csproj b/integrationtest/TargetProjects/NetCoreTestProject.XUnit/NetCoreTestProject.XUnit.csproj index 42ccc73ae5..ec65ba0feb 100644 --- a/integrationtest/TargetProjects/NetCoreTestProject.XUnit/NetCoreTestProject.XUnit.csproj +++ b/integrationtest/TargetProjects/NetCoreTestProject.XUnit/NetCoreTestProject.XUnit.csproj @@ -2,6 +2,7 @@ net6.0 + latest diff --git a/integrationtest/TargetProjects/NetCoreTestProject.XUnit/String/Utf8StringMagicTests.cs b/integrationtest/TargetProjects/NetCoreTestProject.XUnit/String/Utf8StringMagicTests.cs new file mode 100644 index 0000000000..17c4ab4e27 --- /dev/null +++ b/integrationtest/TargetProjects/NetCoreTestProject.XUnit/String/Utf8StringMagicTests.cs @@ -0,0 +1,50 @@ +using ExampleProject.String; +using Xunit; + +namespace ExampleProject.XUnit.String +{ + public class Utf8StringMagicTests + { + [Fact] + public void AddTwoStrings() + { + var sut = new Utf8StringMagic(); + var actual = sut.HelloWorld(); + Assert.Equal("Hello World!"u8, actual); + } + + [Fact] + public void IsNullOrEmpty() + { + var sut = new Utf8StringMagic(); + var actual = sut.IsNullOrEmpty("hello"u8); + Assert.False(actual); + } + + [Fact] + public void IsNullOrEmpty_Empty() + { + var sut = new Utf8StringMagic(); + var actual = sut.IsNullOrEmpty(""u8); + Assert.True(actual); + } + + [Fact] + public void Referenced() + { + var sut = new Utf8StringMagic(); + var input = "hello"u8; + sut.Referenced(out input); + Assert.Equal("world"u8, input); + } + + [Fact] + public void ReferencedEmpty() + { + var sut = new Utf8StringMagic(); + var input = "hello"u8; + sut.ReferencedEmpty(out input); + Assert.Equal(""u8, input); + } + } +} diff --git a/integrationtest/TargetProjects/TargetProject/String/Utf8StringMagic.cs b/integrationtest/TargetProjects/TargetProject/String/Utf8StringMagic.cs new file mode 100644 index 0000000000..de92605da3 --- /dev/null +++ b/integrationtest/TargetProjects/TargetProject/String/Utf8StringMagic.cs @@ -0,0 +1,32 @@ +using System; +using System.Linq; + +namespace ExampleProject.String +{ + public class Utf8StringMagic + { + public ReadOnlySpan HelloWorld() + { + return "Hello"u8 + " "u8 + "World!"u8; + } + + public void Referenced(out ReadOnlySpan test) + { + test = "world"u8; + } + + public void ReferencedEmpty(out ReadOnlySpan test) + { + test = ""u8; + } + + public bool IsNullOrEmpty(ReadOnlySpan myString) + { + if (myString.IsEmpty) + { + return true; + } + return false; + } + } +} diff --git a/integrationtest/ValidationProject/ValidateStrykerResults.cs b/integrationtest/ValidationProject/ValidateStrykerResults.cs index 2bc35f71b3..4ea4d038d5 100644 --- a/integrationtest/ValidationProject/ValidateStrykerResults.cs +++ b/integrationtest/ValidationProject/ValidateStrykerResults.cs @@ -82,8 +82,8 @@ public void NetCore() var report = JsonConvert.DeserializeObject(strykerRunOutput); - CheckReportMutants(report, total: 114, ignored: 55, survived: 4, killed: 6, timeout: 2, nocoverage: 45); - CheckReportTestCounts(report, total: 14); + CheckReportMutants(report, total: 129, ignored: 59, survived: 5, killed: 10, timeout: 2, nocoverage: 45); + CheckReportTestCounts(report, total: 19); } [Fact] @@ -121,8 +121,8 @@ public void NetCoreWithTwoTestProjects() var report = JsonConvert.DeserializeObject(strykerRunOutput); - CheckReportMutants(report, total: 114, ignored: 27, survived: 8, killed: 8, timeout: 2, nocoverage: 67); - CheckReportTestCounts(report, total: 30); + CheckReportMutants(report, total: 129, ignored: 31, survived: 9, killed: 12, timeout: 2, nocoverage: 67); + CheckReportTestCounts(report, total: 35); } [Fact] @@ -140,8 +140,8 @@ public void SolutionRun() var report = JsonConvert.DeserializeObject(strykerRunOutput); - CheckReportMutants(report, total: 114, ignored: 55, survived: 4, killed: 6, timeout: 2, nocoverage: 45); - CheckReportTestCounts(report, total: 30); + CheckReportMutants(report, total: 129, ignored: 59, survived: 5, killed: 10, timeout: 2, nocoverage: 45); + CheckReportTestCounts(report, total: 35); } private void CheckMutationKindsValidity(JsonReport report) diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/StringMutatorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/StringMutatorTests.cs index 39cf4f90d9..77b400095c 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/StringMutatorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/StringMutatorTests.cs @@ -16,6 +16,25 @@ public void ShouldBeMutationLevelStandard() target.MutationLevel.ShouldBe(MutationLevel.Standard); } + [Theory] + [InlineData(@"""""u8", @"""Stryker was here!""u8")] + [InlineData(@"""foo""u8", @"""""u8")] + public void ShouldMutateUtf8StringLiteral(string original, string expected) + { + var syntaxTree = CSharpSyntaxTree.ParseText($"var test = {original};"); + + var literalExpression = syntaxTree.GetRoot().DescendantNodes().OfType().First(); + var mutator = new StringMutator(); + + var result = mutator.ApplyMutations(literalExpression, null).ToList(); + + var mutation = result.ShouldHaveSingleItem(); + + mutation.ReplacementNode.ShouldBeOfType() + .Token.Text.ShouldBe(expected); + mutation.DisplayName.ShouldBe("String mutation"); + } + [Theory] [InlineData("", "Stryker was here!")] [InlineData("foo", "")] diff --git a/src/Stryker.Core/Stryker.Core/Mutators/StringMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/StringMutator.cs index f2fe819e25..0043e42f73 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/StringMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/StringMutator.cs @@ -17,24 +17,53 @@ public override IEnumerable ApplyMutations(LiteralExpressionSyntax nod // Get objectCreationSyntax to check if it contains a regex type. var root = node.Parent?.Parent?.Parent; - if (!IsSpecialType(root) && IsStringLiteral(node)) + if (IsSpecialType(root)) { - var currentValue = (string)node.Token.Value; - var replacementValue = currentValue == "" ? "Stryker was here!" : ""; - yield return new Mutation - { - OriginalNode = node, - ReplacementNode = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(replacementValue)), - DisplayName = "String mutation", - Type = Mutator.String - }; + yield break; } + + SyntaxNode syntaxNode; + string currentValue; + string replacementValue; + + if (IsStringLiteral(node)) + { + currentValue = (string)node.Token.Value; + replacementValue = currentValue == "" ? "Stryker was here!" : ""; + syntaxNode = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, + SyntaxFactory.Literal(replacementValue)); + } + else if (IsUtf8StringLiteral(node)) + { + currentValue = (string)node.Token.Value; + replacementValue = currentValue == "" ? "Stryker was here!" : ""; + syntaxNode = CreateUtf88String(node.GetLeadingTrivia(), replacementValue, node.GetTrailingTrivia()); + } + else + { + yield break; + } + + yield return new Mutation + { + OriginalNode = node, + ReplacementNode = syntaxNode, + DisplayName = "String mutation", + Type = Mutator.String + }; + } + + private static bool IsUtf8StringLiteral(LiteralExpressionSyntax node) + { + var kind = node.Kind(); + return kind is SyntaxKind.Utf8StringLiteralExpression + && node.Parent is not ConstantPatternSyntax; } private static bool IsStringLiteral(LiteralExpressionSyntax node) { var kind = node.Kind(); - return kind == SyntaxKind.StringLiteralExpression + return kind is SyntaxKind.StringLiteralExpression && node.Parent is not ConstantPatternSyntax; } @@ -49,4 +78,19 @@ private static bool IsCtorOfType(ObjectCreationExpressionSyntax ctor, Type type) var ctorType = ctor.Type.ToString(); return ctorType == type.Name || ctorType == type.FullName; } + + private static LiteralExpressionSyntax CreateUtf88String(SyntaxTriviaList leadingTrivia, string stringValue, SyntaxTriviaList trailingTrivia) + { + var quoteCharacter = '"'; + var suffix = "u8"; + + var literal = SyntaxFactory.Token( + leading: leadingTrivia, + kind: SyntaxKind.Utf8StringLiteralToken, + text: quoteCharacter + stringValue + quoteCharacter + suffix, + valueText: "", + trailing: trailingTrivia); + + return SyntaxFactory.LiteralExpression(SyntaxKind.Utf8StringLiteralExpression, literal); + } }