Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add external access to stacktrace APIs for Unit Testing #58082

Merged
merged 9 commits into from
Jan 27, 2022
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.StackTraceExplorer;
using System.Collections.Immutable;
using System;
using System.Linq;

namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.API
{
internal sealed class UnitTestingStackTraceService
sharwell marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly IStackTraceExplorerService _stackTraceService;

public UnitTestingStackTraceService(HostWorkspaceServices services)
Copy link
Contributor

@shyamnamboodiripad shyamnamboodiripad Dec 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be changed to accept Workspace instead? This can then be used to access Workspace.Services here. The Workspace can also be threaded down to Frame and Definition below to avoid the need for caller to pass it when calling Definition.TryNavigateToAsync below.

(You could probably move the call to Workspace.Services to happen inside the Frame constructor.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, this was based off another item. Whatever is easiest we can accomodate

=> _stackTraceService = services.GetRequiredService<IStackTraceExplorerService>();

public async Task<Frame?> TryParseAsync(string line, CancellationToken cancellationToken)
{
var result = await StackTraceAnalyzer.AnalyzeAsync(line, cancellationToken).ConfigureAwait(false);

if (result.ParsedFrames.Length == 0)
{
return null;
}

if (result.ParsedFrames.Length > 1)
{
throw new InvalidOperationException();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not expose an API to parse an entire stack trace and return IEnumerable<Frame> (in addition to the current per-frame API)?

I haven't looked into exactly how we will consume this on UT side. But for completeness it would be nice to have an API that supports parsing an entire trace (especially since the internal API supports this).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever works best for the UT team. We can do either. This was my guess at "best" way to do it so you handle line split and we parse a single line. If you want the whole trace to be handled by us then IEnumerable makes sense

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its not a huge issue but I think handling the whole trace in here may be better since that doesn't preclude passing in individual lines.

In other words, with an API that returns IEnumerable, I can still pass in individual lines and call .Single on the retuned enumerable on our side. However, the opposite is not possible (i.e. I can never pass in an entire stack trace if the API only supports passing one at a time).

}

return new Frame(result.ParsedFrames.Single(), _stackTraceService);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Since this is an array can we say result.ParsedFrames[0]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on #58082 (comment)

If we do one result, then does multiple make sense or is that an error?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes if we support only a single API here that returns multiple this is a moot point.

}

public readonly struct Frame
sharwell marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly ParsedFrame _parsedFrame;
private readonly IStackTraceExplorerService _stackTraceService;

public Frame(ParsedFrame parsedFrame, IStackTraceExplorerService service)
Copy link
Contributor

@shyamnamboodiripad shyamnamboodiripad Dec 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this constructor be internal instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm... probably :)

Copy link
Contributor

@shyamnamboodiripad shyamnamboodiripad Dec 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I was thinking that the constructor needn't be exposed to UT. But I guess that is not possible since this is not a nested struct. So its ok to ignore this (and the below) comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually wait :) this is a nested struct - so it would be nice to make this private since nothing else needs to construct it :) @ryzngard

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They can't be private because then the API couldn't construct them. The only other option is to make an interface and have the implementing type private. Not sure it's worth the trouble

{
_parsedFrame = parsedFrame;
_stackTraceService = service;
}

public (Document? document, int lineNumber) TryGetDocumentAndLine(Solution solution)
=> _stackTraceService.GetDocumentAndLine(solution, _parsedFrame);

public async Task<Definition?> TryFindMethodDefinitionAsync(Solution solution, CancellationToken cancellationToken)
{
var definition = await _stackTraceService.TryFindDefinitionAsync(solution, _parsedFrame, StackFrameSymbolPart.Method, cancellationToken).ConfigureAwait(false);
if (definition == null)
{
return null;
}

return new Definition(definition);
}
}

public readonly struct Definition
sharwell marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly FindUsages.DefinitionItem _definition;

public Definition(FindUsages.DefinitionItem definition)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this constructor be internal instead?

{
_definition = definition;
}

public async Task<bool> TryNavigateToAsync(Workspace workspace, bool showInPreviewTab, bool activateTab, CancellationToken cancellationToken)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we eliminate the Workspace parameter here since it is already passed in the constructor to UnitTestingStackTraceService above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable. As a rule I don't like holding onto data for people that might change, but I think workspace reference is safe here.

{
var canNavigate = await _definition.CanNavigateToAsync(workspace, cancellationToken).ConfigureAwait(false);
if (canNavigate)
{
return await _definition.TryNavigateToAsync(workspace, showInPreviewTab, activateTab, cancellationToken).ConfigureAwait(false);
}

return false;
}
}
}
}