stryker-mutator / stryker-net

Mutation testing for .NET core and .NET framework!
https://stryker-mutator.io
Apache License 2.0
1.76k stars 175 forks source link

Can't extract original expression from #2737

Closed AndyCC25 closed 10 months ago

AndyCC25 commented 10 months ago

When running the dotnet stryker in my project is throwing an exception like the following

Unhandled exception. System.InvalidOperationException: Can't extract original expression from {if(StrykerR4Z2rUyYtnj4mXU.MutantControl.IsActive(126)){AccountNumber.ValueChanged += RefreshAccountNumber;}else{AccountNumber.ValueChanged -= RefreshAccountNumber;}} at Stryker.Core.Instrumentation.ExpressionMethodToBodyEngine.Revert(BaseMethodDeclarationSyntax node) in /_/src/Stryker.Core/Stryker.Core/Instrumentation/ExpressionMethodToBodyEngine.cs:line 55

The code where is that one is like this

AccountNumber.ValueChanged += RefreshAccountNumber;

private void RefreshAccountNumber()
        {
            _value = AccountNumber.DisplayValue;
            JSRuntime.InvokeVoidAsync("adjustMaskValue", new object[] { Id! });
        }

All the logs are like this

[09:18:43 INF] Time Elapsed 00:02:04.3443519 Unhandled exception. System.InvalidOperationException: Can't extract original expression from {if(StrykerR4Z2rUyYtnj4mXU.MutantControl.IsActive(126)){AccountNumber.ValueChanged += RefreshAccountNumber;}else{AccountNumber.ValueChanged -= RefreshAccountNumber;}} at Stryker.Core.Instrumentation.ExpressionMethodToBodyEngine.Revert(BaseMethodDeclarationSyntax node) in //src/Stryker.Core/Stryker.Core/Instrumentation/ExpressionMethodToBodyEngine.cs:line 55 at Stryker.Core.Instrumentation.BaseEngine`1.RemoveInstrumentation(SyntaxNode node) in //src/Stryker.Core/Stryker.Core/Instrumentation/BaseEngine.cs:line 47 at Stryker.Core.Mutants.MutantPlacer.RemoveMutant(SyntaxNode nodeToRemove) in //src/Stryker.Core/Stryker.Core/Mutants/MutantPlacer.cs:line 121 at Stryker.Core.Compiling.RollbackProcess.RemoveMutations(SyntaxTree originalTree, IEnumerable`1 diagnosticInfo) in //src/Stryker.Core/Stryker.Core/Compiling/RollbackProcess.cs:line 205 at Stryker.Core.Compiling.RollbackProcess.Start(CSharpCompilation compiler, ImmutableArray1 diagnostics, Boolean lastAttempt, Boolean devMode) in /_/src/Stryker.Core/Stryker.Core/Compiling/RollbackProcess.cs:line 56 at Stryker.Core.Compiling.CsharpCompilingProcess.TryCompilation(Stream ms, Stream symbolStream, CSharpCompilation compilation, EmitResult previousEmitResult, Boolean lastAttempt, Int32 retryCount) in /_/src/Stryker.Core/Stryker.Core/Compiling/CsharpCompilingProcess.cs:line 135 at Stryker.Core.Compiling.CsharpCompilingProcess.Compile(IEnumerable1 syntaxTrees, Stream ilStream, Stream symbolStream) in //src/Stryker.Core/Stryker.Core/Compiling/CsharpCompilingProcess.cs:line 83 at Stryker.Core.MutationTest.CsharpMutationProcess.CompileMutations(MutationTestInput input) in //src/Stryker.Core/Stryker.Core/MutationTest/CsharpMutationProcess.cs:line 87 at Stryker.Core.MutationTest.CsharpMutationProcess.Mutate(MutationTestInput input) in //src/Stryker.Core/Stryker.Core/MutationTest/CsharpMutationProcess.cs:line 76 at Stryker.Core.MutationTest.MutationTestProcess.Mutate() in //src/Stryker.Core/Stryker.Core/MutationTest/MutationTestProcess.cs:line 88 at Stryker.Core.Initialisation.ProjectMutator.MutateProject(StrykerOptions options, MutationTestInput input, IReporter reporters) in //src/Stryker.Core/Stryker.Core/Initialisation/ProjectMutator.cs:line 38 at Stryker.Core.Initialisation.ProjectOrchestrator.MutateProjects(StrykerOptions options, IReporter reporters, ITestRunner runner)+MoveNext() in //src/Stryker.Core/Stryker.Core/Initialisation/ProjectOrchestrator.cs:line 62 at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) at Stryker.Core.StrykerRunner.RunMutationTest(IStrykerInputs inputs, ILoggerFactory loggerFactory, IProjectOrchestrator projectOrchestrator) in //src/Stryker.Core/Stryker.Core/StrykerRunner.cs:line 61 at Stryker.CLI.StrykerCli.RunStryker(IStrykerInputs inputs) in //src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs:line 93 at Stryker.CLI.StrykerCli.<>cDisplayClass10_0.b_0() in //src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs:line 68 at McMaster.Extensions.CommandLineUtils.CommandLineApplication.<>cDisplayClass143_0.b_0(CancellationToken ) at McMaster.Extensions.CommandLineUtils.CommandLineApplication.ExecuteAsync(String[] args, CancellationToken cancellationToken) at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute(String[] args) at Stryker.CLI.StrykerCli.Run(String[] args) in //src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs:line 74 at Stryker.CLI.Program.Main(String[] args) in //src/Stryker.CLI/Stryker.CLI/Program.cs:line 14

I've tried to avoid the possible tests that are using that logic but still is failing to run those mutations

dupdob commented 10 months ago

thanks for opening this issue. This obviously should not happen. We will look into this.In the meantime, I think I have a workaround for you: could you please add the following stryker comments // Stryker disable once all like this:

// Stryker disable once all
AccountNumber.ValueChanged += RefreshAccountNumber;

And any similar lines that may cause this issue. This should prevent this issue. I think we may need a bit more info to dig into this, we will revert to you if this is confirmed.

dupdob commented 10 months ago

Confirm I need a bit more info: could you please share the method which contains this line? AccountNumber.ValueChanged += RefreshAccountNumber;

According to the stack trace, it looks an expression body method. This should not be a problem of course, I am simply looking for how to reproduce this issue.

AndyCC25 commented 10 months ago

Sure it is inside a Blazor component and inside one of the methods of the lifecycle like this

 protected override async Task OnInitializedAsync()
        {
            await base.OnInitializedAsync();
            AccountNumber.ValueChanged += RefreshAccountNumber;
        }

JIC Something that I'm seeing using the workaround is that it happens in the places where besides having the += usually in these components I'm doing the unsubscribe too in the dispose method like AccountNumber.ValueChanged -= RefreshAccountNumber;

dupdob commented 10 months ago

Good news, I reproduced your issue. But this is a different line. Look for this construct: SomeMetho() => AccountNumber.ValueChanged += RefreshAccountNumber; And add the stryker comments I gave you. The bit of code you provided works properly. Note that this implies there is another workaround: using a statement body (i.e. in brackets) instead of a statement body should fix your problem.

Meanwhile I will see how to correct this.

AndyCC25 commented 10 months ago

Do you mean this:

public void OnDispose()
{
   AccountNumber.ValueChanged -= ValueChanged
}

instead of this public void OnDispose() => AccountNumber.ValueChanged -= ValueChanged;

dupdob commented 10 months ago

exactly

dupdob commented 10 months ago

sorry: I did not reproduce, I introduced a syntax error that triggered the problem, but it should not happen in the wild. Still searching. Will report when I am sure I reproduced it.

AndyCC25 commented 10 months ago

if you need help trying to reproduce this one, I also notice that is happening in my Blazor components when I have a .razor and .razor.cs files for a component

AndyCC25 commented 10 months ago
[13:46:50 WRN] Safe Mode! Stryker will try to continue by rolling back all mutations in method. This should not happen, please report this as an issue on github with the previous error message.
[13:46:50 WRN] Stryker.NET encountered a compile error in C:\test\src\Test\BudgetGroupMaintenance.razor.cs (at 27:32) with message: 'BudgetGroupMaintenance.OnInitialized()': no suitable method found to override (Source code: protected override void OnInitialized()
dupdob commented 10 months ago

Thanks for the details. I have a working theory for why it fails. But first things first: with my workaround are you able to go through the Stryker session? Does it run properly ? I am not sure what the current status is with Blazor projects, but they were not supported in the past. I am pretty sure things have improved now that Stryker have preliminary support for code generators, but it may fail for many reasons, including failing to run said code generators.

This error; no suitable method found to override (Source code: protected override void OnInitialized(), is unlikely to be triggered by a mutation, as mutations do not alter method signature. I suspect it comes from some failing with Blazor generated code that was not generated. As this is a compilation error that encompasses the whole method, Stryker tries to remove every mutations in it (a.k.a Safe Mode). But it turns out that Stryker had to convert the method from expression body to statement body so it is able to mutate it. And the exception happens because Stryker attemps to transform the method back to expression body, but it sill has mutations in it, hence it cannot be transformed legally. As said earlier, this does not happen during 'regular' run, because this kind of error cannot happen with regular mutations. I will see how to secure the mutations removal process for such a situation, to avoid an unpleasant exception, but I am afraid this will not solve your whole problem.

AndyCC25 commented 10 months ago

No, with the workaround the stryker session is not completed due the errors that appear about the no suitable method found to override but I pass all those errors about the expression methods, Would be nice to do something here so that would help to run stryker over projects with blazor, If I can do something extra to help you, I'm available

dupdob commented 10 months ago

Thanks for the feedback. On my side, I have a 'fix' for your original problem. Regarding the problem with code generators: 1) Next Stryker release will provide details when Stryker fails to load a generator. It can help solve the problem locally and/or help us improve Stryker (if possible) in that regard 2) You have some details about this in issue #2736

AndyCC25 commented 10 months ago

Do you know when is gonna be the next release?

rouke-broersma commented 10 months ago

Hoping to release today