-
Notifications
You must be signed in to change notification settings - Fork 359
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1959 from paulvanbrenk/AnyCodeUnitTest
Open Folder Unit Test
- Loading branch information
Showing
25 changed files
with
1,006 additions
and
574 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
Nodejs/Product/Nodejs/Workspace/PackageJsonTestContainer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.IO; | ||
using System.Threading; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel; | ||
using Microsoft.VisualStudio.TestWindow.Extensibility; | ||
using Microsoft.VisualStudio.TestWindow.Extensibility.Model; | ||
|
||
namespace Microsoft.NodejsTools.Workspace | ||
{ | ||
public sealed class PackageJsonTestContainer : ITestContainer | ||
{ | ||
private int version; | ||
|
||
public PackageJsonTestContainer(ITestContainerDiscoverer discoverer, string source, string testRoot) | ||
{ | ||
this.Discoverer = discoverer; | ||
this.Source = source; | ||
this.TestRoot = testRoot; | ||
} | ||
|
||
private PackageJsonTestContainer(PackageJsonTestContainer other) : | ||
this(other.Discoverer, other.Source, other.TestRoot) | ||
{ | ||
this.version = other.version; | ||
} | ||
|
||
public ITestContainerDiscoverer Discoverer { get; } | ||
|
||
public string Source { get; } | ||
|
||
public string TestRoot { get; } | ||
|
||
public IEnumerable<Guid> DebugEngines => Array.Empty<Guid>(); | ||
|
||
public FrameworkVersion TargetFramework => FrameworkVersion.None; | ||
|
||
public Architecture TargetPlatform => Architecture.Default; | ||
|
||
public bool IsAppContainerTestContainer => false; | ||
|
||
public int CompareTo(ITestContainer other) | ||
{ | ||
Debug.Assert(other is PackageJsonTestContainer, "Only test containers based on package.json are expected."); | ||
|
||
var testContainer = (PackageJsonTestContainer)other; | ||
|
||
if (this.version != testContainer.version) | ||
{ | ||
return this.version - testContainer.version; | ||
} | ||
|
||
var sourceCompare = StringComparer.OrdinalIgnoreCase.Compare(this.Source, testContainer.Source); | ||
|
||
return sourceCompare != 0 ? sourceCompare : StringComparer.OrdinalIgnoreCase.Compare(this.TestRoot, testContainer.TestRoot); | ||
} | ||
|
||
public IDeploymentData DeployAppContainer() => null; | ||
|
||
public ITestContainer Snapshot() => new PackageJsonTestContainer(this); | ||
|
||
public bool IsContained(string javaScriptFilePath) | ||
{ | ||
Debug.Assert(!string.IsNullOrEmpty(javaScriptFilePath) && Path.IsPathRooted(javaScriptFilePath), "Expected a rooted path."); | ||
|
||
return javaScriptFilePath.StartsWith(this.TestRoot, StringComparison.OrdinalIgnoreCase); | ||
} | ||
|
||
public void IncreaseVersion() | ||
{ | ||
Interlocked.Increment(ref this.version); | ||
} | ||
} | ||
} |
181 changes: 181 additions & 0 deletions
181
Nodejs/Product/Nodejs/Workspace/PackageJsonTestContainerDiscoverer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel.Composition; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using Microsoft.VisualStudio.TestWindow.Extensibility; | ||
using Microsoft.VisualStudio.Workspace; | ||
using Microsoft.VisualStudio.Workspace.Indexing; | ||
using Microsoft.VisualStudio.Workspace.VSIntegration.Contracts; | ||
|
||
namespace Microsoft.NodejsTools.Workspace | ||
{ | ||
[Export(typeof(ITestContainerDiscoverer))] | ||
public sealed class PackageJsonTestContainerDiscoverer : ITestContainerDiscoverer | ||
{ | ||
private readonly IVsFolderWorkspaceService workspaceService; | ||
|
||
private readonly List<PackageJsonTestContainer> containers = new List<PackageJsonTestContainer>(); | ||
private readonly object containerLock = new object(); | ||
|
||
private IWorkspace activeWorkspace; | ||
|
||
[ImportingConstructor] | ||
public PackageJsonTestContainerDiscoverer(IVsFolderWorkspaceService workspaceService) | ||
{ | ||
this.workspaceService = workspaceService; | ||
this.workspaceService.OnActiveWorkspaceChanged += this.OnActiveWorkspaceChangedAsync; | ||
|
||
if (this.workspaceService.CurrentWorkspace != null) | ||
{ | ||
this.activeWorkspace = this.workspaceService.CurrentWorkspace; | ||
this.RegisterEvents(); | ||
|
||
this.activeWorkspace.JTF.RunAsync(async () => | ||
{ | ||
// Yield so we don't do this now. Don't want to block the constructor. | ||
await Task.Yield(); | ||
// See if we have an update | ||
await AttemptUpdateAsync(); | ||
}); | ||
} | ||
} | ||
|
||
public Uri ExecutorUri => NodejsConstants.PackageJsonExecutorUri; | ||
|
||
public IEnumerable<ITestContainer> TestContainers | ||
{ | ||
get | ||
{ | ||
lock (this.containerLock) | ||
{ | ||
return this.containers.ToArray(); | ||
} | ||
} | ||
} | ||
|
||
public event EventHandler TestContainersUpdated; | ||
|
||
private async Task AttemptUpdateAsync() | ||
{ | ||
var workspace = this.activeWorkspace; | ||
if (workspace != null) | ||
{ | ||
var indexService = workspace.GetIndexWorkspaceService(); | ||
var filesDataValues = await indexService.GetFilesDataValuesAsync<string>(NodejsConstants.TestRootDataValueGuid); | ||
|
||
lock (this.containerLock) | ||
{ | ||
this.containers.Clear(); | ||
foreach (var dataValue in filesDataValues) | ||
{ | ||
var rootFilePath = workspace.MakeRooted(dataValue.Key); | ||
var testRoot = dataValue.Value.Where(f => f.Name == NodejsConstants.TestRootDataValueName).FirstOrDefault()?.Value; | ||
|
||
if (!string.IsNullOrEmpty(testRoot)) | ||
{ | ||
var testRootPath = workspace.MakeRooted(testRoot); | ||
this.containers.Add(new PackageJsonTestContainer(this, rootFilePath, testRootPath)); | ||
} | ||
} | ||
} | ||
} | ||
this.TestContainersUpdated?.Invoke(this, EventArgs.Empty); | ||
} | ||
|
||
private async Task OnActiveWorkspaceChangedAsync(object sender, EventArgs e) | ||
{ | ||
this.UnRegisterEvents(); | ||
|
||
this.activeWorkspace = this.workspaceService.CurrentWorkspace; | ||
|
||
this.RegisterEvents(); | ||
|
||
await AttemptUpdateAsync(); | ||
} | ||
|
||
private void RegisterEvents() | ||
{ | ||
var workspace = this.activeWorkspace; | ||
if (workspace != null) | ||
{ | ||
var fileWatcherService = workspace.GetFileWatcherService(); | ||
if (fileWatcherService != null) | ||
{ | ||
fileWatcherService.OnFileSystemChanged += this.FileSystemChangedAsync; | ||
} | ||
|
||
var indexService = workspace.GetIndexWorkspaceService(); | ||
if (indexService != null) | ||
{ | ||
indexService.OnFileScannerCompleted += this.FileScannerCompletedAsync; | ||
} | ||
} | ||
} | ||
|
||
private void UnRegisterEvents() | ||
{ | ||
var fileWatcherService = this.activeWorkspace?.GetFileWatcherService(); | ||
if (fileWatcherService != null) | ||
{ | ||
fileWatcherService.OnFileSystemChanged -= this.FileSystemChangedAsync; | ||
} | ||
|
||
var indexService = this.activeWorkspace?.GetIndexWorkspaceService(); | ||
if (indexService != null) | ||
{ | ||
indexService.OnFileScannerCompleted -= this.FileScannerCompletedAsync; | ||
} | ||
} | ||
|
||
private Task FileSystemChangedAsync(object sender, FileSystemEventArgs args) | ||
{ | ||
// We only need to raise the containers updated event in case a js file contained in the | ||
// test root is updated. | ||
// Any changes to the 'package.json' will be handled by the FileScannerCompleted event. | ||
if (IsJavaScriptFile(args.FullPath) || args.IsDirectoryChanged()) | ||
{ | ||
// use a flag so we don't raise the event while under the lock | ||
var testsUpdated = false; | ||
lock (this.containerLock) | ||
{ | ||
foreach (var container in this.containers) | ||
{ | ||
if (container.IsContained(args.FullPath)) | ||
{ | ||
container.IncreaseVersion(); | ||
testsUpdated = true; | ||
break; | ||
} | ||
} | ||
} | ||
if (testsUpdated) | ||
{ | ||
this.TestContainersUpdated?.Invoke(this, EventArgs.Empty); | ||
} | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
private async Task FileScannerCompletedAsync(object sender, FileScannerEventArgs args) | ||
{ | ||
await AttemptUpdateAsync(); | ||
} | ||
|
||
private static bool IsJavaScriptFile(string path) | ||
{ | ||
var ext = Path.GetExtension(path); | ||
if (StringComparer.OrdinalIgnoreCase.Equals(ext, ".js") || StringComparer.OrdinalIgnoreCase.Equals(ext, ".jsx")) | ||
{ | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.