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

Open Folder Unit Test #1959

Merged
merged 17 commits into from
Jun 1, 2018
Merged

Conversation

paulvanbrenk
Copy link
Contributor

@paulvanbrenk paulvanbrenk commented May 16, 2018

Adds support for running unit tests in "Open Folder" scenarios.


private async Task OnActiveWorkspaceChangedAsync(object sender, EventArgs e)
{
this.currentWorkspace = this.workspaceService.CurrentWorkspace;
Copy link
Member

Choose a reason for hiding this comment

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

You're not unsubscribing from the previous workspace's event handlers. Maybe that causes leaks, idk for certain. You will also want to subscribe to the new workspace's file watcher and index service. Each IWorkspace spins up a new instance of these services.

}
}
}
this.TestContainersUpdated?.Invoke(this, EventArgs.Empty);
Copy link
Member

Choose a reason for hiding this comment

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

This is being invoked outside of the lock (this.containerLock){ }. Is that intentional? It's in the lock for FileSystemChangedAsync

Copy link
Contributor Author

Choose a reason for hiding this comment

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

looks like it's outside the lock in both cases.


private static bool IsJavaScriptFile(string path)
{
var ext = Path.GetExtension(path);
Copy link
Member

Choose a reason for hiding this comment

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

You're not checking if this is a directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

var testRoot = packageJson.TestRoot;
if (!string.IsNullOrEmpty(testRoot))
{
fileDataValues.Add(new FileDataValue(NodejsConstants.TestRootDataValueGuid, "TestRoot", testRoot));
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

"TestRoot" [](start = 96, length = 10)

Question, not suggestion: should this be a constant? #Closed


public string TestRoot { get; }

public IEnumerable<Guid> DebugEngines { get { yield break; } }
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

yield break; [](start = 54, length = 12)

Unless there's an optimization I don't know about, this is way more expensive than Array.Empty. #Closed


public int CompareTo(ITestContainer other)
{
if (other == null || !(other is PackageJsonTestContainer testContainer))
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

is [](start = 41, length = 2)

is includes a null check. #Closed

{
if (other == null || !(other is PackageJsonTestContainer testContainer))
{
return -1;
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

-1 [](start = 23, length = 2)

This doesn't actually happen, does it? If it does, it seems unlikely that the comparison produces a consistent result when the operands are reversed. #Closed


var sourceCompare = StringComparer.OrdinalIgnoreCase.Compare(this.Source, testContainer.Source);

return sourceCompare == 0 ? sourceCompare : StringComparer.OrdinalIgnoreCase.Compare(this.TestRoot, testContainer.TestRoot);
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

== [](start = 33, length = 2)

!= 0? #Closed

private readonly IVsFolderWorkspaceService workspaceService;

private readonly List<PackageJsonTestContainer> containers = new List<PackageJsonTestContainer>();
private readonly object containerLock = new object();
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

containerLock [](start = 32, length = 13)

I'm probably forgetting some framework guideline, but why not lock on containers? #Closed

Copy link
Contributor Author

@paulvanbrenk paulvanbrenk May 31, 2018

Choose a reason for hiding this comment

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

In this case locking on container would be fine, since it's not a public property. But I prefer to use the well-known pattern to avoid confusion in the future. #Closed

Copy link
Member

Choose a reason for hiding this comment

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

I understood that pattern was for value types or public members of reference types, but I don't feel strongly about it.


In reply to: 192169424 [](ancestors = 192169424)

{
this.currentWorkspace = this.workspaceService.CurrentWorkspace;

await AttemptUpdateAsync();
Copy link
Member

Choose a reason for hiding this comment

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

await AttemptUpdateAsync(); [](start = 12, length = 27)

This seems to do a lot less work than the ctor. Maybe they should share a helper?

}
}

public Uri ExecutorUri { get; } = NodejsConstants.PackageJsonExecutorUri;
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

{ get; } = [](start = 31, length = 10)

=>? #Closed

{
if (this.currentWorkspace != null)
{
var indexServce = this.currentWorkspace.GetIndexWorkspaceService();
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

Typo: indexService #Closed

Copy link
Contributor Author

@paulvanbrenk paulvanbrenk May 31, 2018

Choose a reason for hiding this comment

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

fixed #Closed

await AttemptUpdateAsync();
}

private static bool IsJavaScriptFile(string path)
Copy link
Member

Choose a reason for hiding this comment

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

IsJavaScriptFile [](start = 28, length = 16)

I'm skeptical that there isn't a helper for this or, at least, constants for known file extensions.

Copy link
Member

Choose a reason for hiding this comment

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

There really isn't a helper for detecting JS files?


In reply to: 188796597 [](ancestors = 188796597)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope.

I'll refactor that in another PR

private readonly List<PackageJsonTestContainer> containers = new List<PackageJsonTestContainer>();
private readonly object containerLock = new object();

private IWorkspace currentWorkspace;
Copy link
Member

Choose a reason for hiding this comment

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

currentWorkspace [](start = 27, length = 16)

If containers is guarded by a lock, should this be as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to prevent the call to ToArray in the TestContainers property to now fail, because we would be changing the collection while converting it to an array.

foreach (var dataValue in filesDataValues)
{
var rootFilePath = this.currentWorkspace.MakeRooted(dataValue.Key);
var testRoot = dataValue.Value.Where(f => f.Name == "TestRoot").Select(f => f.Value).FirstOrDefault();
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

"TestRoot" [](start = 76, length = 10)

Constant for "TestRoot"? #Closed

foreach (var dataValue in filesDataValues)
{
var rootFilePath = this.currentWorkspace.MakeRooted(dataValue.Key);
var testRoot = dataValue.Value.Where(f => f.Name == "TestRoot").Select(f => f.Value).FirstOrDefault();
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

.Select(f => f.Value).FirstOrDefault() [](start = 87, length = 38)

Alternatively, .FirstOrDefault()?.Value. #Closed

Copy link
Contributor Author

@paulvanbrenk paulvanbrenk May 31, 2018

Choose a reason for hiding this comment

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

changed #Closed


namespace Microsoft.NodejsTools.TestAdapter
{
[FileExtension(".json")]
Copy link
Member

Choose a reason for hiding this comment

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

".json" [](start = 19, length = 7)

Constant?

break;
}
}
testFx = testFx ?? FrameworkDiscover.Intance.Get("ExportRunner");
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

Intance [](start = 49, length = 7)

Typo: "Instance" #Closed

Copy link
Contributor Author

@paulvanbrenk paulvanbrenk May 31, 2018

Choose a reason for hiding this comment

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

fixed #Closed

break;
}
}
testFx = testFx ?? FrameworkDiscover.Intance.Get("ExportRunner");
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

FrameworkDiscover.Intance.Get("ExportRunner") [](start = 31, length = 45)

Is there some reason to believe this isn't null? #Closed

Copy link
Contributor Author

@paulvanbrenk paulvanbrenk May 31, 2018

Choose a reason for hiding this comment

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

Yes, this is installed by us and doesn't require additional npm packages. #Closed

}

var fileList = Directory.GetFiles(testFolderPath, "*.js", SearchOption.AllDirectories);
var files = string.Join(";", fileList);
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

var files = string.Join(";", fileList); [](start = 12, length = 39)

This seems likely to be expensive. Consider checking the logging level before evaluating it. #Closed

Copy link
Contributor Author

@paulvanbrenk paulvanbrenk May 31, 2018

Choose a reason for hiding this comment

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

All log levels are always outputted, they only change the color of the output. #Closed


discoverySink.SendTestCase(testcase);
}
logger.SendMessage(TestMessageLevel.Informational, "Processing finished for framework 'mocha'.");
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

mocha [](start = 99, length = 5)

What's the connection to mocha? #Closed

{
private List<TestCase> currentTests;
private IFrameworkHandle frameworkHandle;
private TestResult currentResult = null;
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

= null [](start = 40, length = 7)

How come some of the fields are initialized with their default values? #Closed

{
var receiver = new JavaScriptTestExecutor.TestReceiver();
var discoverer = new PackageJsonTestDiscoverer();
discoverer.DiscoverTests(sources, null, frameworkHandle, receiver);
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

null [](start = 46, length = 4)

A named argument might help. #Closed

foreach (var test in tests)
{
var args = new List<string>();
args.AddRange(GetInterpreterArgs(test, workingDir, workingDir));
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

GetInterpreterArgs [](start = 30, length = 18)

Could you just call ToList (or ToArray) on GetInterpreterArgs? #Closed

result.Messages.Add(new TestResultMessage(TestResultMessage.AdditionalInfoCategory, string.Join(Environment.NewLine, standardErrorLines)));
frameworkHandle.RecordResult(result);
frameworkHandle.RecordEnd(test, result.Outcome);
this.currentTests.Remove(test);
Copy link
Member

@amcasey amcasey May 16, 2018

Choose a reason for hiding this comment

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

Remove [](start = 30, length = 6)

It seems strange to remove from a random order list. Is this the correct collection type? #Closed

Copy link
Member

@amcasey amcasey left a comment

Choose a reason for hiding this comment

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

🕐

private IFrameworkHandle frameworkHandle;
private TestResult currentResult = null;
private ResultObject currentResultObject = null;
private TestExecutorWorker worker;

public void RunTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle)
Copy link
Member

Choose a reason for hiding this comment

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

RunTests [](start = 20, length = 8)

Unless there is expected to be exactly one RunTests call, it seems like the in-progress worker should be cancelled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

{
internal sealed class TestReceiver : ITestCaseDiscoverySink
{
public readonly List<TestCase> Tests = new List<TestCase>();
Copy link
Member

Choose a reason for hiding this comment

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

Arguably, this should be exposed as an IReadOnlyList, but it's internal so I don't care very much.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made class private


private void KillNodeProcess()
{
this.nodeProcess?.Kill();
Copy link
Member

Choose a reason for hiding this comment

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

this.nodeProcess?.Kill(); [](start = 12, length = 25)

Does this wait for it to exit? Should it?

Copy link
Contributor Author

@paulvanbrenk paulvanbrenk Jun 1, 2018

Choose a reason for hiding this comment

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

It doesn't, and it shouldn't since we only call this for cancel and on failure.

//get from NodeRemoteDebugPortSupplier::PortSupplierId
private static readonly Guid NodejsRemoteDebugPortSupplierUnsecuredId = new Guid("{9E16F805-5EFC-4CE5-8B67-9AE9B643EF80}");

private readonly ManualResetEvent cancelRequested = new ManualResetEvent(false);
Copy link
Member

Choose a reason for hiding this comment

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

cancelRequested [](start = 42, length = 15)

I'm probably missing something, but this doesn't appear to be used for blocking. If it's just a flag, consider using a simpler type.

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, but don't really want to touch this at this point.

/// <param name="sources">Refers to the list of test sources passed to the test adapter from the client. (Client could be VS or command line)</param>
/// <param name="runContext">Defines the settings related to the current run</param>
/// <param name="frameworkHandle">Handle to framework. Used for recording results</param>
public void RunTests(IEnumerable<string> sources, ITestDiscoverer discoverer)
Copy link
Member

Choose a reason for hiding this comment

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

Is there some reason to believe a run isn't already in progress?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added calls to cancel

}

// where key is the file and value is a list of tests
foreach (var entry in fileToTests)
Copy link
Member

Choose a reason for hiding this comment

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

If the key isn't used, consider iterating over fileToTests.Values.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

var tests = this.currentTests.Where(n => n.DisplayName == testEvent.title);
if (tests.Any())
{
switch (testEvent.type)
Copy link
Member

Choose a reason for hiding this comment

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

type [](start = 38, length = 4)

Just to confirm, we're ignoring other event types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes


this.currentResult.Messages.Add(new TestResultMessage(TestResultMessage.StandardOutCategory, string.Join(Environment.NewLine, standardOutputLines)));
this.currentResult.Messages.Add(new TestResultMessage(TestResultMessage.StandardErrorCategory, string.Join(Environment.NewLine, standardErrorLines)));
this.currentResult.Messages.Add(new TestResultMessage(TestResultMessage.AdditionalInfoCategory, string.Join(Environment.NewLine, standardErrorLines)));
Copy link
Member

Choose a reason for hiding this comment

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

If writing the same thing to two different streams is intentional, consider extracting a local.

private readonly List<PackageJsonTestContainer> containers = new List<PackageJsonTestContainer>();
private readonly object containerLock = new object();

private IWorkspace currentWorkspace;
Copy link
Member

Choose a reason for hiding this comment

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

Why store this? Why not use this.workspaceService.CurrentWorkspace? Does that value change more often than we want?

Copy link
Member

@amcasey amcasey left a comment

Choose a reason for hiding this comment

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

I have some thread-safety concerns, but this looks generally correct.

@paulvanbrenk paulvanbrenk merged commit 4ad1365 into microsoft:master Jun 1, 2018
@paulvanbrenk paulvanbrenk deleted the AnyCodeUnitTest branch June 1, 2018 21:25
@microsoft microsoft deleted a comment from l14zj Jan 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants