Merged PR 562541: Add Yarn integration tests

Add Yarn integration tests and factor out some common logic between Rush and Yarn tests

Related work items: #1742851
This commit is contained in:
Serge Mera 2020-07-08 18:04:27 +00:00
Родитель 3e41d11e59
Коммит 8b76936efe
27 изменённых файлов: 650 добавлений и 133 удалений

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import {Transformer} from "Sdk.Transformers";
// This is an empty facade for a Microsoft internal package.
namespace Contents {
export declare const qualifier: {
};
@@public
export const all: StaticDirectory = Transformer.sealPartialDirectory(d`.`, []);
}

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
module({
name: "Npm.OnCloudBuild"
});

Просмотреть файл

@ -12,6 +12,14 @@ namespace Node {
@@public
export const npmCli = getNpmCli();
const nodeExecutablesDir : Directory = d`${getNodeTool().exe.parent}`;
/**
* Self-contained node executables. Platform dependent.
*/
@@public
export const nodeExecutables : StaticDirectory = Transformer.sealDirectory(nodeExecutablesDir, globR(nodeExecutablesDir));
@@public
export function run(args: Transformer.ExecuteArguments) : Transformer.ExecuteResult {
// Node code can access any of the following user specific environment variables.

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
module({
name: "Yarn"
});

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import {Transformer} from "Sdk.Transformers";
/**
* Returns a static directory containing a valid Yarn installation.
*/
@@public
export function getYarn() : StaticDirectory {
if (Environment.getFlag("[Sdk.BuildXL]microsoftInternal")) {
// Internally in Microsoft we use a nuget package that contains Yarn.
return Transformer.reSealPartialDirectory(importFrom("Npm.OnCloudBuild").Contents.all, r`tools/Yarn`);
}
// For the public build, we require Yarn to be installed
const installedYarnLocation = d`${Context.getMount("ProgramFilesX86").path}/Yarn`;
const packageJson = f`${installedYarnLocation}/package.json`;
if (!File.exists(packageJson))
{
Contract.fail(`Could not find Yarn installed. File '${packageJson.toDiagnosticString()}' does not exist.`);
}
return Transformer.sealDirectory(installedYarnLocation, globR(installedYarnLocation));
}

Просмотреть файл

@ -35,6 +35,8 @@ namespace JavaScript {
],
internalsVisibleTo: [
"Test.BuildXL.FrontEnd.Rush",
"Test.BuildXL.FrontEnd.Yarn",
"Test.BuildXL.FrontEnd.Core"
],
});
}

Просмотреть файл

@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.IO;
using System.Linq;
using BuildXL.Engine;
using BuildXL.FrontEnd.JavaScript;
using BuildXL.Utilities.Configuration;
using Pips = global::BuildXL.Pips.Operations;
namespace Test.BuildXL.FrontEnd.Core
{
/// <summary>
/// Some utilities for JavaScript-based tests
/// </summary>
public static class JavaScriptTestHelper
{
/// <summary>
/// <see cref="AddJavaScriptProject(SpecEvaluationBuilder, string, string, string, string[], (string, string)[])"/>
/// </summary>
public static SpecEvaluationBuilder AddJavaScriptProject(
this SpecEvaluationBuilder builder,
string packageName,
string packageFolder,
string content = null,
string[] dependencies = null,
(string, string)[] scriptCommands = null)
{
var dependenciesWithVersions = dependencies?.Select(dep => (dep, "0.0.1"))?.ToArray();
return AddJavaScriptProjectWithExplicitVersions(builder, packageName, packageFolder, content, dependenciesWithVersions, scriptCommands);
}
/// <summary>
/// Utility for adding a node spec together with a corresponding package.json
/// </summary>
public static SpecEvaluationBuilder AddJavaScriptProjectWithExplicitVersions(
this SpecEvaluationBuilder builder,
string packageName,
string packageFolder,
string content = null,
(string, string)[] dependenciesWithVersions = null,
(string, string)[] scriptCommands = null)
{
return builder
.AddSpec(Path.Combine(packageFolder, "main.js"), content ?? "function A(){}")
.AddSpec(Path.Combine(packageFolder, "package.json"),
CreatePackageJson(packageName, scriptCommands, dependenciesWithVersions ?? new (string, string)[] { }));
}
/// <summary>
/// Persists a Bxl configuration file at the given path
/// </summary>
public static SpecEvaluationBuilder AddBxlConfigurationFile(
this SpecEvaluationBuilder builder,
string path,
string content)
{
builder.AddFile(Path.Combine(path, JavaScriptWorkspaceResolver<DsTest, IJavaScriptResolverSettings>.BxlConfigurationFilename), content);
return builder;
}
/// <summary>
/// Uses the provenance set by the JavaScript scheduler to retrieve a process pip that corresponds to a given package name and script command
/// </summary>
/// <returns>Null if the process is not found</returns>
public static Pips.Process RetrieveProcess(this EngineState engineState, string packageName, string scriptCommand = null)
{
scriptCommand ??= "build";
var projectSymbol = JavaScriptPipConstructor.GetFullSymbolFromProject(packageName, scriptCommand, engineState.SymbolTable);
var processes = engineState.PipGraph.RetrievePipsOfType(Pips.PipType.Process);
return (Pips.Process)processes.FirstOrDefault(process => process.Provenance.OutputValueSymbol == projectSymbol);
}
public static string CreatePackageJson(
string projectName,
(string, string)[] scriptCommands = null,
(string dependency, string version)[] dependenciesWithVersions = null)
{
scriptCommands ??= new[] { ("build", "node ./main.js") };
dependenciesWithVersions ??= new (string, string)[] { };
return $@"
{{
""name"": ""{projectName}"",
""version"": ""0.0.1"",
""description"": ""Test project {projectName}"",
""scripts"": {{
{string.Join(",", scriptCommands.Select(kvp => $"\"{kvp.Item1}\": \"{kvp.Item2}\""))}
}},
""main"": ""main.js"",
""dependencies"": {{
{string.Join(",", dependenciesWithVersions.Select(depAndVer => $"\"{depAndVer.dependency}\":\"{depAndVer.version}\""))}
}}
}}
";
}
}
}

Просмотреть файл

@ -20,6 +20,7 @@ namespace Core {
importFrom("BuildXL.FrontEnd").Core.dll,
importFrom("BuildXL.FrontEnd").Script.dll,
importFrom("BuildXL.FrontEnd").Sdk.dll,
importFrom("BuildXL.FrontEnd").JavaScript.dll,
importFrom("BuildXL.FrontEnd").TypeScript.Net.dll,
importFrom("BuildXL.Pips").dll,
importFrom("BuildXL.Utilities").dll,

Просмотреть файл

@ -2,10 +2,9 @@
// Licensed under the MIT License.
using System.Linq;
using BuildXL.Engine;
using BuildXL.Utilities;
using BuildXL.Utilities.Configuration;
using Test.BuildXL.FrontEnd.Rush.IntegrationTests;
using Test.BuildXL.FrontEnd.Core;
using Xunit;
using Xunit.Abstractions;
using LogEventId = global::BuildXL.FrontEnd.JavaScript.Tracing.LogEventId;
@ -27,8 +26,8 @@ namespace Test.BuildXL.FrontEnd.Rush
public void SimpleRushConfigurationFileIsHonored()
{
var config = Build()
.AddRushProject("@ms/project-A", "src/A")
.AddRushConfigurationFile("src/A", @"
.AddJavaScriptProject("@ms/project-A", "src/A")
.AddBxlConfigurationFile("src/A", @"
{
""outputDirectories"": [""../output/dir""],
""sourceFiles"": [""input/file""]
@ -53,8 +52,8 @@ namespace Test.BuildXL.FrontEnd.Rush
public void PathPatternsAreHonored(string outputDirectoriesJSON, string[] expectedOutputDirectories)
{
var config = Build()
.AddRushProject("@ms/project-A", "src/A")
.AddRushConfigurationFile("src/A", @$"
.AddJavaScriptProject("@ms/project-A", "src/A")
.AddBxlConfigurationFile("src/A", @$"
{{
""outputDirectories"": {outputDirectoriesJSON}
}}")
@ -79,8 +78,8 @@ namespace Test.BuildXL.FrontEnd.Rush
{
// Create a project and schedule its 'build' and 'test' script. Only define output directories for 'build'
var config = Build(executeCommands: "['build', 'test']")
.AddRushProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "build A"), ("test", "test A")})
.AddRushConfigurationFile("src/A", @"
.AddJavaScriptProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "build A"), ("test", "test A")})
.AddBxlConfigurationFile("src/A", @"
{
""outputDirectories"": [{""path"": ""../output/dir"", ""targetScripts"": [""build""]}]
}")
@ -107,8 +106,8 @@ namespace Test.BuildXL.FrontEnd.Rush
public void MalformedConfigurationFileIsHandled(string outputDirectories)
{
var config = Build()
.AddRushProject("@ms/project-A", "src/A")
.AddRushConfigurationFile("src/A", @$"
.AddJavaScriptProject("@ms/project-A", "src/A")
.AddBxlConfigurationFile("src/A", @$"
{{
""outputDirectories"": [{outputDirectories}]
}}")

Просмотреть файл

@ -3,7 +3,7 @@
using BuildXL.Utilities;
using BuildXL.Utilities.Configuration;
using Test.BuildXL.FrontEnd.Rush.IntegrationTests;
using Test.BuildXL.FrontEnd.Core;
using Xunit;
using Xunit.Abstractions;
using LogEventId = global::BuildXL.FrontEnd.JavaScript.Tracing.LogEventId;
@ -30,7 +30,7 @@ namespace Test.BuildXL.FrontEnd.Rush
public void InvalidCustomRushCommands(string customCommands)
{
var config = Build(customRushCommands: customCommands)
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var result = RunRushProjects(config, new[] {
@ -48,7 +48,7 @@ namespace Test.BuildXL.FrontEnd.Rush
// Schedule a rush project with a build command 'execute', and extend it to be
// 'execute --test' via a custom command
var config = Build(customRushCommands: "[{command: 'build', extraArguments: '--test'}]")
.AddRushProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "execute") })
.AddJavaScriptProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "execute") })
.PersistSpecsAndGetConfiguration();
var result = RunRushProjects(config, new[] {
@ -67,8 +67,8 @@ namespace Test.BuildXL.FrontEnd.Rush
// Two projects, one with 'build', the other one with 'test'.
// Extend 'build'to be 'execute --test' via a custom command
var config = Build(customRushCommands: "[{command: 'build', extraArguments: '--test'}]", executeCommands: "['build', 'test']")
.AddRushProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "execute") })
.AddRushProject("@ms/project-B", "src/B", scriptCommands: new[] { ("test", "execute") })
.AddJavaScriptProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "execute") })
.AddJavaScriptProject("@ms/project-B", "src/B", scriptCommands: new[] { ("test", "execute") })
.PersistSpecsAndGetConfiguration();
var result = RunRushProjects(config, new[] {
@ -90,7 +90,7 @@ namespace Test.BuildXL.FrontEnd.Rush
{
// Exercise custom commands with other types
var config = Build(customRushCommands: "[{command: 'build', extraArguments: ['--test', a`atom`, r`relative/path`, p`C:/absolute/path`]}]")
.AddRushProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "execute") })
.AddJavaScriptProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "execute") })
.PersistSpecsAndGetConfiguration();
var result = RunRushProjects(config, new[] {

Просмотреть файл

@ -4,7 +4,7 @@
using System.Linq;
using BuildXL.Engine;
using BuildXL.Utilities.Configuration;
using Test.BuildXL.FrontEnd.Rush.IntegrationTests;
using Test.BuildXL.FrontEnd.Core;
using Xunit;
using Xunit.Abstractions;
using LogEventId = global::BuildXL.FrontEnd.JavaScript.Tracing.LogEventId;
@ -51,7 +51,7 @@ namespace Test.BuildXL.FrontEnd.Rush
// The script commands themselves are not important since nothing gets executed, just scheduled
var config = Build(executeCommands: "['build', 'sign', 'test']")
.AddRushProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "build A"), ("sign", "sign A"), ("test", "test A") })
.AddJavaScriptProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "build A"), ("sign", "sign A"), ("test", "test A") })
.PersistSpecsAndGetConfiguration();
var result = RunRushProjects(config, new[] {
@ -77,9 +77,9 @@ namespace Test.BuildXL.FrontEnd.Rush
// The script commands themselves are not important since nothing gets executed, just scheduled
// We should find B build depending on A build, and B test depending on B build
var config = Build(executeCommands: "['build', 'test']")
.AddRushProject("@ms/project-A", "src/A",
.AddJavaScriptProject("@ms/project-A", "src/A",
scriptCommands: new[] { ("build", "build A"), ("test", "test A") })
.AddRushProject("@ms/project-B", "src/B", dependencies: new[] { "@ms/project-A" },
.AddJavaScriptProject("@ms/project-B", "src/B", dependencies: new[] { "@ms/project-A" },
scriptCommands: new[] { ("build", "build B"), ("test", "test B") })
.PersistSpecsAndGetConfiguration();
@ -120,10 +120,10 @@ namespace Test.BuildXL.FrontEnd.Rush
]";
var config = Build(executeCommands: commands)
.AddRushProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "b A"), ("pre-build", "pr A") })
.AddRushProject("@ms/project-B", "src/B", scriptCommands: new[] { ("build", "b B"), ("pre-build", "pr B"), ("post-build", "ps B") },
.AddJavaScriptProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "b A"), ("pre-build", "pr A") })
.AddJavaScriptProject("@ms/project-B", "src/B", scriptCommands: new[] { ("build", "b B"), ("pre-build", "pr B"), ("post-build", "ps B") },
dependencies: new[] { "@ms/project-A" })
.AddRushProject("@ms/project-C", "src/C", scriptCommands: new[] { ("build", "b C"), ("pre-build", "pr C"), ("post-build", "ps C") },
.AddJavaScriptProject("@ms/project-C", "src/C", scriptCommands: new[] { ("build", "b C"), ("pre-build", "pr C"), ("post-build", "ps C") },
dependencies: new[] { "@ms/project-A" })
.PersistSpecsAndGetConfiguration();
@ -182,7 +182,7 @@ namespace Test.BuildXL.FrontEnd.Rush
private BuildXLEngineResult BuildDummyWithCommands(string commands)
{
var config = Build(executeCommands: commands)
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
return RunRushProjects(config, new[] {

Просмотреть файл

@ -1,9 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using BuildXL.Utilities;
using BuildXL.Utilities.Configuration;
using Test.BuildXL.FrontEnd.Rush.IntegrationTests;
using Test.BuildXL.FrontEnd.Core;
using Xunit;
using Xunit.Abstractions;
using LogEventId = global::BuildXL.FrontEnd.JavaScript.Tracing.LogEventId;
@ -27,7 +26,7 @@ namespace Test.BuildXL.FrontEnd.Rush
public void InvalidRushExportsSettings(string rushExports)
{
var config = Build(rushExports: rushExports)
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var result = RunRushProjects(config, new[] {
@ -44,7 +43,7 @@ namespace Test.BuildXL.FrontEnd.Rush
public void MissingPackageInExportsIsFlagged(string exports, LogEventId expectedError)
{
var config = Build(rushExports: exports)
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var result = RunRushProjects(config, new[] {
@ -60,7 +59,7 @@ namespace Test.BuildXL.FrontEnd.Rush
public void InvalidExportSymbolIsFlagged()
{
var config = Build(rushExports: "[{symbolName: 'invalid-symbol', content: []}]")
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var result = RunRushProjects(config, new[] {
@ -82,7 +81,7 @@ namespace Test.BuildXL.FrontEnd.Rush
rushExports: "[{symbolName: 'exportSymbol', content: ['@ms/project-A']}]",
moduleName: "rushTest",
addDScriptResolver: true)
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.AddSpec("module.config.dsc", "module({name: 'dscriptTest', nameResolutionSemantics: NameResolutionSemantics.implicitProjectReferences});")
// Consume 'exportSymbol' from DScript and make sure it is an array of shared opaques.
// Check as well the share opaque output is related to project A
@ -116,8 +115,8 @@ const assertion2 = Contract.assert(firstOutput.root.isWithin(d`src/A`));")
rushExports: $"[{{symbolName: 'exportSymbol', content: [{exportContent}]}}]",
moduleName: "rushTest",
addDScriptResolver: true)
.AddRushProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "call build"), ("test", "call test") })
.AddRushConfigurationFile("src/A", @"
.AddJavaScriptProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "call build"), ("test", "call test") })
.AddBxlConfigurationFile("src/A", @"
{
""outputDirectories"": [
{""path"": ""output/dir/for/build"", ""targetScripts"": [""build""]},
@ -148,7 +147,7 @@ import {{exportSymbol}} from 'rushTest';
Build(
moduleName: "rushTest",
addDScriptResolver: true)
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.AddSpec("module.config.dsc", "module({name: 'dscriptTest', nameResolutionSemantics: NameResolutionSemantics.implicitProjectReferences});")
// Consume 'all' from DScript
.AddSpec("import {all} from 'rushTest';")
@ -166,7 +165,7 @@ import {{exportSymbol}} from 'rushTest';
{
var config =
Build(rushExports: $"[{{symbolName: 'all', content: []}}]")
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var result = RunRushProjects(config, new[] {

Просмотреть файл

@ -223,13 +223,6 @@ namespace Test.BuildXL.FrontEnd.Rush
";
}
protected static bool IsDependencyAndDependent(global::BuildXL.Pips.Operations.Process dependency, global::BuildXL.Pips.Operations.Process dependent)
{
// Unfortunately the test pip graph we are using doesn't keep track of dependencies/dependents. So we check there is a directory output of the dependency
// that is a directory input for a dependent
return dependency.DirectoryOutputs.Any(directoryOutput => dependent.DirectoryDependencies.Any(directoryDependency => directoryDependency == directoryOutput));
}
private string DefaultRushPrelude(
Dictionary<string, DiscriminatingUnion<string, UnitValue>> environment,
string executeCommands,

Просмотреть файл

@ -13,7 +13,7 @@ using BuildXL.Utilities;
using BuildXL.Utilities.Configuration;
using BuildXL.Utilities.Configuration.Mutable;
using Test.BuildXL.EngineTestUtilities;
using Test.BuildXL.FrontEnd.Rush.IntegrationTests;
using Test.BuildXL.FrontEnd.Core;
using Xunit;
using Xunit.Abstractions;
@ -32,8 +32,8 @@ namespace Test.BuildXL.FrontEnd.Rush
{
// Create two projects A and B such that A -> B.
var config = Build()
.AddRushProject("@ms/project-A", "src/A", "module.exports = function A(){}")
.AddRushProject("@ms/project-B", "src/B", "const A = require('@ms/project-A'); return A();", new string[] { "@ms/project-A"})
.AddJavaScriptProject("@ms/project-A", "src/A", "module.exports = function A(){}")
.AddJavaScriptProject("@ms/project-B", "src/B", "const A = require('@ms/project-A'); return A();", new string[] { "@ms/project-A"})
.PersistSpecsAndGetConfiguration();
var engineResult = RunRushProjects(config, new[] {
@ -60,7 +60,7 @@ namespace Test.BuildXL.FrontEnd.Rush
var testCache = new TestCache();
var config = (CommandLineConfiguration)Build()
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
config.Cache.CacheGraph = true;
@ -107,7 +107,7 @@ namespace Test.BuildXL.FrontEnd.Rush
};
var config = (CommandLineConfiguration)Build(environment)
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
config.Cache.CacheGraph = true;
@ -150,7 +150,7 @@ namespace Test.BuildXL.FrontEnd.Rush
["Test"] = new DiscriminatingUnion<string, UnitValue>(UnitValue.Unit) };
var config = (CommandLineConfiguration)Build(environment)
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
config.Cache.CacheGraph = true;
@ -187,8 +187,8 @@ namespace Test.BuildXL.FrontEnd.Rush
{
// Create two projects with the same package name
var config = Build()
.AddRushProject("@ms/project-A", "src/A")
.AddRushProject("@ms/project-A", "src/B")
.AddJavaScriptProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/B")
.PersistSpecsAndGetConfiguration();
var engineResult = RunRushProjects(config, new[] {
@ -209,7 +209,7 @@ namespace Test.BuildXL.FrontEnd.Rush
// Run a project that writes a file under a nested directory
var config = (CommandLineConfiguration)Build()
.AddRushProjectWithExplicitVersions(
.AddJavaScriptProjectWithExplicitVersions(
"@ms/project-A",
"src/A",
"var fs = require('fs'); fs.mkdirSync('CamelCasedLib'); fs.writeFileSync('CamelCasedLib/out.txt', 'hello');",

Просмотреть файл

@ -1,75 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.IO;
using System.Linq;
using BuildXL.Engine;
using BuildXL.FrontEnd.Rush;
using Test.BuildXL.FrontEnd.Core;
using Xunit;
using Pips = global::BuildXL.Pips.Operations;
namespace Test.BuildXL.FrontEnd.Rush.IntegrationTests
{
/// <nodoc/>
public static class RushIntegrationTestsHelper
{
/// <summary>
/// <see cref="AddRushProject(SpecEvaluationBuilder, string, string, string, string[], (string, string)[])"/>
/// </summary>
public static SpecEvaluationBuilder AddRushProject(
this SpecEvaluationBuilder builder,
string packageName,
string packageFolder,
string content = null,
string[] dependencies = null,
(string, string)[] scriptCommands = null)
{
var dependenciesWithVersions = dependencies?.Select(dep => (dep, "0.0.1"))?.ToArray();
return AddRushProjectWithExplicitVersions(builder, packageName, packageFolder, content, dependenciesWithVersions, scriptCommands);
}
/// <summary>
/// Utility for adding a node spec together with a corresponding package.json
/// </summary>
public static SpecEvaluationBuilder AddRushProjectWithExplicitVersions(
this SpecEvaluationBuilder builder,
string packageName,
string packageFolder,
string content = null,
(string, string)[] dependenciesWithVersions = null,
(string, string)[] scriptCommands = null)
{
return builder
.AddSpec(Path.Combine(packageFolder, "main.js"), content ?? "function A(){}")
.AddSpec(Path.Combine(packageFolder, "package.json"),
RushIntegrationTestBase.CreatePackageJson(packageName, scriptCommands, dependenciesWithVersions ?? new (string, string)[] { }));
}
/// <summary>
/// Persists a rush configuration file at the given path
/// </summary>
public static SpecEvaluationBuilder AddRushConfigurationFile(
this SpecEvaluationBuilder builder,
string path,
string content)
{
builder.AddFile(Path.Combine(path, RushWorkspaceResolver.BxlConfigurationFilename), content);
return builder;
}
/// <summary>
/// Uses the provenance set by the rush scheduler to retrieve a process pip that corresponds to a given package name and script command
/// </summary>
/// <returns>Null if the process is not found</returns>
public static Pips.Process RetrieveProcess(this EngineState engineState, string packageName, string scriptCommand = null)
{
scriptCommand ??= "build";
var projectSymbol = RushPipConstructor.GetFullSymbolFromProject(packageName, scriptCommand, engineState.SymbolTable);
var processes = engineState.PipGraph.RetrievePipsOfType(Pips.PipType.Process);
return (Pips.Process)processes.FirstOrDefault(process => process.Provenance.OutputValueSymbol == projectSymbol);
}
}
}

Просмотреть файл

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using BuildXL.FrontEnd.JavaScript.Tracing;
using BuildXL.Utilities.Configuration;
using Test.BuildXL.FrontEnd.Core;
using Xunit;
using Xunit.Abstractions;
@ -25,7 +26,7 @@ namespace Test.BuildXL.FrontEnd.Rush.IntegrationTests
public void ExplicitInvalidRushLibLocationIsHandled()
{
var config = Build(rushBaseLibLocation: "/path/to/foo")
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var engineResult = RunRushProjects(config, new[] {
@ -53,7 +54,7 @@ namespace Test.BuildXL.FrontEnd.Rush.IntegrationTests
};
var config = Build(environment: environment, rushBaseLibLocation: null)
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var engineResult = RunRushProjects(config, new[] {
@ -69,7 +70,7 @@ namespace Test.BuildXL.FrontEnd.Rush.IntegrationTests
// Explicitly undefine the rush base lib location, but do not expose anything in PATH
// that points to a valid Rush installation
var config = Build(rushBaseLibLocation: null)
.AddRushProject("@ms/project-A", "src/A")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var engineResult = RunRushProjects(config, new[] {

Просмотреть файл

@ -3,14 +3,12 @@
using System.IO;
using System.Linq;
using BuildXL.Engine;
using BuildXL.Utilities;
using BuildXL.Utilities.Configuration;
using Test.BuildXL.FrontEnd.Rush.IntegrationTests;
using Test.BuildXL.FrontEnd.Core;
using Test.BuildXL.TestUtilities.Xunit;
using Xunit;
using Xunit.Abstractions;
using LogEventId = global::BuildXL.FrontEnd.Rush.Tracing.LogEventId;
namespace Test.BuildXL.FrontEnd.Rush
{
@ -31,7 +29,7 @@ namespace Test.BuildXL.FrontEnd.Rush
string commonTempFolder = Path.Combine(TestRoot, "CustomTempFolder").Replace("\\", "/"); ;
var config = Build(commonTempFolder: commonTempFolder)
.AddRushProject("@ms/project-A", "src/A").
.AddJavaScriptProject("@ms/project-A", "src/A").
PersistSpecsAndGetConfiguration();
var result = RunRushProjects(config, new[] {

Просмотреть файл

@ -167,5 +167,12 @@ namespace Test.DScript.Ast
return (FrontEndHostController)engine.FrontEndController;
}
protected static bool IsDependencyAndDependent(global::BuildXL.Pips.Operations.Process dependency, global::BuildXL.Pips.Operations.Process dependent)
{
// Unfortunately the test pip graph we are using doesn't keep track of dependencies/dependents. So we check there is a directory output of the dependency
// that is a directory input for a dependent
return dependency.DirectoryOutputs.Any(directoryOutput => dependent.DirectoryDependencies.Any(directoryDependency => directoryDependency == directoryOutput));
}
}
}

Просмотреть файл

@ -0,0 +1,219 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using BuildXL.Engine;
using BuildXL.Processes;
using BuildXL.Utilities;
using BuildXL.Utilities.Configuration;
using BuildXL.Utilities.Configuration.Mutable;
using Test.BuildXL.EngineTestUtilities;
using Test.BuildXL.FrontEnd.Core;
using Test.BuildXL.TestUtilities;
using Test.BuildXL.TestUtilities.Xunit;
using Test.DScript.Ast;
using Xunit.Abstractions;
namespace Test.BuildXL.FrontEnd.Yarn
{
/// <summary>
/// Provides facilities to run the engine adding Yarn specific artifacts.
/// </summary>
[TestClassIfSupported(requiresWindowsBasedOperatingSystem: true)]
public abstract class YarnIntegrationTestBase : DsTestWithCacheBase
{
/// <summary>
/// Keep in sync with deployment.
/// </summary>
protected string PathToYarn => Path.Combine(TestDeploymentDir, "yarn", "bin", "yarn").Replace("\\", "/");
/// <summary>
/// Keep in sync with deployment.
/// </summary>
protected string PathToNode => Path.Combine(TestDeploymentDir, "Node", OperatingSystemHelper.IsLinuxOS? "bin/node" : "node.exe").Replace("\\", "/");
/// <nodoc/>
protected string PathToNodeFolder => Path.GetDirectoryName(PathToNode).Replace("\\", "/");
/// <summary>
/// Default out dir to use in projects
/// </summary>
protected string OutDir { get; }
/// <summary>
/// Root to the source enlistment root
/// </summary>
protected string SourceRoot { get; }
// By default the engine runs e2e
protected virtual EnginePhases Phase => EnginePhases.Execute;
protected override bool DisableDefaultSourceResolver => true;
protected YarnIntegrationTestBase(ITestOutputHelper output) : base(output, true)
{
RegisterEventSource(global::BuildXL.Engine.ETWLogger.Log);
RegisterEventSource(global::BuildXL.Processes.ETWLogger.Log);
RegisterEventSource(global::BuildXL.Scheduler.ETWLogger.Log);
RegisterEventSource(global::BuildXL.Pips.ETWLogger.Log);
RegisterEventSource(global::BuildXL.FrontEnd.Core.ETWLogger.Log);
RegisterEventSource(global::BuildXL.FrontEnd.Script.ETWLogger.Log);
RegisterEventSource(global::BuildXL.FrontEnd.Yarn.ETWLogger.Log);
RegisterEventSource(global::BuildXL.FrontEnd.JavaScript.ETWLogger.Log);
SourceRoot = Path.Combine(TestRoot, RelativeSourceRoot);
OutDir = "target";
}
protected SpecEvaluationBuilder Build(
Dictionary<string, string> environment = null,
string yarnLocation = "",
string moduleName = "Test")
{
environment ??= new Dictionary<string, string> {
["PATH"] = PathToNodeFolder,
};
return Build(
environment.ToDictionary(kvp => kvp.Key, kvp => new DiscriminatingUnion<string, UnitValue>(kvp.Value)),
yarnLocation,
moduleName);
}
/// <inheritdoc/>
protected SpecEvaluationBuilder Build(
Dictionary<string, DiscriminatingUnion<string, UnitValue>> environment,
string yarnLocation = "",
string moduleName = "Test")
{
environment ??= new Dictionary<string, DiscriminatingUnion<string, UnitValue>> {
["PATH"] = new DiscriminatingUnion<string, UnitValue>(PathToNodeFolder),
};
// We reserve the null string for a true undefined.
if (yarnLocation == string.Empty)
{
yarnLocation = PathToYarn;
}
// Let's explicitly pass an environment, so the process environment won't affect tests by default
return base.Build().Configuration(
DefaultYarnPrelude(
environment: environment,
yarnLocation: yarnLocation,
moduleName: moduleName));
}
protected BuildXLEngineResult RunYarnProjects(
ICommandLineConfiguration config,
TestCache testCache = null,
IDetoursEventListener detoursListener = null)
{
// This bootstraps the 'repo'
if (!YarnInit(config))
{
throw new InvalidOperationException("Yarn init failed.");
}
using (var tempFiles = new TempFileStorage(canGetFileNames: true, rootPath: TestOutputDirectory))
{
var appDeployment = CreateAppDeployment(tempFiles);
((CommandLineConfiguration)config).Engine.Phase = Phase;
((CommandLineConfiguration)config).Sandbox.FileSystemMode = FileSystemMode.RealAndMinimalPipGraph;
var engineResult = CreateAndRunEngine(
config,
appDeployment,
testRootDirectory: null,
rememberAllChangedTrackedInputs: true,
engine: out var engine,
testCache: testCache,
detoursListener: detoursListener);
return engineResult;
}
}
private string DefaultYarnPrelude(
Dictionary<string, DiscriminatingUnion<string, UnitValue>> environment,
string yarnLocation,
string moduleName) => $@"
config({{
resolvers: [
{{
kind: 'Yarn',
moduleName: '{moduleName}',
root: d`.`,
nodeExeLocation: f`{PathToNode}`,
{DictionaryToExpression("environment", environment)}
{(yarnLocation != null ? $"yarnLocation: f`{yarnLocation}`," : string.Empty)}
}}
]
}});";
private static string DictionaryToExpression(string memberName, Dictionary<string, DiscriminatingUnion<string, UnitValue>> dictionary)
{
return (dictionary == null ?
string.Empty :
$"{memberName}: Map.empty<string, (PassthroughEnvironmentVariable | string)>(){ string.Join(string.Empty, dictionary.Select(property => $".add('{property.Key}', {(property.Value?.GetValue() is UnitValue ? "Unit.unit()" : $"'{property.Value?.GetValue()}'")})")) },");
}
private bool YarnInit(ICommandLineConfiguration config)
{
// Create a package.json, root of all the workspaces. This package needs to be private
// since workspaces need to be declared in a private one
var result = YarnRun(config, "init --private --yes");
if (!result)
{
return false;
}
// Update the root package.json to enable workspaces
var pathToPackageJson = config.Layout.SourceDirectory.Combine(PathTable, "package.json").ToString(PathTable);
string mainJson = File.ReadAllText(pathToPackageJson);
int closingBracket = mainJson.LastIndexOf('}');
mainJson = mainJson.Insert(closingBracket, @",
""workspaces"": {
""packages"": [
""src/*""
]}");
File.WriteAllText(pathToPackageJson, mainJson);
return YarnRun(config, "install");
}
private bool YarnRun(ICommandLineConfiguration config, string yarnArgs)
{
string arguments = $"{PathToYarn}.js {yarnArgs}";
string filename = PathToNode;
// Unfortunately, capturing standard out/err non-deterministically hangs node.exe on exit when
// concurrent npm install operations happen. Found reported bugs about this that look similar enough
// to the problem that manifested here.
// So we just report exit codes.
var startInfo = new ProcessStartInfo
{
FileName = filename,
Arguments = arguments,
WorkingDirectory = config.Layout.SourceDirectory.ToString(PathTable),
RedirectStandardError = false,
RedirectStandardOutput = false,
UseShellExecute = false,
};
startInfo.Environment["PATH"] += $";{PathToNodeFolder}";
startInfo.Environment["APPDATA"] = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var runYarn = Process.Start(startInfo);
runYarn.WaitForExit();
return runYarn.ExitCode == 0;
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Linq;
using BuildXL.Pips.Operations;
using Test.BuildXL.FrontEnd.Core;
using Test.BuildXL.FrontEnd.Yarn;
using Xunit;
using Xunit.Abstractions;
namespace Test.BuildXL.FrontEnd.Yarn
{
/// <summary>
/// End to end execution tests for Yarn, including pip execution
/// </summary>
/// <remarks>
/// The common JavaScript functionality is already tested in the Rush related tests, so we don't duplicate it here.
/// </remarks>
public class YarnIntegrationTests : YarnIntegrationTestBase
{
public YarnIntegrationTests(ITestOutputHelper output)
: base(output)
{
}
[Fact]
public void EndToEndPipExecutionWithDependencies()
{
// Create two projects A and B such that A -> B.
var config = Build()
.AddJavaScriptProject("@ms/project-A", "src/A", "module.exports = function A(){}")
.AddJavaScriptProject("@ms/project-B", "src/B", "const A = require('@ms/project-A'); return A();", new string[] { "@ms/project-A"})
.PersistSpecsAndGetConfiguration();
var engineResult = RunYarnProjects(config);
Assert.True(engineResult.IsSuccess);
// Let's do some basic graph validations
var processes = engineResult.EngineState.PipGraph.RetrievePipsOfType(PipType.Process).ToList();
// There should be two process pips
Assert.Equal(2, processes.Count);
// Project A depends on project B
var projectAPip = engineResult.EngineState.RetrieveProcess("_ms_project_A");
var projectBPip = engineResult.EngineState.RetrieveProcess("_ms_project_B");
Assert.True(IsDependencyAndDependent(projectAPip, projectBPip));
}
}
}

Просмотреть файл

@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.IO;
using BuildXL.FrontEnd.JavaScript.Tracing;
using BuildXL.Utilities.Configuration;
using Test.BuildXL.FrontEnd.Core;
using Xunit;
using Xunit.Abstractions;
namespace Test.BuildXL.FrontEnd.Yarn.IntegrationTests
{
public class YarnLocationTests : YarnIntegrationTestBase
{
public YarnLocationTests(ITestOutputHelper output)
: base(output)
{
}
// We don't actually need to execute anything, scheduling is enough
protected override EnginePhases Phase => EnginePhases.Schedule;
[Fact]
public void ExplicitInvalidYarnLocationIsHandled()
{
var config = Build(yarnLocation: "/path/to/foo")
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var engineResult = RunYarnProjects(config);
Assert.False(engineResult.IsSuccess);
AssertErrorEventLogged(LogEventId.ProjectGraphConstructionError);
AssertErrorEventLogged(global::BuildXL.FrontEnd.Core.Tracing.LogEventId.CannotBuildWorkspace);
}
[Fact]
public void PathIsUsedWhenYarnLocationIsUndefined()
{
// Explicitly undefine the yarn location, but add the path to yarn to PATH
var environment = new Dictionary<string, string>
{
["PATH"] = PathToNodeFolder + Path.PathSeparator + Path.GetDirectoryName(PathToYarn).Replace("\\", "/")
};
var config = Build(environment: environment, yarnLocation: null)
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var engineResult = RunYarnProjects(config);
Assert.True(engineResult.IsSuccess);
}
[Fact]
public void YarnInstallationNotFoundIsProperlyHandled()
{
// Explicitly undefine the yarn location, but do not expose anything in PATH either
var config = Build(yarnLocation: null)
.AddJavaScriptProject("@ms/project-A", "src/A")
.PersistSpecsAndGetConfiguration();
var engineResult = RunYarnProjects(config);
Assert.False(engineResult.IsSuccess);
AssertErrorEventLogged(LogEventId.CannotFindGraphBuilderTool);
AssertErrorEventLogged(global::BuildXL.FrontEnd.Core.Tracing.LogEventId.CannotBuildWorkspace);
}
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import * as Managed from "Sdk.Managed";
import * as MSBuild from "Sdk.Selfhost.MSBuild";
import * as Frameworks from "Sdk.Managed.Frameworks";
import * as Node from "Sdk.NodeJs";
import {Transformer} from "Sdk.Transformers";
namespace Test.Yarn {
// TODO: to enable this, we should use an older version of NodeJs for Linux
// Yarn is not easily available in the hosted machines that run the public build. So excluding these tests for now outside of the internal build
const isRunningOnSupportedSystem =
Context.getCurrentHost().cpuArchitecture === "x64" &&
!BuildXLSdk.isHostOsLinux &&
Environment.getFlag("[Sdk.BuildXL]microsoftInternal");
@@public
export const dll = isRunningOnSupportedSystem && BuildXLSdk.test({
// QTest is not supporting opaque directories as part of the deployment
testFramework: importFrom("Sdk.Managed.Testing.XUnit").framework,
runTestArgs: {
unsafeTestRunArguments: {
// These tests require Detours to run itself, so we won't detour the test runner process itself
runWithUntrackedDependencies: true,
},
},
assemblyName: "Test.BuildXL.FrontEnd.Yarn",
sources: globR(d`.`, "*.cs"),
references: [
Script.dll,
Core.dll,
importFrom("BuildXL.Core.UnitTests").EngineTestUtilities.dll,
importFrom("BuildXL.Engine").Engine.dll,
importFrom("BuildXL.Engine").Processes.dll,
importFrom("BuildXL.Engine").Scheduler.dll,
importFrom("BuildXL.FrontEnd").Core.dll,
importFrom("BuildXL.FrontEnd").Script.dll,
importFrom("BuildXL.FrontEnd").Sdk.dll,
importFrom("BuildXL.FrontEnd").JavaScript.dll,
importFrom("BuildXL.FrontEnd").Yarn.dll,
importFrom("BuildXL.FrontEnd").TypeScript.Net.dll,
importFrom("BuildXL.FrontEnd").Utilities.dll,
importFrom("BuildXL.FrontEnd").SdkProjectGraph.dll,
importFrom("BuildXL.Pips").dll,
importFrom("BuildXL.Utilities").dll,
importFrom("BuildXL.Utilities").Collections.dll,
importFrom("BuildXL.Utilities").Configuration.dll,
importFrom("BuildXL.Utilities").Native.dll,
importFrom("BuildXL.Utilities").Script.Constants.dll,
importFrom("BuildXL.Utilities.Instrumentation").Common.dll,
],
runtimeContent: [
// We need Yarn and Node to run these tests
{
subfolder: r`yarn`,
contents: [importFrom("Yarn").getYarn()],
},
{
subfolder: a`node`,
contents: [Node.Node.nodeExecutables]
},
],
});
}

Просмотреть файл

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<!-- Even though this file is not used by Visual Studio to fetch NuGet
packages, it is necessary to tell the VS test discoverer it nees to look for
xunit based unit tests. -->
<package id="xunit.runner.visualstudio" version="2.1.0" targetFramework="net45" />
</packages>

Просмотреть файл

@ -55,6 +55,14 @@ namespace BuildXL.FrontEnd.Yarn
foundPath = pathToYarn;
break;
}
// In some windows installations, only yarn.cmd exists
pathToYarn = absolutePath.Combine(m_context.PathTable, "yarn.cmd");
if (m_host.Engine.FileExists(pathToYarn))
{
foundPath = pathToYarn;
break;
}
}
}

Просмотреть файл

@ -21,7 +21,7 @@ interface PackageJson {
}
if (process.argv.length < 5) {
console.log("Expected arguments: <repo-folder> <path-to-output-graph> <path-to-yarn");
console.log("Expected arguments: <repo-folder> <path-to-output-graph> <path-to-yarn>");
process.exit(1);
}

Просмотреть файл

@ -2881,6 +2881,15 @@
}
}
},
{
"Component": {
"Type": "NuGet",
"NuGet": {
"Name": "Npm.OnCloudBuild",
"Version": "3.1.0"
}
}
},
{
"Component": {
"Type": "NuGet",

Просмотреть файл

@ -57,6 +57,9 @@ export const pkgs = isMicrosoftInternal ? [
// Combined runtimes
{ id: "Dotnet-Runtime", version: "5.0.3", osSkip: [ "macOS", "unix" ] },
// Officially mantained CB package that contains Yarn. Used for Yarn tests.
{ id: "Npm.OnCloudBuild", version: "3.1.0" },
] : [
// Artifact packages and dependencies in OSS