mattwhitfield / Unitverse

A unit test generation extension for Visual Studio that aims to always produce code that compiles - covering the basic cases automatically and preparing as much as it can for the complex cases.
MIT License
89 stars 20 forks source link

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

Closed Hefaistos68 closed 1 year ago

Hefaistos68 commented 1 year ago

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

Hefaistos68 commented 1 year ago

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

mattwhitfield commented 1 year ago

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

Hefaistos68 commented 1 year ago

Weird, doesnt work anywhere for me.

Hefaistos68 commented 1 year ago

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

mattwhitfield commented 1 year ago

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?

Hefaistos68 commented 1 year ago

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

Hefaistos68 commented 1 year ago

Settings here: settings.zip

mattwhitfield commented 1 year ago

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...

Hefaistos68 commented 1 year ago

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

mattwhitfield commented 1 year ago

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

Hefaistos68 commented 1 year ago

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.

mattwhitfield commented 1 year ago

...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;
mattwhitfield commented 1 year ago

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...

Hefaistos68 commented 1 year ago

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, NullReferenceExceptionat 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.

mattwhitfield commented 1 year ago

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...

Hefaistos68 commented 1 year ago

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.

Hefaistos68 commented 1 year ago

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

Hefaistos68 commented 1 year ago

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);

mattwhitfield commented 1 year ago

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 ❤️

Hefaistos68 commented 1 year ago

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.

Hefaistos68 commented 1 year ago

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?

mattwhitfield commented 1 year ago

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...

Hefaistos68 commented 1 year ago

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)

mattwhitfield commented 1 year ago

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?