diff --git a/misc/DthTestProjects/UpdateSearchPathSample/ext/Newtonsoft.Json/project.json b/misc/DthTestProjects/UpdateSearchPathSample/ext/Newtonsoft.Json/project.json new file mode 100644 index 000000000..baf23136c --- /dev/null +++ b/misc/DthTestProjects/UpdateSearchPathSample/ext/Newtonsoft.Json/project.json @@ -0,0 +1,8 @@ +{ + "version": "6.0.8", + "dependencies": { + }, + "frameworks": { + "dnx451": { } + } +} diff --git a/misc/DthTestProjects/UpdateSearchPathSample/home/global.json b/misc/DthTestProjects/UpdateSearchPathSample/home/global.json new file mode 100644 index 000000000..c6bd139a9 --- /dev/null +++ b/misc/DthTestProjects/UpdateSearchPathSample/home/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ + "src", + "../ext" + ] +} diff --git a/misc/DthTestProjects/UpdateSearchPathSample/home/src/MainProject/project.json b/misc/DthTestProjects/UpdateSearchPathSample/home/src/MainProject/project.json new file mode 100644 index 000000000..fb6c9c4e9 --- /dev/null +++ b/misc/DthTestProjects/UpdateSearchPathSample/home/src/MainProject/project.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "Newtonsoft.Json": "6.0.8" + }, + "frameworks": { + "dnx451": { } + } +} diff --git a/src/Microsoft.Dnx.DesignTimeHost/ApplicationContext.cs b/src/Microsoft.Dnx.DesignTimeHost/ApplicationContext.cs index 2f8097b3d..63d005112 100644 --- a/src/Microsoft.Dnx.DesignTimeHost/ApplicationContext.cs +++ b/src/Microsoft.Dnx.DesignTimeHost/ApplicationContext.cs @@ -396,7 +396,12 @@ private bool ResolveDependencies() // hasn't died yet TriggerProjectOutputsChanged(); - state = _projectStateResolver.Resolve(_appPath.Value, _configuration.Value, triggerBuildOutputs, triggerDependencies, ProtocolVersion); + state = _projectStateResolver.Resolve(_appPath.Value, + _configuration.Value, + triggerBuildOutputs, + triggerDependencies, + ProtocolVersion, + _remote.ProjectInformation?.ProjectSearchPaths); } if (state == null) diff --git a/src/Microsoft.Dnx.DesignTimeHost/ProjectStateResolver.cs b/src/Microsoft.Dnx.DesignTimeHost/ProjectStateResolver.cs index d4fa21bc0..48339f90d 100644 --- a/src/Microsoft.Dnx.DesignTimeHost/ProjectStateResolver.cs +++ b/src/Microsoft.Dnx.DesignTimeHost/ProjectStateResolver.cs @@ -13,6 +13,7 @@ using Microsoft.Dnx.DesignTimeHost.Models.OutgoingMessages; using Microsoft.Dnx.Runtime; using Microsoft.Dnx.Runtime.Common.Impl; +using Microsoft.Dnx.Runtime.Internals; using NuGet; namespace Microsoft.Dnx.DesignTimeHost @@ -32,7 +33,12 @@ public ProjectStateResolver(CompilationEngine compilationEngine, _applicationHostContextCreator = applicaitonHostContextCreator; } - public ProjectState Resolve(string appPath, string configuration, bool triggerBuildOutputs, bool triggerDependencies, int protocolVersion) + public ProjectState Resolve(string appPath, + string configuration, + bool triggerBuildOutputs, + bool triggerDependencies, + int protocolVersion, + IList currentSearchPaths) { var state = new ProjectState { @@ -72,9 +78,16 @@ public ProjectState Resolve(string appPath, string configuration, bool triggerBu var sourcesProjectWidesources = project.Files.SourceFiles.ToList(); + ResolveSearchPaths(state); + foreach (var frameworkName in frameworks) { - var dependencyInfo = ResolveProjectDependencies(project, configuration, frameworkName, protocolVersion); + var dependencyInfo = ResolveProjectDependencies(project, + configuration, + frameworkName, + protocolVersion, + GetUpdatedSearchPaths(currentSearchPaths, state.ProjectSearchPaths)); + var dependencySources = new List(sourcesProjectWidesources); var frameworkData = new FrameworkData @@ -121,43 +134,16 @@ public ProjectState Resolve(string appPath, string configuration, bool triggerBu }; state.Projects.Add(projectInfo); - - GlobalSettings settings = null; - - if (state.GlobalJsonPath == null) - { - var root = ProjectRootResolver.ResolveRootDirectory(project.ProjectDirectory); - - if (GlobalSettings.TryGetGlobalSettings(root, out settings)) - { - state.GlobalJsonPath = settings.FilePath; - } - } - - if (state.ProjectSearchPaths == null) - { - var searchPaths = new HashSet() - { - Directory.GetParent(project.ProjectDirectory).FullName - }; - - if (settings != null) - { - foreach (var searchPath in settings.ProjectSearchPaths) - { - var path = Path.Combine(settings.DirectoryPath, searchPath); - searchPaths.Add(Path.GetFullPath(path)); - } - } - - state.ProjectSearchPaths = searchPaths.ToList(); - } } return state; } - private DependencyInfo ResolveProjectDependencies(Project project, string configuration, FrameworkName frameworkName, int protocolVersion) + private DependencyInfo ResolveProjectDependencies(Project project, + string configuration, + FrameworkName frameworkName, + int protocolVersion, + List updatedSearchPath) { var cacheKey = Tuple.Create("DependencyInfo", project.Name, configuration, frameworkName); @@ -178,10 +164,19 @@ private DependencyInfo ResolveProjectDependencies(Project project, string config }; var diagnosticSources = info.Diagnostics.ToLookup(diagnostic => diagnostic.Source); + var projectCandiates = GetProjectCandidates(updatedSearchPath); foreach (var library in applicationHostContext.LibraryManager.GetLibraryDescriptions()) { - var diagnostics = diagnosticSources[library]; + var diagnostics = diagnosticSources[library].ToList(); + + var newDiagnostic = ValidateDependency(library, projectCandiates); + if (newDiagnostic != null) + { + info.Diagnostics.Add(newDiagnostic); + diagnostics.Add(newDiagnostic); + } + var description = CreateDependencyDescription(library, diagnostics, protocolVersion); info.Dependencies[description.Name] = description; @@ -257,6 +252,81 @@ private DependencyInfo ResolveProjectDependencies(Project project, string config }); } + private DiagnosticMessage ValidateDependency(LibraryDescription library, HashSet projectCandidates) + { + if (!library.Resolved || projectCandidates == null) + { + return null; + } + + var foundCandidate = projectCandidates.Contains(library.Identity.Name); + + if ((library.Type == LibraryTypes.Project && !foundCandidate) || + (library.Type == LibraryTypes.Package && foundCandidate)) + { + library.Resolved = false; + library.Type = LibraryTypes.Unresolved; + + return new DiagnosticMessage( + DiagnosticMonikers.NU1010, + $"The type of dependency {library.Identity.Name} was changed.", + library.RequestedRange.FileName, + DiagnosticMessageSeverity.Error, + library.RequestedRange.Line, + library.RequestedRange.Column, + library); + } + + return null; + } + + private static HashSet GetProjectCandidates(IEnumerable searchPaths) + { + if (searchPaths == null) + { + return null; + } + + return new HashSet(searchPaths.Where(path => Directory.Exists(path)) + .SelectMany(path => Directory.GetDirectories(path)) + .Where(path => File.Exists(Path.Combine(path, Project.ProjectFileName))) + .Select(path => Path.GetFileName(path))); + } + + private static void ResolveSearchPaths(ProjectState state) + { + GlobalSettings settings = null; + + if (state.GlobalJsonPath == null) + { + var root = ProjectRootResolver.ResolveRootDirectory(state.Project.ProjectDirectory); + + if (GlobalSettings.TryGetGlobalSettings(root, out settings)) + { + state.GlobalJsonPath = settings.FilePath; + } + } + + if (state.ProjectSearchPaths == null) + { + var searchPaths = new HashSet() + { + Directory.GetParent(state.Project.ProjectDirectory).FullName + }; + + if (settings != null) + { + foreach (var searchPath in settings.ProjectSearchPaths) + { + var path = Path.Combine(settings.DirectoryPath, searchPath); + searchPaths.Add(Path.GetFullPath(path)); + } + } + + state.ProjectSearchPaths = searchPaths.ToList(); + } + } + private static DependencyDescription CreateDependencyDescription(LibraryDescription library, IEnumerable diagnostics, int protocolVersion) @@ -292,5 +362,25 @@ private static string GetProjectRelativeFullPath(Project referencedProject, stri { return Path.GetFullPath(Path.Combine(referencedProject.ProjectDirectory, path)); } + + /// + /// Returns the search paths if they're updated. Otherwise returns null. + /// + private static List GetUpdatedSearchPaths(IList oldSearchPaths, List newSearchPaths) + { + // The oldSearchPaths is null when the current project is not initialized. It is not necessary to + // validate the dependency in this case. + if (oldSearchPaths == null) + { + return null; + } + + if (Enumerable.SequenceEqual(oldSearchPaths, newSearchPaths)) + { + return null; + } + + return newSearchPaths; + } } } diff --git a/src/Microsoft.Dnx.Runtime.Internals/DiagnosticMonikers.cs b/src/Microsoft.Dnx.Runtime.Internals/DiagnosticMonikers.cs index 2ec6a23c3..e2e6b5dff 100644 --- a/src/Microsoft.Dnx.Runtime.Internals/DiagnosticMonikers.cs +++ b/src/Microsoft.Dnx.Runtime.Internals/DiagnosticMonikers.cs @@ -31,5 +31,8 @@ internal class DiagnosticMonikers // The expected lock file doesn't exist. Please run \"dnu restore\" to generate a new lock file. public static readonly string NU1009 = nameof(NU1009); + + // The dependency type was changed + public static readonly string NU1010 = nameof(NU1010); } } diff --git a/src/Microsoft.Dnx.Runtime/DependencyManagement/LibraryDescription.cs b/src/Microsoft.Dnx.Runtime/DependencyManagement/LibraryDescription.cs index cfb47a628..ddf266278 100644 --- a/src/Microsoft.Dnx.Runtime/DependencyManagement/LibraryDescription.cs +++ b/src/Microsoft.Dnx.Runtime/DependencyManagement/LibraryDescription.cs @@ -34,7 +34,7 @@ public LibraryDescription( public LibraryRange RequestedRange { get; set; } public LibraryIdentity Identity { get; } - public string Type { get; } + public string Type { get; set; } public FrameworkName Framework { get; set; } public string Path { get; set; } diff --git a/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/DthStartupTests.cs b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/DthStartupTests.cs index 001ab00f1..8ec92ed84 100644 --- a/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/DthStartupTests.cs +++ b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/DthStartupTests.cs @@ -7,8 +7,10 @@ using System.Linq; using Microsoft.Dnx.CommonTestUtils; using Microsoft.Dnx.DesignTimeHost.FunctionalTests.Infrastructure; +using Microsoft.Dnx.DesignTimeHost.FunctionalTests.Util; using Microsoft.Dnx.Runtime; using Microsoft.Dnx.Testing; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; @@ -68,19 +70,23 @@ public void DthStartup_GetProjectInformation(DnxSdk sdk) { client.Initialize(testProject); - var response = client.DrainTillFirst("ProjectInformation"); - response.EnsureSource(server, client); + var projectInformation = client.DrainAllMessages() + .RetrieveSingleMessage("ProjectInformation") + .EnsureSource(server, client) + .RetrievePayloadAs() + .AssertProperty("Name", projectName); + + projectInformation.RetrievePropertyAs("Configurations") + .AssertJArrayCount(2) + .AssertJArrayContains("Debug") + .AssertJArrayContains("Release"); - var projectInfo = response.Payload; - Assert.Equal(projectName, projectInfo["Name"]); - Assert.Equal(2, projectInfo["Configurations"].Count()); - Assert.Contains("Debug", projectInfo["Configurations"]); - Assert.Contains("Release", projectInfo["Configurations"]); + var frameworkShortNames = projectInformation.RetrievePropertyAs("Frameworks") + .AssertJArrayCount(2) + .Select(f => f["ShortName"].Value()); - var frameworkShorNames = projectInfo["Frameworks"].Select(f => f["ShortName"].Value()); - Assert.Equal(2, frameworkShorNames.Count()); - Assert.Contains("dnxcore50", frameworkShorNames); - Assert.Contains("dnx451", frameworkShorNames); + Assert.Contains("dnxcore50", frameworkShortNames); + Assert.Contains("dnx451", frameworkShortNames); } } @@ -136,19 +142,16 @@ public void DthCompilation_GetDiagnostics_OnEmptyConsoleApp(DnxSdk sdk, int prot client.Initialize(testProject, protocolVersion); client.SendPayLoad("GetDiagnostics"); - var message = client.DrainTillFirst("AllDiagnostics"); - message.EnsureSource(server, client); - var payload = (message.Payload as JArray)?.OfType(); - Assert.NotNull(payload); - Assert.Equal(3, payload.Count()); + var diagnosticsGroup = client.DrainTillFirst("AllDiagnostics") + .EnsureSource(server, client) + .RetrievePayloadAs() + .AssertJArrayCount(3); - foreach (var df in payload) + foreach (var group in diagnosticsGroup) { - var errors = (JArray)df["Errors"]; - Assert.False(errors.Any(), $"Unexpected compilation errors {string.Join(", ", errors.Select(e => e.Value()))}"); - - var warnings = (JArray)df["Warnings"]; - Assert.False(warnings.Any(), $"Unexpected compilation warnings {string.Join(", ", warnings.Select(e => e.Value()))}"); + group.AsJObject() + .AssertProperty("Errors", errorsArray => !errorsArray.Any()) + .AssertProperty("Warnings", warningsArray => !warningsArray.Any()); } } } @@ -167,9 +170,9 @@ public void DthCompilation_RestoreComplete_OnEmptyLibrary(DnxSdk sdk, int protoc // Drain the inital messages client.Initialize(testProject, protocolVersion); - var before = client.DrainTillFirst("Dependencies"); - before.EnsureSource(server, client); - Assert.Null(before.Payload["Dependencies"]["System.Console"]); + client.DrainTillFirst("Dependencies") + .EnsureSource(server, client) + .EnsureNotContainDependency("System.Console"); File.Copy(Path.Combine(testProject, "project-update.json"), Path.Combine(testProject, "project.json"), @@ -179,9 +182,9 @@ public void DthCompilation_RestoreComplete_OnEmptyLibrary(DnxSdk sdk, int protoc client.SendPayLoad("RestoreComplete"); - var after = client.DrainTillFirst("Dependencies"); - after.EnsureSource(server, client); - Assert.NotNull(after.Payload["Dependencies"]["System.Console"]); + client.DrainTillFirst("Dependencies") + .EnsureSource(server, client) + .RetrieveDependency("System.Console"); } } @@ -226,51 +229,47 @@ public void DthCompilation_Initialize_UnresolvedDependency(DnxSdk sdk, int proto { client.Initialize(testProject, protocolVersion); - var dependenciesMessage = client.DrainTillFirst("Dependencies"); - dependenciesMessage.EnsureSource(server, client); - - var dependencies = dependenciesMessage.Payload["Dependencies"]; - Assert.NotNull(dependencies); - - var unresolveDependency = dependencies[expectedUnresolvedDependency]; - Assert.NotNull(unresolveDependency); + var messages = client.DrainAllMessages(); - Assert.Equal(expectedUnresolvedDependency, unresolveDependency["Name"]); - Assert.Equal(expectedUnresolvedDependency, unresolveDependency["DisplayName"]); - Assert.False(unresolveDependency["Resolved"].Value()); - Assert.Equal(expectedUnresolvedType, unresolveDependency["Type"].Value()); + var unresolveDependency = messages.RetrieveSingleMessage("Dependencies") + .EnsureSource(server, client) + .RetrieveDependency(expectedUnresolvedDependency); + unresolveDependency.AssertProperty("Name", expectedUnresolvedDependency) + .AssertProperty("DisplayName", expectedUnresolvedDependency) + .AssertProperty("Resolved", false) + .AssertProperty("Type", expectedUnresolvedType); if (expectedUnresolvedType == "Project") { - Assert.Equal( - Path.Combine(Path.GetDirectoryName(testProject), expectedUnresolvedDependency, Project.ProjectFileName), - unresolveDependency["Path"].Value()); + unresolveDependency.AssertProperty( + "Path", + Path.Combine(Path.GetDirectoryName(testProject), expectedUnresolvedDependency, Project.ProjectFileName)); } else { Assert.False(unresolveDependency["Path"].HasValues); } - var referencesMessage = client.DrainTillFirst("References"); - referencesMessage.EnsureSource(server, client); + var referencesMessage = messages.RetrieveSingleMessage("References") + .EnsureSource(server, client); if (referenceType == "Project") { - var projectReferences = (JArray)referencesMessage.Payload["ProjectReferences"]; - Assert.NotNull(projectReferences); - - var projectReference = (JObject)projectReferences.Single(); var expectedUnresolvedProjectPath = Path.Combine(Path.GetDirectoryName(testProject), expectedUnresolvedDependency, Project.ProjectFileName); - Assert.Equal(expectedUnresolvedDependency, projectReference["Name"]); - Assert.Equal(expectedUnresolvedProjectPath, projectReference["Path"]); - Assert.False(projectReference["WrappedProjectPath"].HasValues); + referencesMessage.RetrievePayloadAs() + .RetrievePropertyAs("ProjectReferences") + .AssertJArrayCount(1) + .RetrieveArraryElementAs(0) + .AssertProperty("Name", expectedUnresolvedDependency) + .AssertProperty("Path", expectedUnresolvedProjectPath) + .AssertProperty("WrappedProjectPath", prop => !prop.HasValues); } else if (referenceType == "Package") { - var projectReferences = (JArray)referencesMessage.Payload["ProjectReferences"]; - Assert.NotNull(projectReferences); - Assert.False(projectReferences.Any()); + referencesMessage.RetrievePayloadAs() + .RetrievePropertyAs("ProjectReferences") + .AssertJArrayCount(0); } } } @@ -290,22 +289,19 @@ public void DthNegative_BrokenProjectPathInLockFile_V1(DnxSdk sdk) Testing.TestUtils.CopyFolder(testProject, targetPath); client.Initialize(targetPath, protocolVersion: 1); - var messages = client.DrainAllMessages(); + var messages = client.DrainAllMessages() + .NotContainMessage("Error"); - Assert.False(ContainsMessage(messages, "Error")); + var error = messages.RetrieveSingleMessage("DependencyDiagnostics") + .RetrieveDependencyDiagnosticsCollection() + .RetrieveDependencyDiagnosticsErrorAt(0); - var dependencyDiagnosticsMessage = RetrieveSingle(messages, "DependencyDiagnostics"); - dependencyDiagnosticsMessage.EnsureSource(server, client); - var errors = (JArray)dependencyDiagnosticsMessage.Payload["Errors"]; - Assert.Equal(1, errors.Count); - Assert.Contains("error NU1001: The dependency EmptyLibrary could not be resolved.", errors[0].Value()); + Assert.Contains("error NU1001: The dependency EmptyLibrary could not be resolved.", error.Value()); - var dependenciesMessage = RetrieveSingle(messages, "Dependencies"); - dependenciesMessage.EnsureSource(server, client); - var dependency = dependenciesMessage.Payload["Dependencies"]["EmptyLibrary"] as JObject; - Assert.NotNull(dependency); - Assert.Equal("EmptyLibrary", dependency["Name"].Value()); - Assert.False(dependency["Resolved"].Value()); + messages.RetrieveSingleMessage("Dependencies") + .RetrieveDependency("EmptyLibrary") + .AssertProperty("Name", "EmptyLibrary") + .AssertProperty("Resolved", false); } } @@ -324,62 +320,87 @@ public void DthNegative_BrokenProjectPathInLockFile_V2(DnxSdk sdk) Testing.TestUtils.CopyFolder(testProject, targetPath); client.Initialize(targetPath, protocolVersion: 2); - var messages = client.DrainAllMessages(); - - Assert.False(ContainsMessage(messages, "Error")); - - var dependencyDiagnosticsMessage = RetrieveSingle(messages, "DependencyDiagnostics"); - dependencyDiagnosticsMessage.EnsureSource(server, client); - var errors = (JArray)dependencyDiagnosticsMessage.Payload["Errors"]; - Assert.Equal(1, errors.Count); - - var formattedMessage = errors[0]["FormattedMessage"]; - Assert.NotNull(formattedMessage); - Assert.Contains("error NU1001: The dependency EmptyLibrary could not be resolved.", formattedMessage.Value()); - - var source = errors[0]["Source"] as JObject; - Assert.NotNull(source); - Assert.Equal("EmptyLibrary", source["Name"].Value()); - - var dependenciesMessage = RetrieveSingle(messages, "Dependencies"); - dependenciesMessage.EnsureSource(server, client); - var dependency = dependenciesMessage.Payload["Dependencies"]["EmptyLibrary"] as JObject; - Assert.NotNull(dependency); - Assert.Equal("EmptyLibrary", dependency["Name"].Value()); - Assert.False(dependency["Resolved"].Value()); - - var dependencyErrors = dependency["Errors"] as JArray; - Assert.NotNull(dependencyErrors); - Assert.Equal(1, dependencyErrors.Count); - - var dependencyWarnings = dependency["Warnings"] as JArray; - Assert.NotNull(dependencyWarnings); - Assert.Equal(0, dependencyWarnings.Count); + var messages = client.DrainAllMessages() + .NotContainMessage("Error"); + + messages.RetrieveSingleMessage("DependencyDiagnostics") + .RetrieveDependencyDiagnosticsCollection() + .RetrieveDependencyDiagnosticsErrorAt(0) + .AssertProperty("FormattedMessage", message => message.Contains("error NU1001: The dependency EmptyLibrary could not be resolved.")) + .RetrievePropertyAs("Source") + .AssertProperty("Name", "EmptyLibrary"); + + messages.RetrieveSingleMessage("Dependencies") + .RetrieveDependency("EmptyLibrary") + .AssertProperty("Errors", errorsArray => errorsArray.Count == 1) + .AssertProperty("Warnings", warningsArray => warningsArray.Count == 0) + .AssertProperty("Name", "EmptyLibrary") + .AssertProperty("Resolved", false); } } - private bool ContainsMessage(IEnumerable messages, string typename) + [Theory] + [MemberData(nameof(DnxSdks))] + public void DthDependencies_UpdateGlobalJson_RefreshDependencies(DnxSdk sdk) { - return messages.FirstOrDefault(msg => string.Equals(msg.MessageType, typename, StringComparison.Ordinal)) != null; - } + using (var disposableDir = new DisposableDir()) + using (var server = DthTestServer.Create(sdk, null)) + using (var client = new DthTestClient(server, 0)) + { + Testing.TestUtils.CopyFolder( + _fixture.GetTestProjectPath("UpdateSearchPathSample"), + Path.Combine(disposableDir, "UpdateSearchPathSample")); - private DthMessage RetrieveSingle(IEnumerable messages, string typename) - { - var result = messages.SingleOrDefault(msg => string.Equals(msg.MessageType, typename, StringComparison.Ordinal)); + var root = Path.Combine(disposableDir, "UpdateSearchPathSample", "home"); + sdk.Dnu.Restore(root).EnsureSuccess(); - if (result == null) - { - if (ContainsMessage(messages, typename)) - { - Assert.False(true, $"More than one {typename} messages exist."); - } - else - { - Assert.False(true, $"{typename} message doesn't exists."); - } - } + client.Initialize(Path.Combine(root, "src", "MainProject"), protocolVersion: 2); + var messages = client.DrainAllMessages(); - return result; + messages.RetrieveSingleMessage("ProjectInformation") + .RetrievePayloadAs() + .RetrievePropertyAs("ProjectSearchPaths") + .AssertJArrayCount(2); + + messages.RetrieveSingleMessage("Dependencies") + .RetrieveDependency("Newtonsoft.Json") + .AssertProperty("Type", "Project") + .AssertProperty("Resolved", true) + .AssertProperty("Errors", array => array.Count == 0, _ => "Dependency shouldn't contain any error."); + + messages.RetrieveSingleMessage("DependencyDiagnostics") + .RetrievePayloadAs() + .AssertProperty("Errors", array => array.Count == 0) + .AssertProperty("Warnings", array => array.Count == 0); + + // Overwrite the global.json to remove search path to ext + File.WriteAllText( + Path.Combine(root, GlobalSettings.GlobalFileName), + JsonConvert.SerializeObject(new { project = new string[] { "src" } })); + + client.SendPayLoad("RefreshDependencies"); + messages = client.DrainAllMessages(); + + messages.RetrieveSingleMessage("ProjectInformation") + .RetrievePayloadAs() + .RetrievePropertyAs("ProjectSearchPaths") + .AssertJArrayCount(1) + .AssertJArrayElement(0, Path.Combine(root, "src")); + + messages.RetrieveSingleMessage("Dependencies") + .RetrieveDependency("Newtonsoft.Json") + .AssertProperty("Type", LibraryTypes.Unresolved) + .AssertProperty("Resolved", false) + .RetrievePropertyAs("Errors") + .AssertJArrayCount(1) + .RetrieveArraryElementAs(0) + .AssertProperty("ErrorCode", "NU1010"); + + messages.RetrieveSingleMessage("DependencyDiagnostics") + .RetrieveDependencyDiagnosticsCollection() + .RetrieveDependencyDiagnosticsErrorAt(0) + .AssertProperty("ErrorCode", "NU1010"); + } } } } diff --git a/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Infrastructure/DthMessage.cs b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Infrastructure/DthMessage.cs index f0f5a2fa4..d956d47a9 100644 --- a/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Infrastructure/DthMessage.cs +++ b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Infrastructure/DthMessage.cs @@ -17,21 +17,5 @@ public class DthMessage public int Version { get; set; } public JToken Payload { get; set; } - - /// - /// Throws if the message is not generated in communication between given server and client - /// - public void EnsureSource(DthTestServer server, DthTestClient client) - { - if (HostId != server.HostId) - { - throw new Exception($"{nameof(HostId)} doesn't match the one of server. Expected {server.HostId} but actually {HostId}."); - } - - if (ContextId != client.ContextId) - { - throw new Exception($"{nameof(ContextId)} doesn't match the one of client. Expected {client.ContextId} but actually {ContextId}."); - } - } } } diff --git a/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Infrastructure/DthTestServer.cs b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Infrastructure/DthTestServer.cs index 68b3f09ff..ebdd49feb 100644 --- a/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Infrastructure/DthTestServer.cs +++ b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Infrastructure/DthTestServer.cs @@ -34,9 +34,9 @@ public static DthTestServer Create(DnxSdk sdk, string projectPath, TimeSpan time var processStartInfo = new ProcessStartInfo() { UseShellExecute = false, - WorkingDirectory = projectPath, + WorkingDirectory = sdk.Location, FileName = bootstraperExe, - Arguments = $"--appbase \"{projectPath}\" \"{dthPath}\" {port} {Process.GetCurrentProcess().Id} {hostId}", + Arguments = $"--appbase \"{sdk.Location}\" \"{dthPath}\" {port} {Process.GetCurrentProcess().Id} {hostId}", RedirectStandardOutput = true, RedirectStandardError = true }; diff --git a/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/DthMessageCollectionExtension.cs b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/DthMessageCollectionExtension.cs new file mode 100644 index 000000000..4bd8075c8 --- /dev/null +++ b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/DthMessageCollectionExtension.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. 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.Linq; +using Microsoft.Dnx.DesignTimeHost.FunctionalTests.Infrastructure; +using Xunit; + +namespace Microsoft.Dnx.DesignTimeHost.FunctionalTests.Util +{ + public static class DthMessageCollectionExtension + { + public static DthMessage RetrieveSingleMessage(this IEnumerable messages, + string typename) + { + var result = messages.SingleOrDefault(msg => string.Equals(msg.MessageType, typename, StringComparison.Ordinal)); + + if (result == null) + { + if (messages.FirstOrDefault(msg => string.Equals(msg.MessageType, typename, StringComparison.Ordinal)) != null) + { + Assert.False(true, $"More than one {typename} messages exist."); + } + else + { + Assert.False(true, $"{typename} message doesn't exists."); + } + } + + return result; + } + + public static IEnumerable ContainsMessage(this IEnumerable messages, + string typename) + { + var contain = messages.FirstOrDefault(msg => string.Equals(msg.MessageType, typename, StringComparison.Ordinal)) != null; + + Assert.True(contain, $"Messages collection doesn't contain message of type {typename}."); + + return messages; + } + + public static IEnumerable NotContainMessage(this IEnumerable messages, string typename) + { + var notContain = messages.FirstOrDefault(msg => string.Equals(msg.MessageType, typename, StringComparison.Ordinal)) == null; + + Assert.True(notContain, $"Message collection contains message of type {typename}."); + + return messages; + } + } +} diff --git a/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/DthMessageExtension.cs b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/DthMessageExtension.cs new file mode 100644 index 000000000..0ebda417b --- /dev/null +++ b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/DthMessageExtension.cs @@ -0,0 +1,84 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Dnx.DesignTimeHost.FunctionalTests.Infrastructure; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dnx.DesignTimeHost.FunctionalTests.Util +{ + public static class DthMessageExtension + { + public static JObject RetrieveDependency(this DthMessage message, string dependencyName) + { + Assert.NotNull(message); + Assert.Equal("Dependencies", message.MessageType); + + var payload = message.Payload as JObject; + Assert.NotNull(payload); + + var dependency = payload["Dependencies"][dependencyName] as JObject; + Assert.NotNull(dependency); + Assert.Equal(dependencyName, dependency["Name"].Value()); + + return dependency; + } + + public static DthMessage EnsureNotContainDependency(this DthMessage message, string dependencyName) + { + Assert.NotNull(message); + Assert.Equal("Dependencies", message.MessageType); + + var payload = message.Payload as JObject; + Assert.NotNull(payload); + + Assert.True(payload["Dependencies"][dependencyName] == null, $"Unexpected dependency {dependencyName} exists."); + + return message; + } + + public static JObject RetrieveDependencyDiagnosticsCollection(this DthMessage message) + { + Assert.NotNull(message); + Assert.Equal("DependencyDiagnostics", message.MessageType); + + var payload = message.Payload as JObject; + Assert.NotNull(payload); + + return payload; + } + + public static T RetrievePayloadAs(this DthMessage message) + where T : JToken + { + Assert.NotNull(message); + AssertType(message.Payload, "Payload"); + + return (T)message.Payload; + } + + /// + /// Throws if the message is not generated in communication between given server and client + /// + public static DthMessage EnsureSource(this DthMessage message, DthTestServer server, DthTestClient client) + { + if (message.HostId != server.HostId) + { + throw new Exception($"{nameof(message.HostId)} doesn't match the one of server. Expected {server.HostId} but actually {message.HostId}."); + } + + if (message.ContextId != client.ContextId) + { + throw new Exception($"{nameof(message.ContextId)} doesn't match the one of client. Expected {client.ContextId} but actually {message.ContextId}."); + } + + return message; + } + + public static void AssertType(object obj, string name) + { + Assert.True(obj is T, $"{name} is not of type {typeof(T).Name}."); + } + } +} diff --git a/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/JArrayExtensions.cs b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/JArrayExtensions.cs new file mode 100644 index 000000000..ce9070d55 --- /dev/null +++ b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/JArrayExtensions.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dnx.DesignTimeHost.FunctionalTests.Util +{ + public static class JArrayExtensions + { + public static JArray AssertJArrayCount(this JArray array, int expectedCount) + { + Assert.NotNull(array); + Assert.Equal(expectedCount, array.Count); + + return array; + } + + public static JArray AssertJArrayElement(this JArray array, int index, T expectedElementValue) + { + Assert.NotNull(array); + + var element = array[index]; + Assert.NotNull(element); + Assert.Equal(expectedElementValue, element.Value()); + + return array; + } + + public static JArray AssertJArrayContains(this JArray array, T value) + { + AssertJArrayContains(array, element => object.Equals(element, value)); + + return array; + } + + public static JArray AssertJArrayContains(this JArray array, Func critiera) + { + bool contains = false; + foreach (var element in array) + { + var value = element.Value(); + + contains = critiera(value); + if (contains) + { + break; + } + } + + Assert.True(contains, "JArray doesn't contains the specified element."); + + return array; + } + + public static T RetrieveArraryElementAs(this JArray json, int index) + where T : JToken + { + Assert.NotNull(json); + Assert.True(index >= 0 && index < json.Count, "Index out of range"); + + var element = json[index]; + DthMessageExtension.AssertType(element, $"Element at {index}"); + + return (T)element; + } + } +} diff --git a/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/JObjectExtensions.cs b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/JObjectExtensions.cs new file mode 100644 index 000000000..22c121ab8 --- /dev/null +++ b/test/Microsoft.Dnx.DesignTimeHost.FunctionalTests/Util/JObjectExtensions.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dnx.DesignTimeHost.FunctionalTests.Util +{ + public static class JObjectExtensions + { + public static JObject AsJObject(this JToken token) + { + DthMessageExtension.AssertType(token, nameof(JToken)); + + return (JObject)token; + } + + public static JObject RetrieveDependencyDiagnosticsErrorAt(this JObject payload, int index) + { + Assert.NotNull(payload); + + return payload.RetrievePropertyAs("Errors") + .RetrieveArraryElementAs(index); + } + + public static T RetrieveDependencyDiagnosticsErrorAt(this JObject payload, int index) + where T : JToken + { + Assert.NotNull(payload); + + return payload.RetrievePropertyAs("Errors") + .RetrieveArraryElementAs(index); + } + + public static T RetrievePropertyAs(this JObject json, string propertyName) + where T : JToken + { + Assert.NotNull(json); + + var property = json[propertyName]; + Assert.NotNull(property); + DthMessageExtension.AssertType(property, $"Property {propertyName}"); + + return (T)property; + } + + public static JObject AssertProperty(this JObject json, string propertyName, T expectedPropertyValue) + { + Assert.NotNull(json); + + var property = json[propertyName]; + Assert.NotNull(property); + Assert.Equal(expectedPropertyValue, property.Value()); + + return json; + } + + public static JObject AssertProperty(this JObject json, string propertyName, Func assertion) + { + return AssertProperty(json, + propertyName, + assertion, + value => $"Assert failed on {propertyName}."); + } + + public static JObject AssertProperty(this JObject json, string propertyName, Func assertion, Func errorMessage) + { + Assert.NotNull(json); + + var property = json[propertyName]; + Assert.False(property == null, $"Property {propertyName} doesn't exist."); + + var propertyValue = property.Value(); + Assert.False(propertyValue == null, $"Property {propertyName} of type {typeof(T).Name} doesn't exist."); + + Assert.True(assertion(propertyValue), + errorMessage(propertyValue)); + + return json; + } + } +}