mattwhitfield/Unitverse

"Goto Tests" menu item in editor window not working?

Closed this issue · 24 comments

Describe the bug
When using the "Goto tests" context menu entry in the editor window, nothing happens, even with existing test(s). When using it on the solution explorer level, it works fine and opens the file containing the tests.

To Reproduce
Create or open a C# project, create unit tests from unitverse, open a source file with methods for which unit tests have been created, right click on a method and select "Goto tests".

Additional context
VS2022 17.5.4, C# project, .NET6

Create tests on the other hand is working fine on methods that have no unit tests yet.

I've tried to repro this one and I couldn't - could you provide a sample?

Weird, doesnt work anywhere for me.

Actually, you could try it with my own repo MoreDateTime, which uses nesting and all the nasty stuff that I bugged you with.

So I opened your repo - tried it, couldn't repro. I was on 17.4.1 - upgraded to 17.5.4 in case that was it - still couldn't repro. Could you export your settings and attach the .unitTestGeneratorConfig here?

Damn it, ok. Will put my VS config + unitTestGeneratorConfig here asap.

Settings here:
settings.zip

Ok - tried it with your settings applied and still can't repro :(

I'm out of things I can try - if you fancy you could try debugging it your end - but I'm out of options...

Not much experience debugging vs extensions but i may try. Don't hold your breath.

You'd want a breakpoint on line 113 of GoToUnitTestsForSymbolCommand - if you have any questions feel free to ask :)

All right, so: it goes through, finds the item, jumps into VsProjectHelper.cs:ActivateItem() in line 177 then it throws this exception:

System.NotSupportedException
  HResult=0x80131515
  Message=Specified method is not supported.
  Source=mscorlib
  StackTrace:
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
   at Microsoft.VisualStudio.ProjectSystem.VS.Implementation.Package.Automation.OAProjectItem.<>c__DisplayClass54_0.<Open>b__0()
   at Microsoft.VisualStudio.ProjectSystem.VS.Implementation.Package.Automation.OAProject.<>c__DisplayClass74_0.<<ExecuteSynchronously>b__0>d.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.VisualStudio.Threading.JoinableTask.CompleteOnCurrentThread()
   at Microsoft.VisualStudio.ProjectSystem.ProjectMultiThreadedService.ExecuteSynchronously(Func`1 asyncAction)
   at Microsoft.VisualStudio.ProjectSystem.VS.Implementation.Package.Automation.OAProjectItem.Open(String ViewKind)
   at Unitverse.Helper.VsProjectHelper.ActivateItem(ProjectItem testProjectItem) in C:\dev\Repos\Unitverse\src\Unitverse\Helper\VsProjectHelper.cs:line 177

  This exception was originally thrown at this call stack:
    [External Code]
    Unitverse.Helper.VsProjectHelper.ActivateItem(EnvDTE.ProjectItem) in VsProjectHelper.cs

As far as I can see, the correct method, file and project is passed into the ActivateItem method. The ProjectItem structure is correctly filled.

...but it only does that if you do it from the editor - and the exact same method is fine when it's finding it via the solution explorer... weird. When you do it from the code editor window, it resolves an IVsHierarchy to the ProjectItem, rather than looking directly through the ProjectItems for the right file.

Does generating tests for a symbol work OK? Because that uses the same code path.

As an experiment - could you try, at the bottom of VsProjectHelper, replacing:

            Ignore.HResult(hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_ExtObject, out var objProj));
            return objProj as ProjectItem;

with

            Ignore.HResult(hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_ExtObject, out var objProj));

            if (objProj is ProjectItem item)
            {
                return Find(item.ContainingProject.ProjectItems, item.Name);
            }

            return objProj as ProjectItem;

As an aside, I really don't like the empty catch { } there - it was in the very original commit (when this project was SentryOne Unit Test Generator) - so I don't even know who put it in or why...

Added the code you mentioned to the GetProjectItem() method, makes no difference. But, what I noticed is something totally different: When I tried to create a test for the same symbol (a method that I know has tests already) then I got an error message saying that it did not find a file. Well, the file was of the right name, but the location was not. It was trying to open a file in a backup folder that is not part of the solution.
Point is: I am using VSHistory, which creates an hidden folder (.vshistory) in every folder of the solution where it stores a backup of the edits made to a solution file.
Now, why the project system would try to access a backup file that is not part of the solution, I have not the slightest idea.

After removing those backup folders, I am getting a different exception, NullReferenceException at at Unitverse.Core.Models.ClassModel..ctor(TypeDeclarationSyntax declaration, SemanticModel semanticModel, Boolean isSingleItem) in C:\dev\Repos\Unitverse\src\Unitverse.Core\Models\ClassModel.cs:line 44
Have to get to work now, will check on that later. If you have any ideas already, let me know.

Maybe interfaceMember is null on that line and then calling TypeSymbol.FindImplementationForInterfaceMember(interfaceMember) throws an NRE?

I wonder if that VSHistory thing causes the wrong item to be found - it's possible that it's causing the wrong VSHierarchy to be returned... The documentation for exactly how that stuff is supposed to work is quite lacking...

Will check on that now.
The VSHistory extension is not loaded and I have deleted the folders (they are usually excluded through the .gitignore)
Seems to me that still, VS loads those files into the solution space, although they are not shown in the solution.

The VSHistory extension is not loaded and I have deleted all its folders.

image

interfaceMember seems ok to me, has a value and points to the intended method. What stumps me here is that its trying to make an Explicit implementation map, while this class has none. But that should not cause a crash, except if there is a bug in Roslyn.
image

So, the problem with the "generate implementation" exception is definitely linked to the class, which implements a template, on other classes it works. Seems like a bug for the Roslyn team.

This leaves us with the other problem: exception on testProjectItem.Open(vsViewKindCode);

Does the NRE happen in TypeSymbol.FindImplementationForInterfaceMember(interfaceMember)? If so I can put some guards around that.

As for the Open() exception, I don't have any great ideas, especially if the Find() addition did not solve it - because that effectively recreates the code path that works (the solution explorer path).

I will do some digging and see if anyone else working with VSX has seen anything similar 👍

Thanks for the help debugging ❤️

Yes, NRE happens there, deep down the Roslyn abyss.

As on the Open() issue, its definitely a strange thing, from what I found nobody ever checks there for an exception, so this seems a rare thing. I will try the same on another machine where I don't see this problem happening, so it might be related to some external component or a outdated / misdated VS component.
I keep digging.

Ok, so some more findings: I could create another project where "Goto tests" actually works, on the same machine. This means this is a project/solution dependent problem.
From what I saw, when it works, the ProjectItem has valid Document and FileCodeModel members, when it doesn't - they are null. The other members seem relatively the same, at least pointing to valid values.

Anything you can make of this?

Have you tried deleting the .vs folder for the solution where it doesn't work? That would cause Roslyn to have to rebuild it's cache I believe...

Deleting the .vs folder resolves the NRE in TypeSymbol.FindImplementationForInterfaceMember(interfaceMember), so this is a caching issue of Roslyn as it seems.

I am using two projects to test:
https://github.com/Hefaistos68/MoreDateTime (goto not working, testing especifically on DateTimeRange.Contains method)
https://github.com/nager/Nager.Date (goto is working, tested on a few different similar methods)

Same machine, same experimental instance of VS (or the default instance)

So I have tried a couple of times to repro this and not had any luck. I just wanted to check if you had any more insight before I close it as not reproducible?