From 6556ab96d320fc2b7647363577a62683df48b6d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Isalski?= Date: Fri, 7 Jun 2024 17:23:23 +0200 Subject: [PATCH] feat(mutator): Add conditional operator mutator (#2583) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(Mutators): Add conditional operator mutator Add conditional operator mutator Add tests for conditional operator mutator Include conditional operator mutator in mutations.md * Fixes to unit tests * feat(Mutators): Remove NegateConditionMutator's conditional expression mutation * Fix integration tests * Fix last integration test * ConditionalExpressionMutator should not mutate declaration patterns. Fix tests. * Fix ValidateStrykerResults compilation. --------- Co-authored-by: MichaƂ Isalski Co-authored-by: Liam Rougoor Co-authored-by: Liam Rougoor <35850587+Liam-Rougoor@users.noreply.github.com> Co-authored-by: Rouke Broersma --- docs/mutations.md | 6 ++ .../ValidateStrykerResults.cs | 10 +- .../Mutants/CsharpMutantOrchestratorTests.cs | 95 ++++++++++--------- .../ConditionalExpressionMutatorTests.cs | 70 ++++++++++++++ .../Mutators/NegateConditionMutatorTests.cs | 1 - .../Inputs/IgnoreMutationsInputTests.cs | 2 +- .../Mutants/CsharpMutantOrchestrator.cs | 1 + .../Mutators/ConditionalExpressionMutator.cs | 51 ++++++++++ .../Stryker.Core/Mutators/Mutator.cs | 4 +- .../Mutators/NegateConditionMutator.cs | 6 -- 10 files changed, 185 insertions(+), 61 deletions(-) create mode 100644 src/Stryker.Core/Stryker.Core.UnitTest/Mutators/ConditionalExpressionMutatorTests.cs create mode 100644 src/Stryker.Core/Stryker.Core/Mutators/ConditionalExpressionMutator.cs diff --git a/docs/mutations.md b/docs/mutations.md index 3e630b276a..f213a89d76 100644 --- a/docs/mutations.md +++ b/docs/mutations.md @@ -256,3 +256,9 @@ For the full list of all available regex mutations, see the [regex mutator docs] | `a ?? b` | `b ?? a` | | `a ?? b` | `a` | | `a ?? b` | `b` | + +## Conditional Operators (_conditional_) +| Original | Mutated | +|---------------------|---------------------| +| `x ? a : b` | `true ? a : b` | +| `x ? a : b` | `false ? a : b` | diff --git a/integrationtest/ValidationProject/ValidateStrykerResults.cs b/integrationtest/ValidationProject/ValidateStrykerResults.cs index b93d3a5dd3..3102ac83e7 100644 --- a/integrationtest/ValidationProject/ValidateStrykerResults.cs +++ b/integrationtest/ValidationProject/ValidateStrykerResults.cs @@ -2,14 +2,12 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; using Newtonsoft.Json; using Shouldly; using Stryker.Core.Mutants; using Stryker.Core.Reporters.Json; -using Stryker.CLI; using Xunit; namespace IntegrationTests @@ -63,7 +61,7 @@ public void NetFullFramework() var report = JsonConvert.DeserializeObject(strykerRunOutput); - CheckReportMutants(report, total: 28, ignored: 7, survived: 2, killed: 7, timeout: 0, nocoverage: 11); + CheckReportMutants(report, total: 29, ignored: 7, survived: 3, killed: 7, timeout: 0, nocoverage: 11); } } @@ -82,7 +80,7 @@ public void NetCore() var report = JsonConvert.DeserializeObject(strykerRunOutput); - CheckReportMutants(report, total: 117, ignored: 57, survived: 4, killed: 7, timeout: 2, nocoverage: 45); + CheckReportMutants(report, total: 120, ignored: 58, survived: 4, killed: 8, timeout: 2, nocoverage: 46); CheckReportTestCounts(report, total: 16); } @@ -121,7 +119,7 @@ public void NetCoreWithTwoTestProjects() var report = JsonConvert.DeserializeObject(strykerRunOutput); - CheckReportMutants(report, total: 117, ignored: 28, survived: 8, killed: 10, timeout: 2, nocoverage: 67); + CheckReportMutants(report, total: 120, ignored: 29, survived: 8, killed: 11, timeout: 2, nocoverage: 68); CheckReportTestCounts(report, total: 32); } @@ -140,7 +138,7 @@ public void SolutionRun() var report = JsonConvert.DeserializeObject(strykerRunOutput); - CheckReportMutants(report, total: 117, ignored: 57, survived: 4, killed: 7, timeout: 2, nocoverage: 45); + CheckReportMutants(report, total: 120, ignored: 58, survived: 4, killed: 8, timeout: 2, nocoverage: 46); CheckReportTestCounts(report, total: 32); } diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/CsharpMutantOrchestratorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/CsharpMutantOrchestratorTests.cs index 7c4147bd49..f55e4f0c63 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/CsharpMutantOrchestratorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutants/CsharpMutantOrchestratorTests.cs @@ -127,7 +127,7 @@ public void ShouldAddReturnDefaultToArrowExpressionOperator() string source = @"public static int operator+ (TestClass value, TestClass other) => Sub(out var x, """")?1:2;"; string expected = - @"public static int operator+ (TestClass value, TestClass other) {if(StrykerNamespace.MutantControl.IsActive(0)){return!(Sub(out var x, """"))?1:2;}else{return Sub(out var x, (StrykerNamespace.MutantControl.IsActive(1)?""Stryker was here!"":""""))?1:2;}}"; + @"public static int operator+ (TestClass value, TestClass other) {if(StrykerNamespace.MutantControl.IsActive(1)){return(false?1:2);}else{if(StrykerNamespace.MutantControl.IsActive(0)){return(true?1:2);}else{return Sub(out var x, (StrykerNamespace.MutantControl.IsActive(2)?""Stryker was here!"":""""))?1:2;}}}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -156,8 +156,8 @@ public void ShouldMutateExpressionBodiedLocalFunction() int SomeMethod() => (true && SomeOtherMethod(out var x)) ? x : 5; }"; string expected = @"void TestMethod(){if(StrykerNamespace.MutantControl.IsActive(0)){}else{ -int SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(1)){return!((true && SomeOtherMethod(out var x)) )? x : 5;}else{if(StrykerNamespace.MutantControl.IsActive(2)){return(true || SomeOtherMethod(out var x)) ? x : 5;}else{return ((StrykerNamespace.MutantControl.IsActive(3)?false:true )&& SomeOtherMethod(out var x)) ? x : 5;}}};}} -}"; +int SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(2)){return(false?x :5);}else{if(StrykerNamespace.MutantControl.IsActive(1)){return(true?x :5);}else{if(StrykerNamespace.MutantControl.IsActive(3)){return(true || SomeOtherMethod(out var x)) ? x : 5;}else{return((StrykerNamespace.MutantControl.IsActive(4)?false:true )&& SomeOtherMethod(out var x)) ? x : 5;}}}}; +}}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -355,28 +355,22 @@ private bool Out(out string test) return true; }"; string expected = @"void TestMethod() -{if(StrykerNamespace.MutantControl.IsActive(0)){}else{if(StrykerNamespace.MutantControl.IsActive(1)){ +{if(StrykerNamespace.MutantControl.IsActive(0)){}else{if(StrykerNamespace.MutantControl.IsActive(2)){ int i = 0; - var result = !(Out(out var test) )? test : """"; + var result = (false?test :""""); } -else{ +else{if(StrykerNamespace.MutantControl.IsActive(1)){ int i = 0; - var result = Out(out var test) ? test : (StrykerNamespace.MutantControl.IsActive(2)?""Stryker was here!"":""""); -} - } + var result = (true?test :""""); } -private bool Out(out string test) -{ - { test = default(string); } - if (StrykerNamespace.MutantControl.IsActive(3)) - { } - else - { - return (StrykerNamespace.MutantControl.IsActive(4) ? false : true); - } - return default(bool); +else{ + int i = 0; + var result = Out(out var test) ? test : (StrykerNamespace.MutantControl.IsActive(3)?""Stryker was here!"":""""); } -"; +}}}private bool Out(out string test) +{{test= default(string);}if(StrykerNamespace.MutantControl.IsActive(4)){}else{ + return (StrykerNamespace.MutantControl.IsActive(5)?false:true); +}return default(bool);}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -397,19 +391,13 @@ private bool Out(int test, Funclambda ) string expected = @"void TestMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ int i = 0; - var result = (StrykerNamespace.MutantControl.IsActive(1)?!(Out(i, (x) => { int.TryParse(""3"", out int y); return x == y;} ) ):Out(i, (x) => {if(StrykerNamespace.MutantControl.IsActive(2)){}else{ int.TryParse((StrykerNamespace.MutantControl.IsActive(3)?"""":""3""), out int y); return (StrykerNamespace.MutantControl.IsActive(4)?x != y:x == y);} return default;}) )? i.ToString() : (StrykerNamespace.MutantControl.IsActive(5)?""Stryker was here!"":""""); + var result = (StrykerNamespace.MutantControl.IsActive(2)?(false?i.ToString() :""""):(StrykerNamespace.MutantControl.IsActive(1)?(true?i.ToString() :""""):Out(i, (x) => {if(StrykerNamespace.MutantControl.IsActive(3)){}else{ int.TryParse((StrykerNamespace.MutantControl.IsActive(4)?"""":""3""), out int y); return (StrykerNamespace.MutantControl.IsActive(5)?x != y:x == y);} return default;}) ? i.ToString() : (StrykerNamespace.MutantControl.IsActive(6)?""Stryker was here!"":""""))); } - } - private bool Out(int test, Func lambda) - { - if (StrykerNamespace.MutantControl.IsActive(6)) - { } - else - { - return (StrykerNamespace.MutantControl.IsActive(7) ? false : true); - } - return default(bool); - }"; +}private bool Out(int test, Funclambda ) +{if(StrykerNamespace.MutantControl.IsActive(7)){}else{ + return (StrykerNamespace.MutantControl.IsActive(8)?false:true); +} +return default(bool);}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -426,9 +414,9 @@ public void ShouldMutateWhenDeclarationInInnerScopeInExpressionForm() var expected = @"void TestMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){}else{ int i = 0; - var result = Out(i, (x) => {if(StrykerNamespace.MutantControl.IsActive(1)){return!(int.TryParse(""3"", out int y) )? true : false;}else{return int.TryParse((StrykerNamespace.MutantControl.IsActive(2)?"""":""3""), out int y) ? (StrykerNamespace.MutantControl.IsActive(3)?false:true ): (StrykerNamespace.MutantControl.IsActive(4)?true:false);}}); -}} -"; + var result = Out(i, (x) => (StrykerNamespace.MutantControl.IsActive(2)?(false?true :false):(StrykerNamespace.MutantControl.IsActive(1)?(true?true :false):int.TryParse((StrykerNamespace.MutantControl.IsActive(3)?"""":""3""), out int y) ? (StrykerNamespace.MutantControl.IsActive(4)?false:true ): (StrykerNamespace.MutantControl.IsActive(5)?true:false)))); +} +}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -494,7 +482,7 @@ string SomeLocalFunction() ShouldMutateSourceInClassToExpected(source, expected); } - + [Fact] public void ShouldMutateConditionalMemberAccessProperly() { @@ -509,7 +497,7 @@ public void ShouldMutateConditionalMemberAccessProperly() ShouldMutateSourceInClassToExpected(source, expected); } - + [Fact] public void ShouldMutateConditionalExpressionOnArrayDeclaration() @@ -742,7 +730,7 @@ public void ShouldMutateComplexExpressionBodiedMethod() { string source = @"public int SomeMethod() => (true && SomeOtherMethod(out var x)) ? x : 5;"; string expected = - @"public int SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(0)){return!((true && SomeOtherMethod(out var x)) )? x : 5;}else{if(StrykerNamespace.MutantControl.IsActive(1)){return(true || SomeOtherMethod(out var x)) ? x : 5;}else{return ((StrykerNamespace.MutantControl.IsActive(2)?false:true )&& SomeOtherMethod(out var x)) ? x : 5;}}}"; + @"public int SomeMethod() {if(StrykerNamespace.MutantControl.IsActive(1)){return(false?x :5);}else{if(StrykerNamespace.MutantControl.IsActive(0)){return(true?x :5);}else{if(StrykerNamespace.MutantControl.IsActive(2)){return(true || SomeOtherMethod(out var x)) ? x : 5;}else{return ((StrykerNamespace.MutantControl.IsActive(3)?false:true )&& SomeOtherMethod(out var x)) ? x : 5;}}}}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -750,9 +738,9 @@ public void ShouldMutateComplexExpressionBodiedMethod() [Fact] public void ShouldMutateExpressionBodiedStaticConstructor() { - string source = @"static Test() => (true && SomeOtherMethod(out var x)) ? x : 5;"; + string source = @"static Test() => (true && SomeOtherMethod(out var x)) ? x : 5;"; string expected = - @"static Test() {using(new StrykerNamespace.MutantContext()){if(StrykerNamespace.MutantControl.IsActive(0)){!((true && SomeOtherMethod(out var x)) )? x : 5;}else{if(StrykerNamespace.MutantControl.IsActive(1)){(true || SomeOtherMethod(out var x)) ? x : 5;}else{((StrykerNamespace.MutantControl.IsActive(2)?false:true )&& SomeOtherMethod(out var x)) ? x : 5;}}}}}"; + @"static Test() {using(new StrykerNamespace.MutantContext()){if(StrykerNamespace.MutantControl.IsActive(1)){(false?x :5);}else{if(StrykerNamespace.MutantControl.IsActive(0)){(true?x :5);}else{if(StrykerNamespace.MutantControl.IsActive(2)){(true || SomeOtherMethod(out var x)) ? x : 5;}else{((StrykerNamespace.MutantControl.IsActive(3)?false:true )&& SomeOtherMethod(out var x)) ? x : 5;}}}}}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -1552,12 +1540,29 @@ public void ShouldMutatePropertiesInArrowFormEvenWithComplexConstruction() static TestClass(){}}"; var expected = @"class Test { -string Value {get {if(StrykerNamespace.MutantControl.IsActive(0)){return!(Out(out var x))? ""empty"": """";}else{return Out(out var x)? (StrykerNamespace.MutantControl.IsActive(1)?"""":""empty""): (StrykerNamespace.MutantControl.IsActive(2)?""Stryker was here!"":"""");}}} +string Value {get {if(StrykerNamespace.MutantControl.IsActive(1)){return(false?""empty"":"""");}else{if(StrykerNamespace.MutantControl.IsActive(0)){return(true?""empty"":"""");}else{return Out(out var x)? (StrykerNamespace.MutantControl.IsActive(2)?"""":""empty""): (StrykerNamespace.MutantControl.IsActive(3)?""Stryker was here!"":"""");}}}} static TestClass(){using(new StrykerNamespace.MutantContext()){}}}"; ShouldMutateSourceInClassToExpected(source, expected); } + [Fact] + public void ShouldMutateConditionalExpression() + { + string source = @"void TestMethod() +{ +var a = 1; +var x = a == 1 ? 5 : 7; +}"; + string expected = @"void TestMethod() +{if(StrykerNamespace.MutantControl.IsActive(0)){}else{ +var a = 1; +var x = (StrykerNamespace.MutantControl.IsActive(2)?(false?5 :7):(StrykerNamespace.MutantControl.IsActive(1)?(true?5 :7):(StrykerNamespace.MutantControl.IsActive(3)?a != 1 :a == 1 )? 5 : 7)); +}}"; + + ShouldMutateSourceInClassToExpected(source, expected); + } + [Fact] public void ShouldMutateStaticProperties() { @@ -1601,7 +1606,7 @@ public void ShouldMutateStaticPropertiesInArrowFormEvenWithComplexConstruction() static string Value => Out(out var x)? ""empty"": """";}"; var expected = @"class Test { -static string Value {get {if(StrykerNamespace.MutantControl.IsActive(0)){return!(Out(out var x))? ""empty"": """";}else{return Out(out var x)? (StrykerNamespace.MutantControl.IsActive(1)?"""":""empty""): (StrykerNamespace.MutantControl.IsActive(2)?""Stryker was here!"":"""");}}}}"; +static string Value {get{if(StrykerNamespace.MutantControl.IsActive(1)){return(false?""empty"":"""");}else{if(StrykerNamespace.MutantControl.IsActive(0)){return(true?""empty"":"""");}else{return Out(out var x)? (StrykerNamespace.MutantControl.IsActive(2)?"""":""empty""): (StrykerNamespace.MutantControl.IsActive(3)?""Stryker was here!"":"""");}}}}}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -1614,8 +1619,7 @@ public void ShouldMutatePropertiesAsArrowExpression() }"; var expected = @"class Test { -string Value {get{if(StrykerNamespace.MutantControl.IsActive(0)){return!(Generator(out var x) )? """" :""test"";}else{return Generator(out var x) ? (StrykerNamespace.MutantControl.IsActive(1)?""Stryker was here!"":"""" ):(StrykerNamespace.MutantControl.IsActive(2)?"""":""test"");}}} -}"; +string Value {get{if(StrykerNamespace.MutantControl.IsActive(1)){return(false?"""" :""test"");}else{if(StrykerNamespace.MutantControl.IsActive(0)){return(true?"""" :""test"");}else{return Generator(out var x) ? (StrykerNamespace.MutantControl.IsActive(2)?""Stryker was here!"":"""" ):(StrykerNamespace.MutantControl.IsActive(3)?"""":""test"");}}}}}"; ShouldMutateSourceInClassToExpected(source, expected); } @@ -1666,8 +1670,7 @@ public void ShouldNotLeakMutationsAcrossDefinitions() }"; var expected = @"class Test { -int GetId(string input) {if(StrykerNamespace.MutantControl.IsActive(0)){return!(int.TryParse(input, out var result) )? result : 0;}else{return int.TryParse(input, out var result) ? result : 0;}} -string Value {get{if(StrykerNamespace.MutantControl.IsActive(1)){return!(Generator(out var x) )? """" :""test"";}else{return Generator(out var x) ? (StrykerNamespace.MutantControl.IsActive(2)?""Stryker was here!"":"""" ):(StrykerNamespace.MutantControl.IsActive(3)?"""":""test"");}}}}}}"; +int GetId(string input) {if(StrykerNamespace.MutantControl.IsActive(1)){return(false?result :0);}else{if(StrykerNamespace.MutantControl.IsActive(0)){return(true?result :0);}else{return int.TryParse(input, out var result) ? result : 0;}}}string Value {get{if(StrykerNamespace.MutantControl.IsActive(3)){return(false?"""" :""test"");}else{if(StrykerNamespace.MutantControl.IsActive(2)){return(true?"""" :""test"");}else{return Generator(out var x) ? (StrykerNamespace.MutantControl.IsActive(4)?""Stryker was here!"":"""" ):(StrykerNamespace.MutantControl.IsActive(5)?"""":""test"");}}}}}"; ShouldMutateSourceInClassToExpected(source, expected); } diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/ConditionalExpressionMutatorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/ConditionalExpressionMutatorTests.cs new file mode 100644 index 0000000000..1567d492bb --- /dev/null +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/ConditionalExpressionMutatorTests.cs @@ -0,0 +1,70 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Shouldly; +using Stryker.Core.Mutators; +using System.Linq; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace Stryker.Core.UnitTest.Mutators +{ + public class ConditionalExpressionMutatorTests : TestBase + { + [Fact] + public void ShouldBeMutationLevelStandard() + { + var target = new ConditionalExpressionMutator(); + target.MutationLevel.ShouldBe(MutationLevel.Standard); + } + + [Fact] + public void ShouldMutate_TwoMutations() + { + var target = new ConditionalExpressionMutator(); + var source = @"251 == 73 ? 1 : 0"; + var tree = CSharpSyntaxTree.ParseText(source); + var originalNode = tree.GetRoot().DescendantNodes().OfType().Single(); + + var result = target.ApplyMutations(originalNode, null).ToList(); + + result.Count.ShouldBe(2, "Two mutations should have been made"); + Assert.Collection( + result, + m1 => (m1.ReplacementNode is ParenthesizedExpressionSyntax pes && pes.Expression is ConditionalExpressionSyntax ces && ces.Condition.Kind() is SyntaxKind.TrueLiteralExpression).ShouldBeTrue(), + m2 => (m2.ReplacementNode is ParenthesizedExpressionSyntax pes && pes.Expression is ConditionalExpressionSyntax ces && ces.Condition.Kind() is SyntaxKind.FalseLiteralExpression).ShouldBeTrue() + ); + } + + [Fact] + public void ShouldMutate_DoNotTouchBranches() + { + var target = new ConditionalExpressionMutator(); + var source = "251 == 73 ? 1 : 0"; + var tree = CSharpSyntaxTree.ParseText(source); + var originalNode = tree.GetRoot().DescendantNodes().OfType().Single(); + + var result = target.ApplyMutations(originalNode, null).ToList(); + + foreach (var mutation in result) + { + var pes = mutation.ReplacementNode.ShouldBeOfType(); + var ces = pes.Expression.ShouldBeOfType(); + ces.WhenTrue.IsEquivalentTo(originalNode.WhenTrue).ShouldBeTrue(); + ces.WhenFalse.IsEquivalentTo(originalNode.WhenFalse).ShouldBeTrue(); + } + } + + [Fact] + public void ShouldNotMutateDeclarationPatterns() + { + var target = new ConditionalExpressionMutator(); + var source = "var y = x is object result ? result.ToString() : null;"; + SyntaxTree tree = CSharpSyntaxTree.ParseText(source); + + var expressionSyntax = tree.GetRoot().DescendantNodes().OfType().Single(); + var result = target.ApplyMutations(expressionSyntax, null).ToList(); + + result.ShouldBeEmpty(); + } + } +} diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/NegateConditionMutatorTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/NegateConditionMutatorTests.cs index ae5f363913..9f28ad7ebc 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/NegateConditionMutatorTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Mutators/NegateConditionMutatorTests.cs @@ -48,7 +48,6 @@ static void Main(string[] args) [Theory] [InlineData("if (Method()) => return true;")] [InlineData("while (Method()) => age++;")] - [InlineData("(Method()? 1:2);")] public void MutatesStatementWithMethodCallWithNoArguments(string method) { var target = new NegateConditionMutator(); diff --git a/src/Stryker.Core/Stryker.Core.UnitTest/Options/Inputs/IgnoreMutationsInputTests.cs b/src/Stryker.Core/Stryker.Core.UnitTest/Options/Inputs/IgnoreMutationsInputTests.cs index 829b6b8759..dbbf9955b0 100644 --- a/src/Stryker.Core/Stryker.Core.UnitTest/Options/Inputs/IgnoreMutationsInputTests.cs +++ b/src/Stryker.Core/Stryker.Core.UnitTest/Options/Inputs/IgnoreMutationsInputTests.cs @@ -27,7 +27,7 @@ public void ShouldValidateExcludedMutation() var ex = Should.Throw(() => target.Validate()); - 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]"); + 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]"); } [Fact] diff --git a/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs b/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs index 8c3eddccf0..118a7bf9c5 100644 --- a/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutants/CsharpMutantOrchestrator.cs @@ -93,6 +93,7 @@ private static List DefaultMutatorList() => new BinaryExpressionMutator(), new BlockMutator(), new BooleanMutator(), + new ConditionalExpressionMutator(), new AssignmentExpressionMutator(), new PrefixUnaryMutator(), new PostfixUnaryMutator(), diff --git a/src/Stryker.Core/Stryker.Core/Mutators/ConditionalExpressionMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/ConditionalExpressionMutator.cs new file mode 100644 index 0000000000..d8cba344be --- /dev/null +++ b/src/Stryker.Core/Stryker.Core/Mutators/ConditionalExpressionMutator.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Stryker.Core.Mutants; + +namespace Stryker.Core.Mutators +{ + public class ConditionalExpressionMutator : MutatorBase + { + public override MutationLevel MutationLevel => MutationLevel.Standard; + + public override IEnumerable ApplyMutations(ConditionalExpressionSyntax node, SemanticModel semanticModel) + { + // if the condition contains variable declarations, we should not mutate it + if (node.Condition.DescendantNodes().OfType().Any()) + { + yield break; + } + + yield return new Mutation() + { + Type = Mutator.Conditional, + DisplayName = "Conditional (true) mutation", + OriginalNode = node, + ReplacementNode = SyntaxFactory.ParenthesizedExpression( + SyntaxFactory.ConditionalExpression( + SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression), + node.WhenTrue, + node.WhenFalse + ) + ) + }; + + yield return new Mutation() + { + Type = Mutator.Conditional, + DisplayName = "Conditional (false) mutation", + OriginalNode = node, + ReplacementNode = SyntaxFactory.ParenthesizedExpression( + SyntaxFactory.ConditionalExpression( + SyntaxFactory.LiteralExpression(SyntaxKind.FalseLiteralExpression), + node.WhenTrue, + node.WhenFalse + ) + ) + }; + } + } +} diff --git a/src/Stryker.Core/Stryker.Core/Mutators/Mutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/Mutator.cs index f4cea8a244..2a9005ffcc 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/Mutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/Mutator.cs @@ -44,7 +44,9 @@ public enum Mutator [MutatorDescription("Math methods")] Math, [MutatorDescription("String Method")] - StringMethod + StringMethod, + [MutatorDescription("Conditional operators")] + Conditional } public static class EnumExtension diff --git a/src/Stryker.Core/Stryker.Core/Mutators/NegateConditionMutator.cs b/src/Stryker.Core/Stryker.Core/Mutators/NegateConditionMutator.cs index 25004eff9b..b77e7d1e36 100644 --- a/src/Stryker.Core/Stryker.Core/Mutators/NegateConditionMutator.cs +++ b/src/Stryker.Core/Stryker.Core/Mutators/NegateConditionMutator.cs @@ -39,12 +39,6 @@ public override IEnumerable ApplyMutations(ExpressionSyntax node, Sema replacement = NegateCondition(whileStatementSyntax.Condition); } break; - case ConditionalExpressionSyntax conditionalExpressionSyntax: - if (conditionalExpressionSyntax.Condition == node) - { - replacement = NegateCondition(conditionalExpressionSyntax.Condition); - } - break; } if (replacement != null)