diff --git a/.vsts/builds/e2e-tests.yml b/.vsts/builds/e2e-tests.yml
new file mode 100644
index 0000000000..014f5481ba
--- /dev/null
+++ b/.vsts/builds/e2e-tests.yml
@@ -0,0 +1,19 @@
+trigger: none
+queue:
+ name: DotNetCore-Windows
+ timeoutInMinutes: 120
+steps:
+- task: NodeTool@0
+ displayName: Install Node 10.x
+ inputs:
+ versionSpec: 10.x
+- powershell: |
+ test/Cli.FunctionalTests/run-tests.ps1 -ci -ProdConManifestUrl $env:ProdConManifestUrl
+ condition: ne(variables['PB_SkipTests'], 'true')
+ displayName: Run E2E tests
+- task: PublishTestResults@2
+ displayName: Publish test results
+ condition: always()
+ inputs:
+ testRunner: vstest
+ testResultsFiles: 'artifacts/logs/**/*.trx'
diff --git a/build/artifacts.props b/build/artifacts.props
index abaa4ce285..e90d320e82 100644
--- a/build/artifacts.props
+++ b/build/artifacts.props
@@ -274,6 +274,7 @@
+
diff --git a/test/Cli.FunctionalTests/AssemblyInfo.cs b/test/Cli.FunctionalTests/AssemblyInfo.cs
new file mode 100644
index 0000000000..cbe539e977
--- /dev/null
+++ b/test/Cli.FunctionalTests/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+// 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 Cli.FunctionalTests.Util;
+using NUnit.Framework;
+
+// Run all test cases in parallel
+[assembly: Parallelizable(ParallelScope.Children)]
+
+[SetUpFixture]
+public class AssemblySetUp
+{
+ public static string TempDir { get; private set; }
+
+ [OneTimeSetUp]
+ public void SetUp()
+ {
+ TempDir = IOUtil.GetTempDir();
+ }
+
+ [OneTimeTearDown]
+ public void TearDown()
+ {
+ IOUtil.DeleteDir(TempDir);
+ }
+}
diff --git a/test/Cli.FunctionalTests/Cli.FunctionalTests.csproj b/test/Cli.FunctionalTests/Cli.FunctionalTests.csproj
new file mode 100644
index 0000000000..385a9dde2a
--- /dev/null
+++ b/test/Cli.FunctionalTests/Cli.FunctionalTests.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netcoreapp2.1
+ false
+ latest
+
+ https://api.nuget.org/v3/index.json;
+ $(DotNetRestoreSources)
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Cli.FunctionalTests/Cli.FunctionalTests.sln b/test/Cli.FunctionalTests/Cli.FunctionalTests.sln
new file mode 100644
index 0000000000..6aff0f5478
--- /dev/null
+++ b/test/Cli.FunctionalTests/Cli.FunctionalTests.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28016.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cli.FunctionalTests", "Cli.FunctionalTests.csproj", "{D44EA496-EF83-4D47-8C45-4DAF5A1B0070}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D44EA496-EF83-4D47-8C45-4DAF5A1B0070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D44EA496-EF83-4D47-8C45-4DAF5A1B0070}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D44EA496-EF83-4D47-8C45-4DAF5A1B0070}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D44EA496-EF83-4D47-8C45-4DAF5A1B0070}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {70432DA7-DCE4-4F73-A00C-E1AB180DDD6A}
+ EndGlobalSection
+EndGlobal
diff --git a/test/Cli.FunctionalTests/Directory.Build.props b/test/Cli.FunctionalTests/Directory.Build.props
new file mode 100644
index 0000000000..6d087b3032
--- /dev/null
+++ b/test/Cli.FunctionalTests/Directory.Build.props
@@ -0,0 +1,2 @@
+
+
diff --git a/test/Cli.FunctionalTests/Directory.Build.rsp b/test/Cli.FunctionalTests/Directory.Build.rsp
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/Cli.FunctionalTests/Directory.Build.targets b/test/Cli.FunctionalTests/Directory.Build.targets
new file mode 100644
index 0000000000..6d087b3032
--- /dev/null
+++ b/test/Cli.FunctionalTests/Directory.Build.targets
@@ -0,0 +1,2 @@
+
+
diff --git a/test/Cli.FunctionalTests/NuGet.config b/test/Cli.FunctionalTests/NuGet.config
new file mode 100644
index 0000000000..4bb3170917
--- /dev/null
+++ b/test/Cli.FunctionalTests/NuGet.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/test/Cli.FunctionalTests/NuGetPackageSource.cs b/test/Cli.FunctionalTests/NuGetPackageSource.cs
new file mode 100644
index 0000000000..30545f8ca5
--- /dev/null
+++ b/test/Cli.FunctionalTests/NuGetPackageSource.cs
@@ -0,0 +1,57 @@
+// 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.Linq;
+
+namespace Cli.FunctionalTests
+{
+ public class NuGetPackageSource
+ {
+ public static NuGetPackageSource None { get; } = new NuGetPackageSource
+ {
+ Name = nameof(None),
+ SourceArgumentLazy = new Lazy(string.Empty),
+ };
+
+ public static NuGetPackageSource NuGetOrg { get; } = new NuGetPackageSource
+ {
+ Name = nameof(NuGetOrg),
+ SourceArgumentLazy = new Lazy("--source https://api.nuget.org/v3/index.json"),
+ };
+
+ public static NuGetPackageSource DotNetCore { get; } = new NuGetPackageSource
+ {
+ Name = nameof(DotNetCore),
+ SourceArgumentLazy = new Lazy("--source https://dotnet.myget.org/F/dotnet-core/api/v3/index.json"),
+ };
+
+ public static NuGetPackageSource EnvironmentVariable { get; } = new NuGetPackageSource
+ {
+ Name = nameof(EnvironmentVariable),
+ SourceArgumentLazy = new Lazy(() => GetSourceArgumentFromEnvironment()),
+ };
+
+ public static NuGetPackageSource EnvironmentVariableAndNuGetOrg { get; } = new NuGetPackageSource
+ {
+ Name = nameof(EnvironmentVariableAndNuGetOrg),
+ SourceArgumentLazy = new Lazy(() => string.Join(" ", EnvironmentVariable.SourceArgument, NuGetOrg.SourceArgument)),
+ };
+
+ private NuGetPackageSource() { }
+
+ public string Name { get; private set; }
+ public string SourceArgument => SourceArgumentLazy.Value;
+ private Lazy SourceArgumentLazy { get; set; }
+
+ public override string ToString() => Name;
+
+ private static string GetSourceArgumentFromEnvironment()
+ {
+ var sourceString = Environment.GetEnvironmentVariable("NUGET_PACKAGE_SOURCE") ??
+ throw new InvalidOperationException("Environment variable NUGET_PACKAGE_SOURCE is required but not set");
+
+ return string.Join(" ", sourceString.Split(',').Select(s => $"--source {s}"));
+ }
+ }
+}
diff --git a/test/Cli.FunctionalTests/README.md b/test/Cli.FunctionalTests/README.md
new file mode 100644
index 0000000000..4587c251c1
--- /dev/null
+++ b/test/Cli.FunctionalTests/README.md
@@ -0,0 +1,5 @@
+# Cli.FunctionalTests
+
+This folder contains tests for ASP.NET Core scenarios in the .NET Core CLI.
+
+This tests in this folder is meant to be kept in isolation from the rest of the repo, and are not invoked during the course of a regular build.
diff --git a/test/Cli.FunctionalTests/RuntimeIdentifier.cs b/test/Cli.FunctionalTests/RuntimeIdentifier.cs
new file mode 100644
index 0000000000..7e638a6935
--- /dev/null
+++ b/test/Cli.FunctionalTests/RuntimeIdentifier.cs
@@ -0,0 +1,55 @@
+// 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.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Cli.FunctionalTests
+{
+ // https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
+ public class RuntimeIdentifier
+ {
+ public static RuntimeIdentifier None = new RuntimeIdentifier() {
+ Name = "none",
+ OSPlatforms = new[] { OSPlatform.Linux, OSPlatform.OSX, OSPlatform.Windows, },
+ };
+
+ public static RuntimeIdentifier Linux_x64 = new RuntimeIdentifier() {
+ Name = "linux-x64",
+ OSPlatforms = new[] { OSPlatform.Linux, },
+ ExecutableFileExtension = string.Empty,
+ };
+
+ public static RuntimeIdentifier OSX_x64 = new RuntimeIdentifier()
+ {
+ Name = "osx-x64",
+ OSPlatforms = new[] { OSPlatform.OSX, },
+ ExecutableFileExtension = string.Empty,
+ };
+
+ public static RuntimeIdentifier Win_x64 = new RuntimeIdentifier()
+ {
+ Name = "win-x64",
+ OSPlatforms = new[] { OSPlatform.Windows, },
+ ExecutableFileExtension = ".exe",
+ };
+
+ public static IEnumerable All = new[]
+ {
+ RuntimeIdentifier.None,
+ RuntimeIdentifier.Linux_x64,
+ RuntimeIdentifier.OSX_x64,
+ RuntimeIdentifier.Win_x64,
+ };
+
+ private RuntimeIdentifier() { }
+
+ public string Name { get; private set; }
+ public string RuntimeArgument => (this == None) ? string.Empty : $"--runtime {Name}";
+ public string Path => (this == None) ? string.Empty : Name;
+ public IEnumerable OSPlatforms { get; private set; }
+ public string ExecutableFileExtension { get; private set; }
+
+ public override string ToString() => Name;
+ }
+}
diff --git a/test/Cli.FunctionalTests/TemplateTests.cs b/test/Cli.FunctionalTests/TemplateTests.cs
new file mode 100644
index 0000000000..7e8143114f
--- /dev/null
+++ b/test/Cli.FunctionalTests/TemplateTests.cs
@@ -0,0 +1,220 @@
+// 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 System.Net;
+using System.Runtime.InteropServices;
+using Cli.FunctionalTests.Templates;
+using Cli.FunctionalTests.Util;
+using NuGet.Versioning;
+using NUnit.Framework;
+
+namespace Cli.FunctionalTests
+{
+ [TestFixture]
+ public class TemplateTests
+ {
+ [Test]
+ [TestCaseSource(nameof(RestoreData))]
+ public void _1_Restore(Template template)
+ {
+ var expected = template.ExpectedObjFilesAfterRestore;
+ var actual = template.ObjFilesAfterRestore;
+ CollectionAssert.AreEquivalent(expected, actual);
+ }
+
+ [Test]
+ [TestCaseSource(nameof(RestoreData))]
+ public void _2_RestoreIncremental(Template template)
+ {
+ var expected = template.ExpectedObjFilesAfterRestore;
+ var actual = template.ObjFilesAfterRestoreIncremental;
+ CollectionAssert.AreEquivalent(expected, actual);
+ }
+
+ [Test]
+ [TestCaseSource(nameof(BuildData))]
+ public void _3_Build(Template template)
+ {
+ var expectedObj = template.ExpectedObjFilesAfterBuild;
+ var actualObj = template.ObjFilesAfterBuild;
+ CollectionAssert.AreEquivalent(expectedObj, actualObj);
+
+ var expectedBin = template.ExpectedBinFilesAfterBuild;
+ var actualBin = template.BinFilesAfterBuild;
+ CollectionAssert.AreEquivalent(expectedBin, actualBin);
+ }
+
+ [Test]
+ [TestCaseSource(nameof(BuildData))]
+ public void _4_BuildIncremental(Template template)
+ {
+ var expectedObj = template.ExpectedObjFilesAfterBuild;
+ var actualObj = template.ObjFilesAfterBuildIncremental;
+ CollectionAssert.AreEquivalent(expectedObj, actualObj);
+
+ var expectedBin = template.ExpectedBinFilesAfterBuild;
+ var actualBin = template.BinFilesAfterBuildIncremental;
+ CollectionAssert.AreEquivalent(expectedBin, actualBin);
+ }
+
+ [Test]
+ [TestCaseSource(nameof(RunData))]
+ public void _5_Run(Template template)
+ {
+ var statusCode = template.HttpResponseAfterRun.StatusCode;
+ Assert.AreEqual(HttpStatusCode.OK, statusCode,
+ GetMessage(statusCode, template.ServerOutputAfterRun, template.ServerErrorAfterRun));
+
+ statusCode = template.HttpsResponseAfterRun.StatusCode;
+ Assert.AreEqual(HttpStatusCode.OK, statusCode,
+ GetMessage(statusCode, template.ServerOutputAfterRun, template.ServerErrorAfterRun));
+ }
+
+ [NonParallelizable]
+ [Test]
+ [TestCaseSource(nameof(RunNonParallelizableData))]
+ public void _5_RunNonParallelizable(Template template)
+ {
+ _5_Run(template);
+ }
+
+ [Test]
+ [TestCaseSource(nameof(PublishData))]
+ public void _6_Publish(Template template)
+ {
+ var expected = template.ExpectedFilesAfterPublish;
+ var actual = template.FilesAfterPublish;
+ CollectionAssert.AreEquivalent(expected, actual);
+ }
+
+ [Test]
+ [TestCaseSource(nameof(PublishData))]
+ public void _7_PublishIncremental(Template template)
+ {
+ var expected = template.ExpectedFilesAfterPublish;
+ var actual = template.FilesAfterPublishIncremental;
+ CollectionAssert.AreEquivalent(expected, actual);
+ }
+
+ [Test]
+ [TestCaseSource(nameof(ExecData))]
+ public void _8_Exec(Template template)
+ {
+ var statusCode = template.HttpResponseAfterExec.StatusCode;
+ Assert.AreEqual(HttpStatusCode.OK, statusCode,
+ GetMessage(statusCode, template.ServerOutputAfterExec, template.ServerErrorAfterExec));
+
+ statusCode = template.HttpsResponseAfterExec.StatusCode;
+ Assert.AreEqual(HttpStatusCode.OK, statusCode,
+ GetMessage(statusCode, template.ServerOutputAfterExec, template.ServerErrorAfterExec));
+ }
+
+ private static string GetMessage(HttpStatusCode statusCode, string serverOutput, string serverError)
+ {
+ return String.Join(Environment.NewLine,
+ $"StatusCode: {statusCode}",
+ string.Empty,
+ "ServerOutput",
+ "------------",
+ serverOutput,
+ string.Empty,
+ "ServerError",
+ "------------",
+ serverError);
+ }
+
+ private static IEnumerable GetTemplates(RuntimeIdentifier runtimeIdentifier)
+ {
+ // Offline restore is broken in SDK 2.1.301 (https://github.com/aspnet/Universe/issues/1220)
+ var offlinePackageSource = (DotNetUtil.SdkVersion == new SemanticVersion(2, 1, 301)) ?
+ NuGetPackageSource.NuGetOrg : NuGetPackageSource.None;
+
+ // Pre-release SDKs require a private nuget feed
+ var onlinePackageSource = DotNetUtil.RequiresPrivateFeed ?
+ NuGetPackageSource.EnvironmentVariableAndNuGetOrg : NuGetPackageSource.NuGetOrg;
+
+ if (runtimeIdentifier == RuntimeIdentifier.None)
+ {
+ // Framework-dependent
+ return new[]
+ {
+ Template.GetInstance(NuGetPackageSource.None, runtimeIdentifier),
+ Template.GetInstance(offlinePackageSource, runtimeIdentifier),
+
+ // Offline restore currently not supported for RazorClassLibrary template (https://github.com/aspnet/Universe/issues/1123)
+ Template.GetInstance(onlinePackageSource, runtimeIdentifier),
+
+ Template.GetInstance(offlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(offlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(offlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(offlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(offlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(offlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(offlinePackageSource, runtimeIdentifier),
+ };
+ }
+ else
+ {
+ // Self-contained
+ return new[]
+ {
+ // ClassLibrary does not require a package source, even for self-contained deployments
+ Template.GetInstance(NuGetPackageSource.None, runtimeIdentifier),
+
+ Template.GetInstance(onlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(onlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(onlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(onlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(onlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(onlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(onlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(onlinePackageSource, runtimeIdentifier),
+ Template.GetInstance(onlinePackageSource, runtimeIdentifier),
+ };
+ }
+ }
+
+ private static readonly IEnumerable _restoreTemplates = RuntimeIdentifier.All.SelectMany(r => GetTemplates(r));
+
+ // Must call ToList() or similar on RestoreData to ensure TestCaseData instances can be compared to each other,
+ // which is required to use Except() in RunData.
+ public static IEnumerable RestoreData = _restoreTemplates.Select(t => new TestCaseData(t)).ToList();
+
+ public static IEnumerable BuildData => RestoreData;
+
+ public static IEnumerable PublishData => BuildData;
+
+ private static readonly IEnumerable _runData =
+ from tcd in BuildData
+ let t = (Template)tcd.Arguments[0]
+ // Only interested in verifying web applications
+ where (t.Type == TemplateType.WebApplication)
+ // "dotnet run" is only relevant for framework-dependent apps
+ where (t.RuntimeIdentifier == RuntimeIdentifier.None)
+ select tcd;
+
+ // On Linux, calling "dotnet run" on multiple React templates in parallel may fail since the default
+ // fs.inotify.max_user_watches is too low. One workaround is to increase fs.inotify.max_user_watches,
+ // but this means tests will fail on a default machine. A simpler workaround is to disable parallel
+ // execution for these tests.
+ public static IEnumerable RunNonParallelizableData =
+ from tcd in _runData
+ let t = (Template)tcd.Arguments[0]
+ where (t is ReactTemplate)
+ select tcd;
+
+ public static IEnumerable RunData = _runData.Except(RunNonParallelizableData);
+
+ public static IEnumerable ExecData =
+ from tcd in PublishData
+ let t = (Template)tcd.Arguments[0]
+ // Only interested in verifying web applications
+ where (t.Type == TemplateType.WebApplication)
+ // Can only run framework-dependent apps and self-contained apps matching the current platform
+ let runnable = t.RuntimeIdentifier.OSPlatforms.Any(p => RuntimeInformation.IsOSPlatform(p))
+ select (runnable ? tcd : tcd.Ignore($"RuntimeIdentifier '{t.RuntimeIdentifier}' cannot be executed on this platform"));
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/AngularTemplate.cs b/test/Cli.FunctionalTests/Templates/AngularTemplate.cs
new file mode 100644
index 0000000000..debc4a6a9d
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/AngularTemplate.cs
@@ -0,0 +1,67 @@
+// 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.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Cli.FunctionalTests.Util;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public class AngularTemplate : SpaBaseTemplate
+ {
+ public AngularTemplate() { }
+
+ public override string Name => "angular";
+
+ protected override IEnumerable NormalizeFilesAfterPublish(IEnumerable filesAfterPublish)
+ {
+ // Remove generated hashes since they may vary by platform
+ return base.NormalizeFilesAfterPublish(filesAfterPublish)
+ .Select(f => Regex.Replace(f, @"\.[0-9a-f]{20}\.", ".[HASH]."));
+ }
+
+ private IDictionary>> _additionalFilesAfterPublish =>
+ new Dictionary>>()
+ {
+ { "common", () => new[]
+ {
+ Path.Combine("wwwroot", "favicon.ico"),
+ Path.Combine("ClientApp", "dist", "3rdpartylicenses.txt"),
+ Path.Combine("ClientApp", "dist", "index.html"),
+ }
+ },
+ { "netcoreapp2.1", () =>
+ _additionalFilesAfterPublish["common"]()
+ .Concat(new[]
+ {
+ Path.Combine("ClientApp", "dist", "glyphicons-halflings-regular.[HASH].woff2"),
+ Path.Combine("ClientApp", "dist", "glyphicons-halflings-regular.[HASH].svg"),
+ Path.Combine("ClientApp", "dist", "glyphicons-halflings-regular.[HASH].ttf"),
+ Path.Combine("ClientApp", "dist", "glyphicons-halflings-regular.[HASH].eot"),
+ Path.Combine("ClientApp", "dist", "glyphicons-halflings-regular.[HASH].woff"),
+ Path.Combine("ClientApp", "dist", $"inline.[HASH].bundle.js"),
+ Path.Combine("ClientApp", "dist", $"main.[HASH].bundle.js"),
+ Path.Combine("ClientApp", "dist", $"polyfills.[HASH].bundle.js"),
+ Path.Combine("ClientApp", "dist", $"styles.[HASH].bundle.css"),
+ })
+ },
+ { "netcoreapp2.2", () =>
+ _additionalFilesAfterPublish["common"]()
+ .Concat(new[]
+ {
+ Path.Combine("ClientApp", "dist", $"runtime.[HASH].js"),
+ Path.Combine("ClientApp", "dist", $"main.[HASH].js"),
+ Path.Combine("ClientApp", "dist", $"polyfills.[HASH].js"),
+ Path.Combine("ClientApp", "dist", $"styles.[HASH].css"),
+ })
+ },
+ };
+
+ public override IEnumerable ExpectedFilesAfterPublish =>
+ base.ExpectedFilesAfterPublish
+ .Concat(_additionalFilesAfterPublish[DotNetUtil.TargetFrameworkMoniker]());
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/ClassLibraryTemplate.cs b/test/Cli.FunctionalTests/Templates/ClassLibraryTemplate.cs
new file mode 100644
index 0000000000..e88c0a3ccf
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/ClassLibraryTemplate.cs
@@ -0,0 +1,48 @@
+// 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.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public class ClassLibraryTemplate : Template
+ {
+ public ClassLibraryTemplate() { }
+
+ public override string Name => "classlib";
+
+ public override string OutputPath => Path.Combine("Debug", "netstandard2.0", RuntimeIdentifier.Path);
+
+ public override TemplateType Type => TemplateType.ClassLibrary;
+
+ public override IEnumerable ExpectedObjFilesAfterBuild =>
+ base.ExpectedObjFilesAfterBuild
+ .Concat(new[]
+ {
+ $"{Name}.AssemblyInfo.cs",
+ $"{Name}.AssemblyInfoInputs.cache",
+ $"{Name}.assets.cache",
+ $"{Name}.csproj.CoreCompileInputs.cache",
+ $"{Name}.csproj.FileListAbsolute.txt",
+ $"{Name}.csprojAssemblyReference.cache",
+ $"{Name}.dll",
+ $"{Name}.pdb",
+ }.Select(p => Path.Combine(OutputPath, p)));
+
+ public override IEnumerable ExpectedBinFilesAfterBuild => new[]
+ {
+ $"{Name}.deps.json",
+ $"{Name}.dll",
+ $"{Name}.pdb",
+ }.Select(p => Path.Combine(OutputPath, p));
+
+ public override IEnumerable ExpectedFilesAfterPublish => new[]
+ {
+ $"{Name}.deps.json",
+ $"{Name}.dll",
+ $"{Name}.pdb",
+ };
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/ConsoleApplicationTemplate.cs b/test/Cli.FunctionalTests/Templates/ConsoleApplicationTemplate.cs
new file mode 100644
index 0000000000..d72dca2d87
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/ConsoleApplicationTemplate.cs
@@ -0,0 +1,375 @@
+// 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.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Cli.FunctionalTests.Util;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public class ConsoleApplicationTemplate : ClassLibraryTemplate
+ {
+ public ConsoleApplicationTemplate() { }
+
+ public override string Name => "console";
+
+ public override string OutputPath => Path.Combine("Debug", DotNetUtil.TargetFrameworkMoniker, RuntimeIdentifier.Path);
+
+ public override TemplateType Type => TemplateType.ConsoleApplication;
+
+ private IDictionary>> _additionalObjFilesAfterBuild =>
+ new Dictionary>>()
+ {
+ { RuntimeIdentifier.None, () => Enumerable.Empty() },
+ { RuntimeIdentifier.Win_x64, () => new[]
+ {
+ Path.Combine(DotNetUtil.TargetFrameworkMoniker, RuntimeIdentifier.Path, "host", $"{Name}.exe"),
+ }
+ },
+ { RuntimeIdentifier.Linux_x64, () => new[]
+ {
+ Path.Combine(DotNetUtil.TargetFrameworkMoniker, RuntimeIdentifier.Path, "host", $"{Name}"),
+ }
+ },
+ { RuntimeIdentifier.OSX_x64, () => _additionalObjFilesAfterBuild[RuntimeIdentifier.Linux_x64]() },
+ };
+
+ public override IEnumerable ExpectedObjFilesAfterBuild =>
+ base.ExpectedObjFilesAfterBuild
+ .Concat(_additionalObjFilesAfterBuild[RuntimeIdentifier]());
+
+ private IDictionary>> _additionalBinFilesAfterBuild =>
+ new Dictionary>>()
+ {
+ { RuntimeIdentifier.None, () => new[]
+ {
+ $"{Name}.runtimeconfig.dev.json",
+ $"{Name}.runtimeconfig.json",
+ }.Select(p => Path.Combine(OutputPath, p))
+ },
+ { RuntimeIdentifier.Win_x64, () =>
+ _additionalBinFilesAfterBuild[RuntimeIdentifier.None]()
+ .Concat(new[]
+ {
+ $"{Name}.exe",
+ "hostfxr.dll",
+ "hostpolicy.dll",
+ }.Select(p => Path.Combine(OutputPath, p)))
+ },
+ { RuntimeIdentifier.Linux_x64, () =>
+ _additionalBinFilesAfterBuild[RuntimeIdentifier.None]()
+ .Concat(new[]
+ {
+ $"{Name}",
+ "libhostfxr.so",
+ "libhostpolicy.so",
+ }.Select(p => Path.Combine(OutputPath, p)))
+ },
+ { RuntimeIdentifier.OSX_x64, () =>
+ _additionalBinFilesAfterBuild[RuntimeIdentifier.Linux_x64]()
+ .Select(f => Regex.Replace(f, ".so$", ".dylib"))
+ },
+ };
+
+ public override IEnumerable ExpectedBinFilesAfterBuild =>
+ base.ExpectedBinFilesAfterBuild
+ .Concat(_additionalBinFilesAfterBuild[RuntimeIdentifier]());
+
+ protected override IEnumerable NormalizeFilesAfterPublish(IEnumerable filesAfterPublish)
+ {
+ // A few files included in self-contained deployments contain version numbers in the filename, which must
+ // be replaced so tests can pass on all versions.
+ return base.NormalizeFilesAfterPublish(filesAfterPublish)
+ .Select(f => Regex.Replace(f, @"_amd64_amd64_[0-9\.]+\.dll$", "_amd64_amd64_[VERSION].dll"));
+ }
+
+ private Func> _additionalFilesAfterPublishCommon = () => new[]
+ {
+ "Microsoft.CSharp.dll",
+ "Microsoft.VisualBasic.dll",
+ // It may seem unusual to include Microsoft.Win32 assemblies in all platforms, but it appears to be by design
+ // https://github.com/dotnet/corefx/issues/14896
+ "Microsoft.Win32.Primitives.dll",
+ "Microsoft.Win32.Registry.dll",
+ "mscorlib.dll",
+ "netstandard.dll",
+ "System.AppContext.dll",
+ "System.Buffers.dll",
+ "System.Collections.Concurrent.dll",
+ "System.Collections.dll",
+ "System.Collections.Immutable.dll",
+ "System.Collections.NonGeneric.dll",
+ "System.Collections.Specialized.dll",
+ "System.ComponentModel.Annotations.dll",
+ "System.ComponentModel.DataAnnotations.dll",
+ "System.ComponentModel.dll",
+ "System.ComponentModel.EventBasedAsync.dll",
+ "System.ComponentModel.Primitives.dll",
+ "System.ComponentModel.TypeConverter.dll",
+ "System.Configuration.dll",
+ "System.Console.dll",
+ "System.Core.dll",
+ "System.Data.Common.dll",
+ "System.Data.dll",
+ "System.Diagnostics.Contracts.dll",
+ "System.Diagnostics.Debug.dll",
+ "System.Diagnostics.DiagnosticSource.dll",
+ "System.Diagnostics.FileVersionInfo.dll",
+ "System.Diagnostics.Process.dll",
+ "System.Diagnostics.StackTrace.dll",
+ "System.Diagnostics.TextWriterTraceListener.dll",
+ "System.Diagnostics.Tools.dll",
+ "System.Diagnostics.TraceSource.dll",
+ "System.Diagnostics.Tracing.dll",
+ "System.dll",
+ "System.Drawing.dll",
+ "System.Drawing.Primitives.dll",
+ "System.Dynamic.Runtime.dll",
+ "System.Globalization.Calendars.dll",
+ "System.Globalization.dll",
+ "System.Globalization.Extensions.dll",
+ "System.IO.Compression.Brotli.dll",
+ "System.IO.Compression.dll",
+ "System.IO.Compression.FileSystem.dll",
+ "System.IO.Compression.ZipFile.dll",
+ "System.IO.dll",
+ "System.IO.FileSystem.AccessControl.dll",
+ "System.IO.FileSystem.dll",
+ "System.IO.FileSystem.DriveInfo.dll",
+ "System.IO.FileSystem.Primitives.dll",
+ "System.IO.FileSystem.Watcher.dll",
+ "System.IO.IsolatedStorage.dll",
+ "System.IO.MemoryMappedFiles.dll",
+ "System.IO.Pipes.AccessControl.dll",
+ "System.IO.Pipes.dll",
+ "System.IO.UnmanagedMemoryStream.dll",
+ "System.Linq.dll",
+ "System.Linq.Expressions.dll",
+ "System.Linq.Parallel.dll",
+ "System.Linq.Queryable.dll",
+ "System.Memory.dll",
+ "System.Net.dll",
+ "System.Net.Http.dll",
+ "System.Net.HttpListener.dll",
+ "System.Net.Mail.dll",
+ "System.Net.NameResolution.dll",
+ "System.Net.NetworkInformation.dll",
+ "System.Net.Ping.dll",
+ "System.Net.Primitives.dll",
+ "System.Net.Requests.dll",
+ "System.Net.Security.dll",
+ "System.Net.ServicePoint.dll",
+ "System.Net.Sockets.dll",
+ "System.Net.WebClient.dll",
+ "System.Net.WebHeaderCollection.dll",
+ "System.Net.WebProxy.dll",
+ "System.Net.WebSockets.Client.dll",
+ "System.Net.WebSockets.dll",
+ "System.Numerics.dll",
+ "System.Numerics.Vectors.dll",
+ "System.ObjectModel.dll",
+ "System.Private.CoreLib.dll",
+ "System.Private.DataContractSerialization.dll",
+ "System.Private.Uri.dll",
+ "System.Private.Xml.dll",
+ "System.Private.Xml.Linq.dll",
+ "System.Reflection.DispatchProxy.dll",
+ "System.Reflection.dll",
+ "System.Reflection.Emit.dll",
+ "System.Reflection.Emit.ILGeneration.dll",
+ "System.Reflection.Emit.Lightweight.dll",
+ "System.Reflection.Extensions.dll",
+ "System.Reflection.Metadata.dll",
+ "System.Reflection.Primitives.dll",
+ "System.Reflection.TypeExtensions.dll",
+ "System.Resources.Reader.dll",
+ "System.Resources.ResourceManager.dll",
+ "System.Resources.Writer.dll",
+ "System.Runtime.CompilerServices.VisualC.dll",
+ "System.Runtime.dll",
+ "System.Runtime.Extensions.dll",
+ "System.Runtime.Handles.dll",
+ "System.Runtime.InteropServices.dll",
+ "System.Runtime.InteropServices.RuntimeInformation.dll",
+ "System.Runtime.InteropServices.WindowsRuntime.dll",
+ "System.Runtime.Loader.dll",
+ "System.Runtime.Numerics.dll",
+ "System.Runtime.Serialization.dll",
+ "System.Runtime.Serialization.Formatters.dll",
+ "System.Runtime.Serialization.Json.dll",
+ "System.Runtime.Serialization.Primitives.dll",
+ "System.Runtime.Serialization.Xml.dll",
+ "System.Security.AccessControl.dll",
+ "System.Security.Claims.dll",
+ "System.Security.Cryptography.Algorithms.dll",
+ "System.Security.Cryptography.Cng.dll",
+ "System.Security.Cryptography.Csp.dll",
+ "System.Security.Cryptography.Encoding.dll",
+ "System.Security.Cryptography.OpenSsl.dll",
+ "System.Security.Cryptography.Primitives.dll",
+ "System.Security.Cryptography.X509Certificates.dll",
+ "System.Security.dll",
+ "System.Security.Principal.dll",
+ "System.Security.Principal.Windows.dll",
+ "System.Security.SecureString.dll",
+ "System.ServiceModel.Web.dll",
+ "System.ServiceProcess.dll",
+ "System.Text.Encoding.dll",
+ "System.Text.Encoding.Extensions.dll",
+ "System.Text.RegularExpressions.dll",
+ "System.Threading.dll",
+ "System.Threading.Overlapped.dll",
+ "System.Threading.Tasks.Dataflow.dll",
+ "System.Threading.Tasks.dll",
+ "System.Threading.Tasks.Extensions.dll",
+ "System.Threading.Tasks.Parallel.dll",
+ "System.Threading.Thread.dll",
+ "System.Threading.ThreadPool.dll",
+ "System.Threading.Timer.dll",
+ "System.Transactions.dll",
+ "System.Transactions.Local.dll",
+ "System.ValueTuple.dll",
+ "System.Web.dll",
+ "System.Web.HttpUtility.dll",
+ "System.Windows.dll",
+ "System.Xml.dll",
+ "System.Xml.Linq.dll",
+ "System.Xml.ReaderWriter.dll",
+ "System.Xml.Serialization.dll",
+ "System.Xml.XDocument.dll",
+ "System.Xml.XmlDocument.dll",
+ "System.Xml.XmlSerializer.dll",
+ "System.Xml.XPath.dll",
+ "System.Xml.XPath.XDocument.dll",
+ "WindowsBase.dll",
+ };
+
+ private IDictionary>> _additionalFilesAfterPublish =>
+ new Dictionary>>()
+ {
+ { RuntimeIdentifier.None, () => new[]
+ {
+ $"{Name}.runtimeconfig.json",
+ }
+ },
+ { RuntimeIdentifier.Win_x64, () =>
+ _additionalFilesAfterPublish[RuntimeIdentifier.None]()
+ .Concat(_additionalFilesAfterPublishCommon())
+ .Concat(new[]
+ {
+ $"{Name}.exe",
+ "api-ms-win-core-console-l1-1-0.dll",
+ "api-ms-win-core-datetime-l1-1-0.dll",
+ "api-ms-win-core-debug-l1-1-0.dll",
+ "api-ms-win-core-errorhandling-l1-1-0.dll",
+ "api-ms-win-core-file-l1-1-0.dll",
+ "api-ms-win-core-file-l1-2-0.dll",
+ "api-ms-win-core-file-l2-1-0.dll",
+ "api-ms-win-core-handle-l1-1-0.dll",
+ "api-ms-win-core-heap-l1-1-0.dll",
+ "api-ms-win-core-interlocked-l1-1-0.dll",
+ "api-ms-win-core-libraryloader-l1-1-0.dll",
+ "api-ms-win-core-localization-l1-2-0.dll",
+ "api-ms-win-core-memory-l1-1-0.dll",
+ "api-ms-win-core-namedpipe-l1-1-0.dll",
+ "api-ms-win-core-processenvironment-l1-1-0.dll",
+ "api-ms-win-core-processthreads-l1-1-0.dll",
+ "api-ms-win-core-processthreads-l1-1-1.dll",
+ "api-ms-win-core-profile-l1-1-0.dll",
+ "api-ms-win-core-rtlsupport-l1-1-0.dll",
+ "api-ms-win-core-string-l1-1-0.dll",
+ "api-ms-win-core-synch-l1-1-0.dll",
+ "api-ms-win-core-synch-l1-2-0.dll",
+ "api-ms-win-core-sysinfo-l1-1-0.dll",
+ "api-ms-win-core-timezone-l1-1-0.dll",
+ "api-ms-win-core-util-l1-1-0.dll",
+ "api-ms-win-crt-conio-l1-1-0.dll",
+ "api-ms-win-crt-convert-l1-1-0.dll",
+ "api-ms-win-crt-environment-l1-1-0.dll",
+ "api-ms-win-crt-filesystem-l1-1-0.dll",
+ "api-ms-win-crt-heap-l1-1-0.dll",
+ "api-ms-win-crt-locale-l1-1-0.dll",
+ "api-ms-win-crt-math-l1-1-0.dll",
+ "api-ms-win-crt-multibyte-l1-1-0.dll",
+ "api-ms-win-crt-private-l1-1-0.dll",
+ "api-ms-win-crt-process-l1-1-0.dll",
+ "api-ms-win-crt-runtime-l1-1-0.dll",
+ "api-ms-win-crt-stdio-l1-1-0.dll",
+ "api-ms-win-crt-string-l1-1-0.dll",
+ "api-ms-win-crt-time-l1-1-0.dll",
+ "api-ms-win-crt-utility-l1-1-0.dll",
+ "clrcompression.dll",
+ "clretwrc.dll",
+ "clrjit.dll",
+ "coreclr.dll",
+ "dbgshim.dll",
+ "hostfxr.dll",
+ "hostpolicy.dll",
+ "Microsoft.DiaSymReader.Native.amd64.dll",
+ "mscordaccore.dll",
+ "mscordaccore_amd64_amd64_[VERSION].dll",
+ "mscordbi.dll",
+ "mscorrc.debug.dll",
+ "mscorrc.dll",
+ "sos.dll",
+ "SOS.NETCore.dll",
+ "sos_amd64_amd64_[VERSION].dll",
+ "ucrtbase.dll",
+ })
+ },
+ { RuntimeIdentifier.Linux_x64, () =>
+ _additionalFilesAfterPublish[RuntimeIdentifier.None]()
+ .Concat(_additionalFilesAfterPublishCommon())
+ .Concat(new[]
+ {
+ $"{Name}",
+ "createdump",
+ "libclrjit.so",
+ "libcoreclr.so",
+ "libcoreclrtraceptprovider.so",
+ "libdbgshim.so",
+ "libhostfxr.so",
+ "libhostpolicy.so",
+ "libmscordaccore.so",
+ "libmscordbi.so",
+ "libsos.so",
+ "libsosplugin.so",
+ "SOS.NETCore.dll",
+ "sosdocsunix.txt",
+ "System.Globalization.Native.so",
+ "System.IO.Compression.Native.a",
+ "System.IO.Compression.Native.so",
+ "System.Native.a",
+ "System.Native.so",
+ "System.Net.Http.Native.a",
+ "System.Net.Http.Native.so",
+ "System.Net.Security.Native.a",
+ "System.Net.Security.Native.so",
+ "System.Security.Cryptography.Native.OpenSsl.a",
+ "System.Security.Cryptography.Native.OpenSsl.so",
+ })
+ },
+ { RuntimeIdentifier.OSX_x64, () =>
+ _additionalFilesAfterPublish[RuntimeIdentifier.Linux_x64]()
+ .Where(f => f != "createdump")
+ .Where(f => f != "libcoreclrtraceptprovider.so")
+ .Where(f => f != "libsosplugin.so")
+ .Select(f => Regex.Replace(f, ".so$", ".dylib"))
+ .Concat(new[]
+ {
+ "System.Security.Cryptography.Native.Apple.a",
+ "System.Security.Cryptography.Native.Apple.dylib",
+ })
+ }
+ };
+
+ public override IEnumerable ExpectedFilesAfterPublish =>
+ base.ExpectedFilesAfterPublish
+ .Concat(_additionalFilesAfterPublish[RuntimeIdentifier]());
+ }
+}
+
+
diff --git a/test/Cli.FunctionalTests/Templates/MvcTemplate.cs b/test/Cli.FunctionalTests/Templates/MvcTemplate.cs
new file mode 100644
index 0000000000..ca5a9cdf65
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/MvcTemplate.cs
@@ -0,0 +1,49 @@
+// 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.IO;
+using System.Linq;
+using Cli.FunctionalTests.Util;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public class MvcTemplate : RazorBootstrapJQueryTemplate
+ {
+ public MvcTemplate() { }
+
+ public override string Name => "mvc";
+
+ protected override string RazorPath => "Views";
+
+ private IDictionary>> _additionalObjFilesAfterBuild =>
+ new Dictionary>>()
+ {
+ { "common", () => new[]
+ {
+ Path.Combine("Razor", RazorPath, "_ViewStart.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Home", "Index.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Home", "Privacy.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Shared", "_CookieConsentPartial.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Shared", "_Layout.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Shared", "_ValidationScriptsPartial.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Shared", "Error.g.cshtml.cs"),
+ }
+ },
+ { "netcoreapp2.1", () =>
+ _additionalObjFilesAfterBuild["common"]()
+ .Concat(new[]
+ {
+ Path.Combine("Razor", RazorPath, "Home", "About.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Home", "Contact.g.cshtml.cs"),
+ })
+ },
+ { "netcoreapp2.2", () => _additionalObjFilesAfterBuild["common"]() },
+ };
+
+ public override IEnumerable ExpectedObjFilesAfterBuild =>
+ base.ExpectedObjFilesAfterBuild
+ .Concat(_additionalObjFilesAfterBuild[DotNetUtil.TargetFrameworkMoniker]().Select(p => Path.Combine(OutputPath, p)));
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/RazorApplicationBaseTemplate.cs b/test/Cli.FunctionalTests/Templates/RazorApplicationBaseTemplate.cs
new file mode 100644
index 0000000000..f1dceecde4
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/RazorApplicationBaseTemplate.cs
@@ -0,0 +1,61 @@
+// 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.IO;
+using System.Linq;
+using Cli.FunctionalTests.Util;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public abstract class RazorApplicationBaseTemplate : WebTemplate
+ {
+ protected abstract string RazorPath { get; }
+
+ private IDictionary>> _additionalObjFilesAfterBuild =>
+ new Dictionary>>()
+ {
+ { RuntimeIdentifier.None, () => new[]
+ {
+ Path.Combine("Razor", RazorPath, "_ViewImports.g.cshtml.cs"),
+ }.Select(p => Path.Combine(OutputPath, p))
+ },
+ { RuntimeIdentifier.Win_x64, () =>
+ _additionalObjFilesAfterBuild[RuntimeIdentifier.None]()
+ .Concat(new[]
+ {
+ Path.Combine(DotNetUtil.TargetFrameworkMoniker, RuntimeIdentifier.Path, "host", $"{Name}.exe"),
+ })
+ },
+ { RuntimeIdentifier.Linux_x64, () =>
+ _additionalObjFilesAfterBuild[RuntimeIdentifier.None]()
+ .Concat(new[]
+ {
+ Path.Combine(DotNetUtil.TargetFrameworkMoniker, RuntimeIdentifier.Path, "host", $"{Name}"),
+ })
+ },
+ { RuntimeIdentifier.OSX_x64, () => _additionalObjFilesAfterBuild[RuntimeIdentifier.Linux_x64]() },
+ };
+
+ public override IEnumerable ExpectedObjFilesAfterBuild =>
+ base.ExpectedObjFilesAfterBuild
+ .Concat(RazorUtil.GetExpectedObjFilesAfterBuild(this))
+ .Concat(_additionalObjFilesAfterBuild[RuntimeIdentifier]())
+ // Some files are duplicated in WebTemplate and RazorUtil, since they are needed by RazorClassLibraryTemplate
+ .Distinct();
+
+ public override IEnumerable ExpectedBinFilesAfterBuild =>
+ base.ExpectedBinFilesAfterBuild
+ .Concat(RazorUtil.GetExpectedBinFilesAfterBuild(this));
+
+ public override IEnumerable ExpectedFilesAfterPublish =>
+ base.ExpectedFilesAfterPublish
+ .Concat(RazorUtil.GetExpectedFilesAfterPublish(this))
+ .Concat(new[]
+ {
+ "appsettings.Development.json",
+ "appsettings.json",
+ });
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/RazorBootstrapJQueryTemplate.cs b/test/Cli.FunctionalTests/Templates/RazorBootstrapJQueryTemplate.cs
new file mode 100644
index 0000000000..645ab55dd9
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/RazorBootstrapJQueryTemplate.cs
@@ -0,0 +1,94 @@
+// 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.IO;
+using System.Linq;
+using Cli.FunctionalTests.Util;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public abstract class RazorBootstrapJQueryTemplate : RazorApplicationBaseTemplate
+ {
+ private IDictionary>> _additionalFilesAfterPublish =>
+ new Dictionary>>()
+ {
+ { "common", () => new[]
+ {
+ Path.Combine("wwwroot", "favicon.ico"),
+ Path.Combine("wwwroot", "css", "site.css"),
+ Path.Combine("wwwroot", "css", "site.min.css"),
+ Path.Combine("wwwroot", "images", "banner1.svg"),
+ Path.Combine("wwwroot", "images", "banner2.svg"),
+ Path.Combine("wwwroot", "images", "banner3.svg"),
+ Path.Combine("wwwroot", "js", "site.js"),
+ Path.Combine("wwwroot", "js", "site.min.js"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "LICENSE"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap.css"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap.css.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap.min.css"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap.min.css.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "js", "bootstrap.js"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "js", "bootstrap.min.js"),
+ Path.Combine("wwwroot", "lib", "jquery", ".bower.json"),
+ Path.Combine("wwwroot", "lib", "jquery", "LICENSE.txt"),
+ Path.Combine("wwwroot", "lib", "jquery", "dist", "jquery.js"),
+ Path.Combine("wwwroot", "lib", "jquery", "dist", "jquery.min.js"),
+ Path.Combine("wwwroot", "lib", "jquery", "dist", "jquery.min.map"),
+ Path.Combine("wwwroot", "lib", "jquery-validation", ".bower.json"),
+ Path.Combine("wwwroot", "lib", "jquery-validation", "LICENSE.md"),
+ Path.Combine("wwwroot", "lib", "jquery-validation", "dist", "additional-methods.js"),
+ Path.Combine("wwwroot", "lib", "jquery-validation", "dist", "additional-methods.min.js"),
+ Path.Combine("wwwroot", "lib", "jquery-validation", "dist", "jquery.validate.js"),
+ Path.Combine("wwwroot", "lib", "jquery-validation", "dist", "jquery.validate.min.js"),
+ Path.Combine("wwwroot", "lib", "jquery-validation-unobtrusive", "jquery.validate.unobtrusive.js"),
+ Path.Combine("wwwroot", "lib", "jquery-validation-unobtrusive", "jquery.validate.unobtrusive.min.js"),
+ Path.Combine("wwwroot", "lib", "jquery-validation-unobtrusive", "LICENSE.txt"),
+ }
+ },
+ { "netcoreapp2.1", () =>
+ _additionalFilesAfterPublish["common"]()
+ .Concat(new[]
+ {
+ Path.Combine("wwwroot", "lib", "bootstrap", ".bower.json"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-theme.css"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-theme.css.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-theme.min.css"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-theme.min.css.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "fonts", "glyphicons-halflings-regular.eot"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "fonts", "glyphicons-halflings-regular.svg"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "fonts", "glyphicons-halflings-regular.ttf"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "fonts", "glyphicons-halflings-regular.woff"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "fonts", "glyphicons-halflings-regular.woff2"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "js", "npm.js"),
+ Path.Combine("wwwroot", "lib", "jquery-validation-unobtrusive", ".bower.json"),
+ })
+ },
+ { "netcoreapp2.2", () =>
+ _additionalFilesAfterPublish["common"]()
+ .Concat(new[]
+ {
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-grid.css"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-grid.css.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-grid.min.css"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-grid.min.css.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-reboot.css"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-reboot.css.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-reboot.min.css"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "css", "bootstrap-reboot.min.css.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "js", "bootstrap.bundle.js"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "js", "bootstrap.bundle.js.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "js", "bootstrap.bundle.min.js"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "js", "bootstrap.bundle.min.js.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "js", "bootstrap.js.map"),
+ Path.Combine("wwwroot", "lib", "bootstrap", "dist", "js", "bootstrap.min.js.map"),
+ })
+ },
+ };
+
+ public override IEnumerable ExpectedFilesAfterPublish =>
+ base.ExpectedFilesAfterPublish
+ .Concat(_additionalFilesAfterPublish[DotNetUtil.TargetFrameworkMoniker]());
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/RazorClassLibraryTemplate.cs b/test/Cli.FunctionalTests/Templates/RazorClassLibraryTemplate.cs
new file mode 100644
index 0000000000..a872f2bccc
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/RazorClassLibraryTemplate.cs
@@ -0,0 +1,359 @@
+// 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.IO;
+using System.Linq;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public class RazorClassLibraryTemplate : ClassLibraryTemplate
+ {
+ public RazorClassLibraryTemplate() { }
+
+ public override string Name => "razorclasslib";
+
+ public override IEnumerable ExpectedObjFilesAfterBuild =>
+ base.ExpectedObjFilesAfterBuild
+ .Concat(RazorUtil.GetExpectedObjFilesAfterBuild(this))
+ .Concat(new[]
+ {
+ Path.Combine("Razor", "Areas", "MyFeature", "Pages", "Page1.g.cshtml.cs"),
+ }.Select(p => Path.Combine(OutputPath, p)));
+
+ // We set PreserveCompilationContext=true for all project types in the Razor.Sdk. This results in an refs directory to be created
+ // in the build output directory which is undesirable. We should consider setting PreserveCompilationContext=true only if the
+ // app's an executable.
+ // https://github.com/aspnet/Razor/issues/2308
+ public override IEnumerable ExpectedBinFilesAfterBuild =>
+ base.ExpectedBinFilesAfterBuild
+ .Concat(RazorUtil.GetExpectedBinFilesAfterBuild(this))
+ .Concat(new[]
+ {
+ Path.Combine("refs", "Microsoft.Win32.Primitives.dll"),
+ Path.Combine("refs", "mscorlib.dll"),
+ Path.Combine("refs", "netstandard.dll"),
+ Path.Combine("refs", "System.AppContext.dll"),
+ Path.Combine("refs", "System.Collections.Concurrent.dll"),
+ Path.Combine("refs", "System.Collections.dll"),
+ Path.Combine("refs", "System.Collections.NonGeneric.dll"),
+ Path.Combine("refs", "System.Collections.Specialized.dll"),
+ Path.Combine("refs", "System.ComponentModel.Composition.dll"),
+ Path.Combine("refs", "System.ComponentModel.dll"),
+ Path.Combine("refs", "System.ComponentModel.EventBasedAsync.dll"),
+ Path.Combine("refs", "System.ComponentModel.Primitives.dll"),
+ Path.Combine("refs", "System.ComponentModel.TypeConverter.dll"),
+ Path.Combine("refs", "System.Console.dll"),
+ Path.Combine("refs", "System.Core.dll"),
+ Path.Combine("refs", "System.Data.Common.dll"),
+ Path.Combine("refs", "System.Data.dll"),
+ Path.Combine("refs", "System.Diagnostics.Contracts.dll"),
+ Path.Combine("refs", "System.Diagnostics.Debug.dll"),
+ Path.Combine("refs", "System.Diagnostics.FileVersionInfo.dll"),
+ Path.Combine("refs", "System.Diagnostics.Process.dll"),
+ Path.Combine("refs", "System.Diagnostics.StackTrace.dll"),
+ Path.Combine("refs", "System.Diagnostics.TextWriterTraceListener.dll"),
+ Path.Combine("refs", "System.Diagnostics.Tools.dll"),
+ Path.Combine("refs", "System.Diagnostics.TraceSource.dll"),
+ Path.Combine("refs", "System.Diagnostics.Tracing.dll"),
+ Path.Combine("refs", "System.dll"),
+ Path.Combine("refs", "System.Drawing.dll"),
+ Path.Combine("refs", "System.Drawing.Primitives.dll"),
+ Path.Combine("refs", "System.Dynamic.Runtime.dll"),
+ Path.Combine("refs", "System.Globalization.Calendars.dll"),
+ Path.Combine("refs", "System.Globalization.dll"),
+ Path.Combine("refs", "System.Globalization.Extensions.dll"),
+ Path.Combine("refs", "System.IO.Compression.dll"),
+ Path.Combine("refs", "System.IO.Compression.FileSystem.dll"),
+ Path.Combine("refs", "System.IO.Compression.ZipFile.dll"),
+ Path.Combine("refs", "System.IO.dll"),
+ Path.Combine("refs", "System.IO.FileSystem.dll"),
+ Path.Combine("refs", "System.IO.FileSystem.DriveInfo.dll"),
+ Path.Combine("refs", "System.IO.FileSystem.Primitives.dll"),
+ Path.Combine("refs", "System.IO.FileSystem.Watcher.dll"),
+ Path.Combine("refs", "System.IO.IsolatedStorage.dll"),
+ Path.Combine("refs", "System.IO.MemoryMappedFiles.dll"),
+ Path.Combine("refs", "System.IO.Pipes.dll"),
+ Path.Combine("refs", "System.IO.UnmanagedMemoryStream.dll"),
+ Path.Combine("refs", "System.Linq.dll"),
+ Path.Combine("refs", "System.Linq.Expressions.dll"),
+ Path.Combine("refs", "System.Linq.Parallel.dll"),
+ Path.Combine("refs", "System.Linq.Queryable.dll"),
+ Path.Combine("refs", "System.Net.dll"),
+ Path.Combine("refs", "System.Net.Http.dll"),
+ Path.Combine("refs", "System.Net.NameResolution.dll"),
+ Path.Combine("refs", "System.Net.NetworkInformation.dll"),
+ Path.Combine("refs", "System.Net.Ping.dll"),
+ Path.Combine("refs", "System.Net.Primitives.dll"),
+ Path.Combine("refs", "System.Net.Requests.dll"),
+ Path.Combine("refs", "System.Net.Security.dll"),
+ Path.Combine("refs", "System.Net.Sockets.dll"),
+ Path.Combine("refs", "System.Net.WebHeaderCollection.dll"),
+ Path.Combine("refs", "System.Net.WebSockets.Client.dll"),
+ Path.Combine("refs", "System.Net.WebSockets.dll"),
+ Path.Combine("refs", "System.Numerics.dll"),
+ Path.Combine("refs", "System.ObjectModel.dll"),
+ Path.Combine("refs", "System.Reflection.dll"),
+ Path.Combine("refs", "System.Reflection.Extensions.dll"),
+ Path.Combine("refs", "System.Reflection.Primitives.dll"),
+ Path.Combine("refs", "System.Resources.Reader.dll"),
+ Path.Combine("refs", "System.Resources.ResourceManager.dll"),
+ Path.Combine("refs", "System.Resources.Writer.dll"),
+ Path.Combine("refs", "System.Runtime.CompilerServices.VisualC.dll"),
+ Path.Combine("refs", "System.Runtime.dll"),
+ Path.Combine("refs", "System.Runtime.Extensions.dll"),
+ Path.Combine("refs", "System.Runtime.Handles.dll"),
+ Path.Combine("refs", "System.Runtime.InteropServices.dll"),
+ Path.Combine("refs", "System.Runtime.InteropServices.RuntimeInformation.dll"),
+ Path.Combine("refs", "System.Runtime.Numerics.dll"),
+ Path.Combine("refs", "System.Runtime.Serialization.dll"),
+ Path.Combine("refs", "System.Runtime.Serialization.Formatters.dll"),
+ Path.Combine("refs", "System.Runtime.Serialization.Json.dll"),
+ Path.Combine("refs", "System.Runtime.Serialization.Primitives.dll"),
+ Path.Combine("refs", "System.Runtime.Serialization.Xml.dll"),
+ Path.Combine("refs", "System.Security.Claims.dll"),
+ Path.Combine("refs", "System.Security.Cryptography.Algorithms.dll"),
+ Path.Combine("refs", "System.Security.Cryptography.Csp.dll"),
+ Path.Combine("refs", "System.Security.Cryptography.Encoding.dll"),
+ Path.Combine("refs", "System.Security.Cryptography.Primitives.dll"),
+ Path.Combine("refs", "System.Security.Cryptography.X509Certificates.dll"),
+ Path.Combine("refs", "System.Security.Principal.dll"),
+ Path.Combine("refs", "System.Security.SecureString.dll"),
+ Path.Combine("refs", "System.ServiceModel.Web.dll"),
+ Path.Combine("refs", "System.Text.Encoding.dll"),
+ Path.Combine("refs", "System.Text.Encoding.Extensions.dll"),
+ Path.Combine("refs", "System.Text.RegularExpressions.dll"),
+ Path.Combine("refs", "System.Threading.dll"),
+ Path.Combine("refs", "System.Threading.Overlapped.dll"),
+ Path.Combine("refs", "System.Threading.Tasks.dll"),
+ Path.Combine("refs", "System.Threading.Tasks.Parallel.dll"),
+ Path.Combine("refs", "System.Threading.Thread.dll"),
+ Path.Combine("refs", "System.Threading.ThreadPool.dll"),
+ Path.Combine("refs", "System.Threading.Timer.dll"),
+ Path.Combine("refs", "System.Transactions.dll"),
+ Path.Combine("refs", "System.ValueTuple.dll"),
+ Path.Combine("refs", "System.Web.dll"),
+ Path.Combine("refs", "System.Windows.dll"),
+ Path.Combine("refs", "System.Xml.dll"),
+ Path.Combine("refs", "System.Xml.Linq.dll"),
+ Path.Combine("refs", "System.Xml.ReaderWriter.dll"),
+ Path.Combine("refs", "System.Xml.Serialization.dll"),
+ Path.Combine("refs", "System.Xml.XDocument.dll"),
+ Path.Combine("refs", "System.Xml.XmlDocument.dll"),
+ Path.Combine("refs", "System.Xml.XmlSerializer.dll"),
+ Path.Combine("refs", "System.Xml.XPath.dll"),
+ Path.Combine("refs", "System.Xml.XPath.XDocument.dll"),
+ }.Select(p => Path.Combine(OutputPath, p)));
+
+ private IEnumerable _additionalFilesAfterPublishCommon = new[]
+ {
+ "Microsoft.AspNetCore.Antiforgery.dll",
+ "Microsoft.AspNetCore.Authentication.Abstractions.dll",
+ "Microsoft.AspNetCore.Authentication.Core.dll",
+ "Microsoft.AspNetCore.Authorization.dll",
+ "Microsoft.AspNetCore.Authorization.Policy.dll",
+ "Microsoft.AspNetCore.Cors.dll",
+ "Microsoft.AspNetCore.Cryptography.Internal.dll",
+ "Microsoft.AspNetCore.DataProtection.Abstractions.dll",
+ "Microsoft.AspNetCore.DataProtection.dll",
+ "Microsoft.AspNetCore.Diagnostics.Abstractions.dll",
+ "Microsoft.AspNetCore.Hosting.Abstractions.dll",
+ "Microsoft.AspNetCore.Hosting.Server.Abstractions.dll",
+ "Microsoft.AspNetCore.Html.Abstractions.dll",
+ "Microsoft.AspNetCore.Http.Abstractions.dll",
+ "Microsoft.AspNetCore.Http.dll",
+ "Microsoft.AspNetCore.Http.Extensions.dll",
+ "Microsoft.AspNetCore.Http.Features.dll",
+ "Microsoft.AspNetCore.JsonPatch.dll",
+ "Microsoft.AspNetCore.Localization.dll",
+ "Microsoft.AspNetCore.Mvc.Abstractions.dll",
+ "Microsoft.AspNetCore.Mvc.ApiExplorer.dll",
+ "Microsoft.AspNetCore.Mvc.Core.dll",
+ "Microsoft.AspNetCore.Mvc.Cors.dll",
+ "Microsoft.AspNetCore.Mvc.DataAnnotations.dll",
+ "Microsoft.AspNetCore.Mvc.dll",
+ "Microsoft.AspNetCore.Mvc.Formatters.Json.dll",
+ "Microsoft.AspNetCore.Mvc.Localization.dll",
+ "Microsoft.AspNetCore.Mvc.Razor.dll",
+ "Microsoft.AspNetCore.Mvc.Razor.Extensions.dll",
+ "Microsoft.AspNetCore.Mvc.RazorPages.dll",
+ "Microsoft.AspNetCore.Mvc.TagHelpers.dll",
+ "Microsoft.AspNetCore.Mvc.ViewFeatures.dll",
+ "Microsoft.AspNetCore.Razor.dll",
+ "Microsoft.AspNetCore.Razor.Language.dll",
+ "Microsoft.AspNetCore.Razor.Runtime.dll",
+ "Microsoft.AspNetCore.ResponseCaching.Abstractions.dll",
+ "Microsoft.AspNetCore.Routing.Abstractions.dll",
+ "Microsoft.AspNetCore.Routing.dll",
+ "Microsoft.AspNetCore.WebUtilities.dll",
+ "Microsoft.CodeAnalysis.CSharp.dll",
+ "Microsoft.CodeAnalysis.dll",
+ "Microsoft.CodeAnalysis.Razor.dll",
+ "Microsoft.CSharp.dll",
+ "Microsoft.DotNet.PlatformAbstractions.dll",
+ "Microsoft.Extensions.Caching.Abstractions.dll",
+ "Microsoft.Extensions.Caching.Memory.dll",
+ "Microsoft.Extensions.Configuration.Abstractions.dll",
+ "Microsoft.Extensions.DependencyInjection.Abstractions.dll",
+ "Microsoft.Extensions.DependencyInjection.dll",
+ "Microsoft.Extensions.DependencyModel.dll",
+ "Microsoft.Extensions.FileProviders.Abstractions.dll",
+ "Microsoft.Extensions.FileProviders.Composite.dll",
+ "Microsoft.Extensions.FileSystemGlobbing.dll",
+ "Microsoft.Extensions.Hosting.Abstractions.dll",
+ "Microsoft.Extensions.Localization.Abstractions.dll",
+ "Microsoft.Extensions.Localization.dll",
+ "Microsoft.Extensions.Logging.Abstractions.dll",
+ "Microsoft.Extensions.ObjectPool.dll",
+ "Microsoft.Extensions.Options.dll",
+ "Microsoft.Extensions.Primitives.dll",
+ "Microsoft.Extensions.WebEncoders.dll",
+ "Microsoft.Net.Http.Headers.dll",
+ "Microsoft.Win32.Registry.dll",
+ "Newtonsoft.Json.Bson.dll",
+ "Newtonsoft.Json.dll",
+ "System.AppContext.dll",
+ "System.Buffers.dll",
+ "System.Collections.Concurrent.dll",
+ "System.Collections.Immutable.dll",
+ "System.ComponentModel.Annotations.dll",
+ "System.Diagnostics.DiagnosticSource.dll",
+ "System.Diagnostics.StackTrace.dll",
+ "System.Dynamic.Runtime.dll",
+ "System.IO.FileSystem.Primitives.dll",
+ "System.Linq.dll",
+ "System.Linq.Expressions.dll",
+ "System.Memory.dll",
+ "System.Numerics.Vectors.dll",
+ "System.ObjectModel.dll",
+ "System.Reflection.Emit.dll",
+ "System.Reflection.Emit.ILGeneration.dll",
+ "System.Reflection.Emit.Lightweight.dll",
+ "System.Reflection.Metadata.dll",
+ "System.Reflection.TypeExtensions.dll",
+ "System.Runtime.CompilerServices.Unsafe.dll",
+ "System.Runtime.Numerics.dll",
+ "System.Security.AccessControl.dll",
+ "System.Security.Cryptography.Cng.dll",
+ "System.Security.Cryptography.OpenSsl.dll",
+ "System.Security.Cryptography.Pkcs.dll",
+ "System.Security.Cryptography.Primitives.dll",
+ "System.Security.Cryptography.Xml.dll",
+ "System.Security.Permissions.dll",
+ "System.Security.Principal.Windows.dll",
+ "System.Text.Encodings.Web.dll",
+ "System.Text.RegularExpressions.dll",
+ "System.Threading.dll",
+ "System.Threading.Tasks.Extensions.dll",
+ "System.Threading.Tasks.Parallel.dll",
+ "System.Threading.Thread.dll",
+ "System.ValueTuple.dll",
+ "System.Xml.ReaderWriter.dll",
+ "System.Xml.XDocument.dll",
+ "System.Xml.XmlDocument.dll",
+ "System.Xml.XPath.dll",
+ "System.Xml.XPath.XDocument.dll",
+ };
+
+ private IEnumerable _additionalFilesAfterPublishSelfContained = new[]
+ {
+ "System.Collections.dll",
+ "System.Console.dll",
+ "System.Diagnostics.Debug.dll",
+ "System.Diagnostics.FileVersionInfo.dll",
+ "System.Diagnostics.Tools.dll",
+ "System.Diagnostics.Tracing.dll",
+ "System.Globalization.Calendars.dll",
+ "System.Globalization.dll",
+ "System.IO.Compression.dll",
+ "System.IO.dll",
+ "System.IO.FileSystem.dll",
+ "System.Reflection.dll",
+ "System.Reflection.Extensions.dll",
+ "System.Reflection.Primitives.dll",
+ "System.Resources.ResourceManager.dll",
+ "System.Runtime.dll",
+ "System.Runtime.Extensions.dll",
+ "System.Runtime.Handles.dll",
+ "System.Runtime.InteropServices.dll",
+ "System.Runtime.InteropServices.RuntimeInformation.dll",
+ "System.Security.Cryptography.Algorithms.dll",
+ "System.Security.Cryptography.Csp.dll",
+ "System.Security.Cryptography.Encoding.dll",
+ "System.Security.Cryptography.X509Certificates.dll",
+ "System.Text.Encoding.CodePages.dll",
+ "System.Text.Encoding.dll",
+ "System.Text.Encoding.Extensions.dll",
+ "System.Threading.Tasks.dll",
+ };
+
+ private IDictionary>> _additionalFilesAfterPublish =>
+ new Dictionary>>()
+ {
+ { RuntimeIdentifier.None, () =>
+ _additionalFilesAfterPublishCommon
+ .Concat(new[]
+ {
+ Path.Combine("runtimes", "debian.8-x64", "native", "System.Security.Cryptography.Native.OpenSsl.so"),
+ Path.Combine("runtimes", "fedora.23-x64", "native", "System.Security.Cryptography.Native.OpenSsl.so"),
+ Path.Combine("runtimes", "fedora.24-x64", "native", "System.Security.Cryptography.Native.OpenSsl.so"),
+ Path.Combine("runtimes", "opensuse.13.2-x64", "native", "System.Security.Cryptography.Native.OpenSsl.so"),
+ Path.Combine("runtimes", "opensuse.42.1-x64", "native", "System.Security.Cryptography.Native.OpenSsl.so"),
+ Path.Combine("runtimes", "osx", "lib", "netstandard1.6", "System.Security.Cryptography.Algorithms.dll"),
+ Path.Combine("runtimes", "osx.10.10-x64", "native", "System.Security.Cryptography.Native.Apple.dylib"),
+ Path.Combine("runtimes", "osx.10.10-x64", "native", "System.Security.Cryptography.Native.OpenSsl.dylib"),
+ Path.Combine("runtimes", "rhel.7-x64", "native", "System.Security.Cryptography.Native.OpenSsl.so"),
+ Path.Combine("runtimes", "ubuntu.14.04-x64", "native", "System.Security.Cryptography.Native.OpenSsl.so"),
+ Path.Combine("runtimes", "ubuntu.16.04-x64", "native", "System.Security.Cryptography.Native.OpenSsl.so"),
+ Path.Combine("runtimes", "ubuntu.16.10-x64", "native", "System.Security.Cryptography.Native.OpenSsl.so"),
+ Path.Combine("runtimes", "unix", "lib", "netstandard1.1", "System.Runtime.InteropServices.RuntimeInformation.dll"),
+ Path.Combine("runtimes", "unix", "lib", "netstandard1.3", "System.Diagnostics.FileVersionInfo.dll"),
+ Path.Combine("runtimes", "unix", "lib", "netstandard1.3", "System.IO.Compression.dll"),
+ Path.Combine("runtimes", "unix", "lib", "netstandard1.3", "System.Security.Cryptography.Csp.dll"),
+ Path.Combine("runtimes", "unix", "lib", "netstandard1.3", "System.Security.Cryptography.Encoding.dll"),
+ Path.Combine("runtimes", "unix", "lib", "netstandard1.3", "System.Text.Encoding.CodePages.dll"),
+ Path.Combine("runtimes", "unix", "lib", "netstandard1.6", "System.Security.Cryptography.Algorithms.dll"),
+ Path.Combine("runtimes", "unix", "lib", "netstandard1.6", "System.Security.Cryptography.OpenSsl.dll"),
+ Path.Combine("runtimes", "unix", "lib", "netstandard1.6", "System.Security.Cryptography.X509Certificates.dll"),
+ Path.Combine("runtimes", "unix", "lib", "netstandard2.0", "Microsoft.Win32.Registry.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.1", "System.Runtime.InteropServices.RuntimeInformation.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.3", "System.Diagnostics.FileVersionInfo.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.3", "System.IO.Compression.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.3", "System.Security.AccessControl.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.3", "System.Security.Cryptography.Csp.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.3", "System.Security.Cryptography.Encoding.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.3", "System.Security.Principal.Windows.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.3", "System.Text.Encoding.CodePages.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.6", "System.Security.Cryptography.Algorithms.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.6", "System.Security.Cryptography.Cng.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard1.6", "System.Security.Cryptography.X509Certificates.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard2.0", "Microsoft.Win32.Registry.dll"),
+ Path.Combine("runtimes", "win", "lib", "netstandard2.0", "System.Security.Cryptography.Pkcs.dll"),
+ })
+ },
+ { RuntimeIdentifier.Win_x64, () =>
+ _additionalFilesAfterPublishCommon
+ .Concat(_additionalFilesAfterPublishSelfContained)
+ .Concat(new[]
+ {
+ "System.Threading.Overlapped.dll",
+ })
+ },
+ { RuntimeIdentifier.Linux_x64, () =>
+ _additionalFilesAfterPublishCommon
+ .Concat(_additionalFilesAfterPublishSelfContained)
+ .Concat(new[]
+ {
+ "System.Private.Uri.dll",
+ })
+ },
+ { RuntimeIdentifier.OSX_x64, () => _additionalFilesAfterPublish[RuntimeIdentifier.Linux_x64]() },
+ };
+
+ public override IEnumerable ExpectedFilesAfterPublish =>
+ base.ExpectedFilesAfterPublish
+ .Concat(RazorUtil.GetExpectedFilesAfterPublish(this))
+ .Concat(_additionalFilesAfterPublish[RuntimeIdentifier]());
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/RazorTemplate.cs b/test/Cli.FunctionalTests/Templates/RazorTemplate.cs
new file mode 100644
index 0000000000..778ef5833d
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/RazorTemplate.cs
@@ -0,0 +1,49 @@
+// 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.IO;
+using System.Linq;
+using Cli.FunctionalTests.Util;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public class RazorTemplate : RazorBootstrapJQueryTemplate
+ {
+ public RazorTemplate() { }
+
+ public override string Name => "razor";
+
+ protected override string RazorPath => "Pages";
+
+ private IDictionary>> _additionalObjFilesAfterBuild =>
+ new Dictionary>>()
+ {
+ { "common", () => new[]
+ {
+ Path.Combine("Razor", RazorPath, "_ViewStart.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Error.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Index.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Privacy.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Shared", "_CookieConsentPartial.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Shared", "_Layout.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Shared", "_ValidationScriptsPartial.g.cshtml.cs"),
+ }
+ },
+ { "netcoreapp2.1", () =>
+ _additionalObjFilesAfterBuild["common"]()
+ .Concat(new[]
+ {
+ Path.Combine("Razor", RazorPath, "About.g.cshtml.cs"),
+ Path.Combine("Razor", RazorPath, "Contact.g.cshtml.cs"),
+ })
+ },
+ { "netcoreapp2.2", () => _additionalObjFilesAfterBuild["common"]() },
+ };
+
+ public override IEnumerable ExpectedObjFilesAfterBuild =>
+ base.ExpectedObjFilesAfterBuild
+ .Concat(_additionalObjFilesAfterBuild[DotNetUtil.TargetFrameworkMoniker]().Select(p => Path.Combine(OutputPath, p)));
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/RazorUtil.cs b/test/Cli.FunctionalTests/Templates/RazorUtil.cs
new file mode 100644
index 0000000000..3a1ad48db2
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/RazorUtil.cs
@@ -0,0 +1,39 @@
+// 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.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public static class RazorUtil
+ {
+ public static IEnumerable GetExpectedObjFilesAfterBuild(Template template) => new[]
+ {
+ // Added between 2.1.300-rc1 and 2.1.300-rtm (https://github.com/aspnet/Razor/pull/2316)
+ $"{template.Name}.csproj.CopyComplete",
+ $"{template.Name}.RazorAssemblyInfo.cache",
+ $"{template.Name}.RazorAssemblyInfo.cs",
+ $"{template.Name}.RazorCoreGenerate.cache",
+ $"{template.Name}.RazorTargetAssemblyInfo.cache",
+ $"{template.Name}.RazorTargetAssemblyInfo.cs",
+ $"{template.Name}.TagHelpers.input.cache",
+ $"{template.Name}.TagHelpers.output.cache",
+ $"{template.Name}.Views.dll",
+ $"{template.Name}.Views.pdb",
+ }.Select(p => Path.Combine(template.OutputPath, p));
+
+ public static IEnumerable GetExpectedBinFilesAfterBuild(Template template) => new[]
+ {
+ $"{template.Name}.Views.dll",
+ $"{template.Name}.Views.pdb",
+ }.Select(p => Path.Combine(template.OutputPath, p));
+
+ public static IEnumerable GetExpectedFilesAfterPublish(Template template) => new[]
+ {
+ $"{template.Name}.Views.dll",
+ $"{template.Name}.Views.pdb",
+ };
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/ReactReduxTemplate.cs b/test/Cli.FunctionalTests/Templates/ReactReduxTemplate.cs
new file mode 100644
index 0000000000..11cd150541
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/ReactReduxTemplate.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace Cli.FunctionalTests.Templates
+{
+ public class ReactReduxTemplate : ReactTemplate
+ {
+ public ReactReduxTemplate() { }
+
+ public override string Name => "reactredux";
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/ReactTemplate.cs b/test/Cli.FunctionalTests/Templates/ReactTemplate.cs
new file mode 100644
index 0000000000..212f4763c5
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/ReactTemplate.cs
@@ -0,0 +1,60 @@
+// 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.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Cli.FunctionalTests.Util;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public class ReactTemplate : SpaBaseTemplate
+ {
+ public ReactTemplate() { }
+
+ public override string Name => "react";
+
+ protected override IEnumerable NormalizeFilesAfterPublish(IEnumerable filesAfterPublish)
+ {
+ // Remove generated hashes since they may vary by platform
+ return base.NormalizeFilesAfterPublish(filesAfterPublish)
+ .Select(f => Regex.Replace(f, @"\.[0-9a-f]{8}\.", ".[HASH]."));
+ }
+
+ private IDictionary>> _additionalFilesAfterPublish =>
+ new Dictionary>>()
+ {
+ { "common", () => new[]
+ {
+ Path.Combine("ClientApp", "build", "asset-manifest.json"),
+ Path.Combine("ClientApp", "build", "favicon.ico"),
+ Path.Combine("ClientApp", "build", "index.html"),
+ Path.Combine("ClientApp", "build", "manifest.json"),
+ Path.Combine("ClientApp", "build", "service-worker.js"),
+ Path.Combine("ClientApp", "build", "static", "css", "main.[HASH].css"),
+ Path.Combine("ClientApp", "build", "static", "css", "main.[HASH].css.map"),
+ Path.Combine("ClientApp", "build", "static", "js", "main.[HASH].js"),
+ Path.Combine("ClientApp", "build", "static", "js", "main.[HASH].js.map"),
+ }
+ },
+ { "netcoreapp2.1", () =>
+ _additionalFilesAfterPublish["common"]()
+ .Concat(new[]
+ {
+ Path.Combine("ClientApp", "build", "static", "media", "glyphicons-halflings-regular.[HASH].woff2"),
+ Path.Combine("ClientApp", "build", "static", "media", "glyphicons-halflings-regular.[HASH].svg"),
+ Path.Combine("ClientApp", "build", "static", "media", "glyphicons-halflings-regular.[HASH].ttf"),
+ Path.Combine("ClientApp", "build", "static", "media", "glyphicons-halflings-regular.[HASH].eot"),
+ Path.Combine("ClientApp", "build", "static", "media", "glyphicons-halflings-regular.[HASH].woff"),
+ })
+ },
+ { "netcoreapp2.2", () => _additionalFilesAfterPublish["common"]() },
+ };
+
+ public override IEnumerable ExpectedFilesAfterPublish =>
+ base.ExpectedFilesAfterPublish
+ .Concat(_additionalFilesAfterPublish[DotNetUtil.TargetFrameworkMoniker]());
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/SpaBaseTemplate.cs b/test/Cli.FunctionalTests/Templates/SpaBaseTemplate.cs
new file mode 100644
index 0000000000..6cfeccc16c
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/SpaBaseTemplate.cs
@@ -0,0 +1,21 @@
+// 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.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public abstract class SpaBaseTemplate : RazorApplicationBaseTemplate
+ {
+ protected override string RazorPath => "Pages";
+
+ public override IEnumerable ExpectedObjFilesAfterBuild =>
+ base.ExpectedObjFilesAfterBuild
+ .Concat(new[]
+ {
+ Path.Combine("Razor", RazorPath, "Error.g.cshtml.cs"),
+ }.Select(p => Path.Combine(OutputPath, p)));
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/Template.cs b/test/Cli.FunctionalTests/Templates/Template.cs
new file mode 100644
index 0000000000..72e7837df2
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/Template.cs
@@ -0,0 +1,248 @@
+// 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.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading;
+using Cli.FunctionalTests.Util;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public abstract class Template
+ {
+ private static readonly TimeSpan _sleepBetweenOutputContains = TimeSpan.FromMilliseconds(100);
+
+ private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler()
+ {
+ // Allow self-signed certs
+ ServerCertificateCustomValidationCallback = (m, c, ch, p) => true
+ })
+ {
+ // Prevent failures due to slow requests when running many tests in parallel
+ Timeout = Timeout.InfiniteTimeSpan,
+ };
+
+ private static ConcurrentDictionary<(Type Type, NuGetPackageSource NuGetPackageSource, RuntimeIdentifier RuntimeIdentifier), Template> _templates =
+ new ConcurrentDictionary<(Type Type, NuGetPackageSource NuGetPackageSource, RuntimeIdentifier RuntimeIdentifier), Template>();
+
+ public static T GetInstance(NuGetPackageSource nuGetPackageSource, RuntimeIdentifier runtimeIdentifier) where T : Template, new()
+ {
+ return (T)_templates.GetOrAdd((typeof(T), nuGetPackageSource, runtimeIdentifier),
+ (k) => new T() { NuGetPackageSource = nuGetPackageSource, RuntimeIdentifier = runtimeIdentifier });
+ }
+
+ private Lazy> _objFilesAfterRestore;
+ private Lazy> _objFilesAfterRestoreIncremental;
+ private Lazy<(IEnumerable ObjFiles, IEnumerable BinFiles)> _filesAfterBuild;
+ private Lazy<(IEnumerable ObjFiles, IEnumerable BinFiles)> _filesAfterBuildIncremental;
+ private Lazy> _filesAfterPublish;
+ private Lazy> _filesAfterPublishIncremental;
+ private Lazy<(HttpResponseMessage Http, HttpResponseMessage Https, string ServerOutput, string ServerError )> _httpResponsesAfterRun;
+ private Lazy<(HttpResponseMessage Http, HttpResponseMessage Https, string ServerOutput, string ServerError)> _httpResponsesAfterExec;
+
+ public NuGetPackageSource NuGetPackageSource { get; private set; }
+ public RuntimeIdentifier RuntimeIdentifier { get; private set; }
+
+ protected Template()
+ {
+ _objFilesAfterRestore = new Lazy>(
+ GetObjFilesAfterRestore, LazyThreadSafetyMode.ExecutionAndPublication);
+
+ _objFilesAfterRestoreIncremental = new Lazy>(
+ GetObjFilesAfterRestoreIncremental, LazyThreadSafetyMode.ExecutionAndPublication);
+
+ _filesAfterBuild = new Lazy<(IEnumerable ObjFiles, IEnumerable BinFiles)>(
+ GetFilesAfterBuild, LazyThreadSafetyMode.ExecutionAndPublication);
+
+ _filesAfterBuildIncremental = new Lazy<(IEnumerable ObjFiles, IEnumerable BinFiles)>(
+ GetFilesAfterBuildIncremental, LazyThreadSafetyMode.ExecutionAndPublication);
+
+ _filesAfterPublish = new Lazy>(
+ GetFilesAfterPublish, LazyThreadSafetyMode.ExecutionAndPublication);
+
+ _filesAfterPublishIncremental = new Lazy>(
+ GetFilesAfterPublishIncremental, LazyThreadSafetyMode.ExecutionAndPublication);
+
+ _httpResponsesAfterRun = new Lazy<(HttpResponseMessage Http, HttpResponseMessage Https, string ServerOutput, string ServerError)>(
+ GetHttpResponsesAfterRun, LazyThreadSafetyMode.ExecutionAndPublication);
+
+ _httpResponsesAfterExec = new Lazy<(HttpResponseMessage Http, HttpResponseMessage Https, string ServerOutput, string ServerError)>(
+ GetHttpResponsesAfterExec, LazyThreadSafetyMode.ExecutionAndPublication);
+ }
+
+ public override string ToString() => $"{Name}, source: {NuGetPackageSource}, rid: {RuntimeIdentifier}, sdk: {DotNetUtil.SdkVersion}, runtime: {DotNetUtil.RuntimeVersion}";
+
+ private string TempDir => Path.Combine(AssemblySetUp.TempDir, Name, NuGetPackageSource.Name, RuntimeIdentifier.Name );
+
+ public abstract string Name { get; }
+ public abstract string OutputPath { get; }
+ public abstract TemplateType Type { get; }
+ public virtual string RelativeUrl => string.Empty;
+
+ public IEnumerable ObjFilesAfterRestore => _objFilesAfterRestore.Value;
+ public IEnumerable ObjFilesAfterRestoreIncremental => _objFilesAfterRestoreIncremental.Value;
+ public IEnumerable ObjFilesAfterBuild => _filesAfterBuild.Value.ObjFiles;
+ public IEnumerable BinFilesAfterBuild => _filesAfterBuild.Value.BinFiles;
+ public IEnumerable ObjFilesAfterBuildIncremental => _filesAfterBuildIncremental.Value.ObjFiles;
+ public IEnumerable BinFilesAfterBuildIncremental => _filesAfterBuildIncremental.Value.BinFiles;
+ public IEnumerable FilesAfterPublish => NormalizeFilesAfterPublish(_filesAfterPublish.Value);
+ public IEnumerable FilesAfterPublishIncremental => NormalizeFilesAfterPublish(_filesAfterPublishIncremental.Value);
+ public HttpResponseMessage HttpResponseAfterRun => _httpResponsesAfterRun.Value.Http;
+ public HttpResponseMessage HttpsResponseAfterRun => _httpResponsesAfterRun.Value.Https;
+ public string ServerOutputAfterRun => _httpResponsesAfterRun.Value.ServerOutput;
+ public string ServerErrorAfterRun => _httpResponsesAfterRun.Value.ServerError;
+ public HttpResponseMessage HttpResponseAfterExec => _httpResponsesAfterExec.Value.Http;
+ public HttpResponseMessage HttpsResponseAfterExec => _httpResponsesAfterExec.Value.Https;
+ public string ServerOutputAfterExec => _httpResponsesAfterExec.Value.ServerOutput;
+ public string ServerErrorAfterExec => _httpResponsesAfterExec.Value.ServerError;
+
+ public virtual IEnumerable ExpectedObjFilesAfterRestore => new[]
+ {
+ $"{Name}.csproj.nuget.cache",
+ $"{Name}.csproj.nuget.g.props",
+ $"{Name}.csproj.nuget.g.targets",
+ "project.assets.json",
+ };
+
+ public virtual IEnumerable ExpectedObjFilesAfterBuild => ExpectedObjFilesAfterRestore;
+
+ public abstract IEnumerable ExpectedBinFilesAfterBuild { get; }
+
+ public abstract IEnumerable ExpectedFilesAfterPublish { get; }
+
+ // Hook for subclasses to modify template immediately after "dotnet new". Typically used
+ // for temporary workarounds (e.g. changing TFM in csproj).
+ protected virtual void AfterNew(string tempDir) { }
+
+ // Hook for subclasses to normalize files after publish (e.g. replacing hash codes)
+ protected virtual IEnumerable NormalizeFilesAfterPublish(IEnumerable filesAfterPublish)
+ {
+ return filesAfterPublish;
+ }
+
+ private IEnumerable GetObjFilesAfterRestore()
+ {
+ Directory.CreateDirectory(TempDir);
+ DotNetUtil.New(Name, TempDir);
+ AfterNew(TempDir);
+ DotNetUtil.Restore(TempDir, NuGetPackageSource, RuntimeIdentifier);
+ return IOUtil.GetFiles(Path.Combine(TempDir, "obj"));
+ }
+
+ private IEnumerable GetObjFilesAfterRestoreIncremental()
+ {
+ // RestoreIncremental depends on Restore
+ _ = ObjFilesAfterRestore;
+
+ DotNetUtil.Restore(TempDir, NuGetPackageSource, RuntimeIdentifier);
+ return IOUtil.GetFiles(Path.Combine(TempDir, "obj"));
+ }
+
+ private (IEnumerable ObjFiles, IEnumerable BinFiles) GetFilesAfterBuild()
+ {
+ // Build depends on RestoreIncremental
+ _ = ObjFilesAfterRestoreIncremental;
+
+ DotNetUtil.Build(TempDir, NuGetPackageSource, RuntimeIdentifier);
+ return (IOUtil.GetFiles(Path.Combine(TempDir, "obj")), IOUtil.GetFiles(Path.Combine(TempDir, "bin")));
+ }
+
+ private (IEnumerable ObjFiles, IEnumerable BinFiles) GetFilesAfterBuildIncremental()
+ {
+ // BuildIncremental depends on Build
+ _ = ObjFilesAfterBuild;
+
+ DotNetUtil.Build(TempDir, NuGetPackageSource, RuntimeIdentifier);
+ return (IOUtil.GetFiles(Path.Combine(TempDir, "obj")), IOUtil.GetFiles(Path.Combine(TempDir, "bin")));
+ }
+
+ private IEnumerable GetFilesAfterPublish()
+ {
+ // Publish depends on BuildIncremental
+ _ = BinFilesAfterBuildIncremental;
+
+ DotNetUtil.Publish(TempDir, RuntimeIdentifier);
+ return IOUtil.GetFiles(Path.Combine(TempDir, DotNetUtil.PublishOutput));
+ }
+
+ private IEnumerable GetFilesAfterPublishIncremental()
+ {
+ // PublishIncremental depends on Publish
+ _ = FilesAfterPublish;
+
+ DotNetUtil.Publish(TempDir, RuntimeIdentifier);
+ return IOUtil.GetFiles(Path.Combine(TempDir, DotNetUtil.PublishOutput));
+ }
+
+ private (HttpResponseMessage Http, HttpResponseMessage Https, string ServerOutput, string ServerError) GetHttpResponsesAfterRun()
+ {
+ // Run depends on BuildIncremental
+ _ = BinFilesAfterBuildIncremental;
+
+ return GetHttpResponses(DotNetUtil.Run(TempDir, RuntimeIdentifier));
+ }
+
+ private (HttpResponseMessage Http, HttpResponseMessage Https, string ServerOutput, string ServerError) GetHttpResponsesAfterExec()
+ {
+ // Exec depends on PublishIncremental
+ _ = FilesAfterPublishIncremental;
+
+ return GetHttpResponses(DotNetUtil.Exec(TempDir, Name, RuntimeIdentifier));
+ }
+
+ private (HttpResponseMessage Http, HttpResponseMessage Https, string ServerOutput, string ServerError) GetHttpResponses(
+ (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) process)
+ {
+ try
+ {
+ var (httpUrl, httpsUrl) = ScrapeUrls(process);
+ return (
+ Get(new Uri(new Uri(httpUrl), RelativeUrl)),
+ Get(new Uri(new Uri(httpsUrl), RelativeUrl)),
+ process.OutputBuilder.ToString(),
+ process.ErrorBuilder.ToString()
+ );
+ }
+ finally
+ {
+ DotNetUtil.StopProcess(process, throwOnError: false);
+ }
+ }
+
+ private (string HttpUrl, string HttpsUrl) ScrapeUrls(
+ (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) process)
+ {
+ // Extract URLs from output
+ while (true)
+ {
+ var output = process.OutputBuilder.ToString();
+ if (output.Contains("Application started"))
+ {
+ var httpUrl = Regex.Match(output, @"Now listening on: (http:\S*)").Groups[1].Value;
+ var httpsUrl = Regex.Match(output, @"Now listening on: (https:\S*)").Groups[1].Value;
+ return (httpUrl, httpsUrl);
+ }
+ else if (process.Process.HasExited)
+ {
+ var startInfo = process.Process.StartInfo;
+ throw new InvalidOperationException(
+ $"Failed to start process '{startInfo.FileName} {startInfo.Arguments}'" + Environment.NewLine + output);
+ }
+ else
+ {
+ Thread.Sleep(_sleepBetweenOutputContains);
+ }
+ }
+ }
+
+ private HttpResponseMessage Get(Uri requestUri)
+ {
+ return _httpClient.GetAsync(requestUri).Result;
+ }
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/TemplateType.cs b/test/Cli.FunctionalTests/Templates/TemplateType.cs
new file mode 100644
index 0000000000..564f5c06a8
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/TemplateType.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace Cli.FunctionalTests.Templates
+{
+ public enum TemplateType
+ {
+ ClassLibrary,
+ ConsoleApplication,
+ WebApplication,
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/WebApiTemplate.cs b/test/Cli.FunctionalTests/Templates/WebApiTemplate.cs
new file mode 100644
index 0000000000..4bb6fa3c48
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/WebApiTemplate.cs
@@ -0,0 +1,25 @@
+// 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.Collections.Generic;
+using System.Linq;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public class WebApiTemplate : WebTemplate
+ {
+ public WebApiTemplate() { }
+
+ public override string Name => "webapi";
+
+ public override string RelativeUrl => "/api/values";
+
+ public override IEnumerable ExpectedFilesAfterPublish =>
+ base.ExpectedFilesAfterPublish
+ .Concat(new[]
+ {
+ "appsettings.Development.json",
+ "appsettings.json",
+ });
+ }
+}
diff --git a/test/Cli.FunctionalTests/Templates/WebTemplate.cs b/test/Cli.FunctionalTests/Templates/WebTemplate.cs
new file mode 100644
index 0000000000..a2e77925f6
--- /dev/null
+++ b/test/Cli.FunctionalTests/Templates/WebTemplate.cs
@@ -0,0 +1,251 @@
+// 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.IO;
+using System.Linq;
+using Cli.FunctionalTests.Util;
+
+namespace Cli.FunctionalTests.Templates
+{
+ public class WebTemplate : ConsoleApplicationTemplate
+ {
+ public WebTemplate() { }
+
+ public override string Name => "web";
+
+ public override TemplateType Type => TemplateType.WebApplication;
+
+ public override IEnumerable ExpectedObjFilesAfterBuild =>
+ base.ExpectedObjFilesAfterBuild
+ .Concat(new[]
+ {
+ $"{Name}.RazorAssemblyInfo.cache",
+ $"{Name}.RazorAssemblyInfo.cs",
+ $"{Name}.RazorTargetAssemblyInfo.cache",
+ }.Select(p => Path.Combine(OutputPath, p)));
+
+ private IDictionary<(string TargetFrameworkMoniker, RuntimeIdentifier), Func>> _additionalFilesAfterPublish =>
+ new Dictionary<(string TargetFrameworkMoniker, RuntimeIdentifier), Func>>()
+ {
+ { ("netcoreapp2.1", RuntimeIdentifier.None), () => new[]
+ {
+ // Publish includes all *.config and *.json files (https://github.com/aspnet/websdk/issues/334)
+ "NuGet.config",
+ "web.config",
+ }
+ },
+ { ("netcoreapp2.1", RuntimeIdentifier.Linux_x64), () =>
+ _additionalFilesAfterPublish[("netcoreapp2.1", RuntimeIdentifier.None)]()
+ .Concat(new[]
+ {
+ "Microsoft.AspNetCore.Antiforgery.dll",
+ "Microsoft.AspNetCore.Authentication.Abstractions.dll",
+ "Microsoft.AspNetCore.Authentication.Cookies.dll",
+ "Microsoft.AspNetCore.Authentication.Core.dll",
+ "Microsoft.AspNetCore.Authentication.dll",
+ "Microsoft.AspNetCore.Authentication.Facebook.dll",
+ "Microsoft.AspNetCore.Authentication.Google.dll",
+ "Microsoft.AspNetCore.Authentication.JwtBearer.dll",
+ "Microsoft.AspNetCore.Authentication.MicrosoftAccount.dll",
+ "Microsoft.AspNetCore.Authentication.OAuth.dll",
+ "Microsoft.AspNetCore.Authentication.OpenIdConnect.dll",
+ "Microsoft.AspNetCore.Authentication.Twitter.dll",
+ "Microsoft.AspNetCore.Authentication.WsFederation.dll",
+ "Microsoft.AspNetCore.Authorization.dll",
+ "Microsoft.AspNetCore.Authorization.Policy.dll",
+ "Microsoft.AspNetCore.Connections.Abstractions.dll",
+ "Microsoft.AspNetCore.CookiePolicy.dll",
+ "Microsoft.AspNetCore.Cors.dll",
+ "Microsoft.AspNetCore.Cryptography.Internal.dll",
+ "Microsoft.AspNetCore.Cryptography.KeyDerivation.dll",
+ "Microsoft.AspNetCore.DataProtection.Abstractions.dll",
+ "Microsoft.AspNetCore.DataProtection.dll",
+ "Microsoft.AspNetCore.DataProtection.Extensions.dll",
+ "Microsoft.AspNetCore.Diagnostics.Abstractions.dll",
+ "Microsoft.AspNetCore.Diagnostics.dll",
+ "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.dll",
+ "Microsoft.AspNetCore.dll",
+ "Microsoft.AspNetCore.HostFiltering.dll",
+ "Microsoft.AspNetCore.Hosting.Abstractions.dll",
+ "Microsoft.AspNetCore.Hosting.dll",
+ "Microsoft.AspNetCore.Hosting.Server.Abstractions.dll",
+ "Microsoft.AspNetCore.Html.Abstractions.dll",
+ "Microsoft.AspNetCore.Http.Abstractions.dll",
+ "Microsoft.AspNetCore.Http.Connections.Common.dll",
+ "Microsoft.AspNetCore.Http.Connections.dll",
+ "Microsoft.AspNetCore.Http.dll",
+ "Microsoft.AspNetCore.Http.Extensions.dll",
+ "Microsoft.AspNetCore.Http.Features.dll",
+ "Microsoft.AspNetCore.HttpOverrides.dll",
+ "Microsoft.AspNetCore.HttpsPolicy.dll",
+ "Microsoft.AspNetCore.Identity.dll",
+ "Microsoft.AspNetCore.Identity.EntityFrameworkCore.dll",
+ "Microsoft.AspNetCore.Identity.UI.dll",
+ "Microsoft.AspNetCore.Identity.UI.Views.dll",
+ "Microsoft.AspNetCore.JsonPatch.dll",
+ "Microsoft.AspNetCore.Localization.dll",
+ "Microsoft.AspNetCore.Localization.Routing.dll",
+ "Microsoft.AspNetCore.MiddlewareAnalysis.dll",
+ "Microsoft.AspNetCore.Mvc.Abstractions.dll",
+ "Microsoft.AspNetCore.Mvc.ApiExplorer.dll",
+ "Microsoft.AspNetCore.Mvc.Core.dll",
+ "Microsoft.AspNetCore.Mvc.Cors.dll",
+ "Microsoft.AspNetCore.Mvc.DataAnnotations.dll",
+ "Microsoft.AspNetCore.Mvc.dll",
+ "Microsoft.AspNetCore.Mvc.Formatters.Json.dll",
+ "Microsoft.AspNetCore.Mvc.Formatters.Xml.dll",
+ "Microsoft.AspNetCore.Mvc.Localization.dll",
+ "Microsoft.AspNetCore.Mvc.Razor.dll",
+ "Microsoft.AspNetCore.Mvc.Razor.Extensions.dll",
+ "Microsoft.AspNetCore.Mvc.RazorPages.dll",
+ "Microsoft.AspNetCore.Mvc.TagHelpers.dll",
+ "Microsoft.AspNetCore.Mvc.ViewFeatures.dll",
+ "Microsoft.AspNetCore.NodeServices.dll",
+ "Microsoft.AspNetCore.Owin.dll",
+ "Microsoft.AspNetCore.Razor.dll",
+ "Microsoft.AspNetCore.Razor.Language.dll",
+ "Microsoft.AspNetCore.Razor.Runtime.dll",
+ "Microsoft.AspNetCore.ResponseCaching.Abstractions.dll",
+ "Microsoft.AspNetCore.ResponseCaching.dll",
+ "Microsoft.AspNetCore.ResponseCompression.dll",
+ "Microsoft.AspNetCore.Rewrite.dll",
+ "Microsoft.AspNetCore.Routing.Abstractions.dll",
+ "Microsoft.AspNetCore.Routing.dll",
+ "Microsoft.AspNetCore.Server.HttpSys.dll",
+ "Microsoft.AspNetCore.Server.IISIntegration.dll",
+ "Microsoft.AspNetCore.Server.Kestrel.Core.dll",
+ "Microsoft.AspNetCore.Server.Kestrel.dll",
+ "Microsoft.AspNetCore.Server.Kestrel.Https.dll",
+ "Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.dll",
+ "Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.dll",
+ "Microsoft.AspNetCore.Session.dll",
+ "Microsoft.AspNetCore.SignalR.Common.dll",
+ "Microsoft.AspNetCore.SignalR.Core.dll",
+ "Microsoft.AspNetCore.SignalR.dll",
+ "Microsoft.AspNetCore.SignalR.Protocols.Json.dll",
+ "Microsoft.AspNetCore.SpaServices.dll",
+ "Microsoft.AspNetCore.SpaServices.Extensions.dll",
+ "Microsoft.AspNetCore.StaticFiles.dll",
+ "Microsoft.AspNetCore.WebSockets.dll",
+ "Microsoft.AspNetCore.WebUtilities.dll",
+ "Microsoft.CodeAnalysis.CSharp.dll",
+ "Microsoft.CodeAnalysis.dll",
+ "Microsoft.CodeAnalysis.Razor.dll",
+ "Microsoft.DotNet.PlatformAbstractions.dll",
+ "Microsoft.EntityFrameworkCore.Abstractions.dll",
+ "Microsoft.EntityFrameworkCore.Design.dll",
+ "Microsoft.EntityFrameworkCore.dll",
+ "Microsoft.EntityFrameworkCore.InMemory.dll",
+ "Microsoft.EntityFrameworkCore.Relational.dll",
+ "Microsoft.EntityFrameworkCore.SqlServer.dll",
+ "Microsoft.Extensions.Caching.Abstractions.dll",
+ "Microsoft.Extensions.Caching.Memory.dll",
+ "Microsoft.Extensions.Caching.SqlServer.dll",
+ "Microsoft.Extensions.Configuration.Abstractions.dll",
+ "Microsoft.Extensions.Configuration.Binder.dll",
+ "Microsoft.Extensions.Configuration.CommandLine.dll",
+ "Microsoft.Extensions.Configuration.dll",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables.dll",
+ "Microsoft.Extensions.Configuration.FileExtensions.dll",
+ "Microsoft.Extensions.Configuration.Ini.dll",
+ "Microsoft.Extensions.Configuration.Json.dll",
+ "Microsoft.Extensions.Configuration.KeyPerFile.dll",
+ "Microsoft.Extensions.Configuration.UserSecrets.dll",
+ "Microsoft.Extensions.Configuration.Xml.dll",
+ "Microsoft.Extensions.DependencyInjection.Abstractions.dll",
+ "Microsoft.Extensions.DependencyInjection.dll",
+ "Microsoft.Extensions.DependencyModel.dll",
+ "Microsoft.Extensions.DiagnosticAdapter.dll",
+ "Microsoft.Extensions.FileProviders.Abstractions.dll",
+ "Microsoft.Extensions.FileProviders.Composite.dll",
+ "Microsoft.Extensions.FileProviders.Embedded.dll",
+ "Microsoft.Extensions.FileProviders.Physical.dll",
+ "Microsoft.Extensions.FileSystemGlobbing.dll",
+ "Microsoft.Extensions.Hosting.Abstractions.dll",
+ "Microsoft.Extensions.Hosting.dll",
+ "Microsoft.Extensions.Http.dll",
+ "Microsoft.Extensions.Identity.Core.dll",
+ "Microsoft.Extensions.Identity.Stores.dll",
+ "Microsoft.Extensions.Localization.Abstractions.dll",
+ "Microsoft.Extensions.Localization.dll",
+ "Microsoft.Extensions.Logging.Abstractions.dll",
+ "Microsoft.Extensions.Logging.Configuration.dll",
+ "Microsoft.Extensions.Logging.Console.dll",
+ "Microsoft.Extensions.Logging.Debug.dll",
+ "Microsoft.Extensions.Logging.dll",
+ "Microsoft.Extensions.Logging.EventSource.dll",
+ "Microsoft.Extensions.Logging.TraceSource.dll",
+ "Microsoft.Extensions.ObjectPool.dll",
+ "Microsoft.Extensions.Options.ConfigurationExtensions.dll",
+ "Microsoft.Extensions.Options.dll",
+ "Microsoft.Extensions.Primitives.dll",
+ "Microsoft.Extensions.WebEncoders.dll",
+ "Microsoft.IdentityModel.Logging.dll",
+ "Microsoft.IdentityModel.Protocols.dll",
+ "Microsoft.IdentityModel.Protocols.OpenIdConnect.dll",
+ "Microsoft.IdentityModel.Protocols.WsFederation.dll",
+ "Microsoft.IdentityModel.Tokens.dll",
+ "Microsoft.IdentityModel.Tokens.Saml.dll",
+ "Microsoft.IdentityModel.Xml.dll",
+ "Microsoft.Net.Http.Headers.dll",
+ "Newtonsoft.Json.Bson.dll",
+ "Newtonsoft.Json.dll",
+ "Remotion.Linq.dll",
+ "System.Data.SqlClient.dll",
+ "System.IdentityModel.Tokens.Jwt.dll",
+ "System.Interactive.Async.dll",
+ "System.IO.Pipelines.dll",
+ "System.Net.Http.Formatting.dll",
+ "System.Net.WebSockets.WebSocketProtocol.dll",
+ "System.Runtime.CompilerServices.Unsafe.dll",
+ "System.Security.Cryptography.Pkcs.dll",
+ "System.Security.Cryptography.Xml.dll",
+ "System.Security.Permissions.dll",
+ "System.Text.Encoding.CodePages.dll",
+ "System.Text.Encodings.Web.dll",
+ "System.Threading.Channels.dll",
+ })
+ },
+ { ("netcoreapp2.1", RuntimeIdentifier.OSX_x64), () =>
+ _additionalFilesAfterPublish[("netcoreapp2.1", RuntimeIdentifier.Linux_x64)]()
+ },
+ { ("netcoreapp2.1", RuntimeIdentifier.Win_x64), () =>
+ _additionalFilesAfterPublish[("netcoreapp2.1", RuntimeIdentifier.Linux_x64)]()
+ .Concat(new[]
+ {
+ "sni.dll",
+ })
+ },
+ { ("netcoreapp2.2", RuntimeIdentifier.None), () =>
+ _additionalFilesAfterPublish[("netcoreapp2.1", RuntimeIdentifier.None)]()
+ },
+ { ("netcoreapp2.2", RuntimeIdentifier.Linux_x64), () =>
+ _additionalFilesAfterPublish[("netcoreapp2.1", RuntimeIdentifier.Linux_x64)]()
+ .Concat(new[]
+ {
+ "Microsoft.AspNetCore.Diagnostics.HealthChecks.dll",
+ "Microsoft.AspNetCore.Server.IIS.dll",
+ "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll",
+ "Microsoft.Extensions.Diagnostics.HealthChecks.dll",
+ })
+ },
+ { ("netcoreapp2.2", RuntimeIdentifier.OSX_x64), () =>
+ _additionalFilesAfterPublish[("netcoreapp2.2", RuntimeIdentifier.Linux_x64)]()
+ },
+ { ("netcoreapp2.2", RuntimeIdentifier.Win_x64), () =>
+ _additionalFilesAfterPublish[("netcoreapp2.2", RuntimeIdentifier.Linux_x64)]()
+ .Concat(new[]
+ {
+ "aspnetcorev2_inprocess.dll",
+ "sni.dll",
+ })
+ },
+ };
+
+ public override IEnumerable ExpectedFilesAfterPublish =>
+ base.ExpectedFilesAfterPublish
+ .Concat(_additionalFilesAfterPublish[(DotNetUtil.TargetFrameworkMoniker, RuntimeIdentifier)]());
+ }
+}
diff --git a/test/Cli.FunctionalTests/Util/ConcurrentStringBuilder.cs b/test/Cli.FunctionalTests/Util/ConcurrentStringBuilder.cs
new file mode 100644
index 0000000000..4d829977b5
--- /dev/null
+++ b/test/Cli.FunctionalTests/Util/ConcurrentStringBuilder.cs
@@ -0,0 +1,37 @@
+// 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.Text;
+
+namespace Cli.FunctionalTests.Util
+{
+ public class ConcurrentStringBuilder
+ {
+ private StringBuilder _stringBuilder = new StringBuilder();
+ private object _lock = new object();
+
+ public void AppendLine()
+ {
+ lock (_lock)
+ {
+ _stringBuilder.AppendLine();
+ }
+ }
+
+ public void AppendLine(string data)
+ {
+ lock (_lock)
+ {
+ _stringBuilder.AppendLine(data);
+ }
+ }
+
+ public override string ToString()
+ {
+ lock (_lock)
+ {
+ return _stringBuilder.ToString();
+ }
+ }
+ }
+}
diff --git a/test/Cli.FunctionalTests/Util/DotNetUtil.cs b/test/Cli.FunctionalTests/Util/DotNetUtil.cs
new file mode 100644
index 0000000000..5c6d2f51c8
--- /dev/null
+++ b/test/Cli.FunctionalTests/Util/DotNetUtil.cs
@@ -0,0 +1,243 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading;
+using Microsoft.Extensions.Internal;
+using Newtonsoft.Json;
+using NuGet.Versioning;
+
+namespace Cli.FunctionalTests.Util
+{
+ internal static class DotNetUtil
+ {
+ private const string _clearPackageSourcesNuGetConfig =
+@"
+
+
+
+
+
+";
+
+ // Bind to dynamic port 0 to avoid port conflicts during parallel tests
+ private const string _urls = "--urls http://127.0.0.1:0;https://127.0.0.1:0";
+
+ // Must publish to folder under "bin" or "obj" to prevent double-copying publish output during incremental publish
+ public static string PublishOutput => Path.Combine("bin", "pub");
+
+ private static readonly Lazy<(SemanticVersion SdkVersion, SemanticVersion RuntimeVersion)> _versions =
+ new Lazy<(SemanticVersion SdkVersion, SemanticVersion RuntimeVersion)>(GetVersions, LazyThreadSafetyMode.PublicationOnly);
+
+ public static SemanticVersion SdkVersion => _versions.Value.SdkVersion;
+
+ public static SemanticVersion RuntimeVersion => _versions.Value.RuntimeVersion;
+
+ private static readonly Lazy _requiresPrivateFeed = new Lazy(GetRequiresPrivateFeed, LazyThreadSafetyMode.PublicationOnly);
+
+ public static bool RequiresPrivateFeed => _requiresPrivateFeed.Value;
+
+ public static string TargetFrameworkMoniker => $"netcoreapp{RuntimeVersion.Major}.{RuntimeVersion.Minor}";
+
+ private static readonly HttpClient _httpClient = new HttpClient();
+
+ private static readonly IEnumerable> _globalEnvironment = new KeyValuePair[] {
+ // Ignore globally-installed .NET Core components
+ new KeyValuePair("DOTNET_MULTILEVEL_LOOKUP", "false"),
+ };
+
+ private static (SemanticVersion SdkVersion, SemanticVersion RuntimeVersion) GetVersions()
+ {
+ var info = RunDotNet("--info", workingDirectory: null);
+
+ var sdkVersionString = Regex.Match(info, @"Version:\s*(\S+)").Groups[1].Value;
+ var sdkVersion = SemanticVersion.Parse(sdkVersionString);
+
+ // Select highest version of Microsoft.NETCore.App which matches major and minor version of SDK
+ var runtimeVersionPattern = $@"Microsoft.NETCore.App\s*({sdkVersion.Major}.{sdkVersion.Minor}\S+)";
+ var runtimeVersionString = Regex.Match(info, runtimeVersionPattern, RegexOptions.RightToLeft).Groups[1].Value;
+ var runtimeVersion = SemanticVersion.Parse(runtimeVersionString);
+
+ // Supported version range is [2.1.300,2.2.100] (inclusive)
+ if (sdkVersion >= new SemanticVersion(2, 1, 300) && sdkVersion <= new SemanticVersion(2, 2, 100))
+ {
+ return (sdkVersion, runtimeVersion);
+ }
+ else
+ {
+ throw new InvalidOperationException($"Unsupported SDK version: {sdkVersion}");
+ }
+ }
+
+ // Private feed is required if nuget.org doesn't contain the matching version of Microsoft.NETCore.App
+ private static bool GetRequiresPrivateFeed()
+ {
+ var versionString = _httpClient.GetStringAsync("https://api.nuget.org/v3-flatcontainer/microsoft.netcore.app/index.json").Result;
+ var definition = new { Versions = Enumerable.Empty() };
+
+ var versions = JsonConvert.DeserializeAnonymousType(versionString, definition);
+ return !versions.Versions.Contains(RuntimeVersion.ToString());
+ }
+
+ private static IEnumerable> GetEnvironment(NuGetPackageSource nuGetPackageSource)
+ {
+ // Set NUGET_PACKAGES to an initially-empty, distinct folder for each NuGetPackageSource. This ensures packages are loaded
+ // from either NuGetFallbackFolder or configured sources, and *not* loaded from the default per-user global-packages folder.
+ //
+ // [5/7/2018] NUGET_PACKAGES cannot be set to a folder under the application due to https://github.com/dotnet/cli/issues/9216.
+ yield return new KeyValuePair("NUGET_PACKAGES", Path.Combine(AssemblySetUp.TempDir, nuGetPackageSource.Name));
+ }
+
+ public static string New(string template, string workingDirectory)
+ {
+ // Clear all packages sources by default. May be overridden by NuGetPackageSource parameter.
+ File.WriteAllText(Path.Combine(workingDirectory, "NuGet.config"), _clearPackageSourcesNuGetConfig);
+
+ // Pass "--debug:ephemeral-hive" to build template contents in-memory, rather than using the default
+ // "%UserProfile%\.templateengine" cache, which may be out-of-date when testing newer builds with the same version.
+ return RunDotNet($"new {template} --name {template} --output . --no-restore --debug:ephemeral-hive", workingDirectory);
+ }
+
+ public static string Restore(string workingDirectory, NuGetPackageSource packageSource, RuntimeIdentifier runtimeIdentifier)
+ {
+ return RunDotNet($"restore /warnaserror --no-cache {packageSource.SourceArgument} {runtimeIdentifier.RuntimeArgument}",
+ workingDirectory, GetEnvironment(packageSource));
+ }
+
+ public static string Build(string workingDirectory, NuGetPackageSource packageSource, RuntimeIdentifier runtimeIdentifier)
+ {
+ // "dotnet build" cannot use "--no-restore" if the app is self-contained and the SDK contains a patched runtime
+ // https://github.com/dotnet/sdk/issues/2312, https://github.com/dotnet/cli/issues/9514
+ bool restoreRequired = (runtimeIdentifier != RuntimeIdentifier.None) && (DotNetUtil.RuntimeVersion.Patch > 0);
+
+ var restoreArgument = restoreRequired ? $"--no-cache {packageSource.SourceArgument}" : "--no-restore";
+
+ return RunDotNet($"build /warnaserror {restoreArgument} {runtimeIdentifier.RuntimeArgument}", workingDirectory, GetEnvironment(packageSource));
+ }
+
+ public static (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) Run(
+ string workingDirectory, RuntimeIdentifier runtimeIdentifier)
+ {
+ return StartDotNet($"run --no-build {_urls} {runtimeIdentifier.RuntimeArgument}", workingDirectory);
+ }
+
+ public static string Publish(string workingDirectory, RuntimeIdentifier runtimeIdentifier)
+ {
+ return RunDotNet($"publish --no-build -o {PublishOutput} {runtimeIdentifier.RuntimeArgument}", workingDirectory);
+ }
+
+ internal static (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) Exec(
+ string workingDirectory, string name, RuntimeIdentifier runtimeIdentifier)
+ {
+ if (runtimeIdentifier == RuntimeIdentifier.None)
+ {
+ var path = Path.Combine(PublishOutput, $"{name}.dll");
+ return StartDotNet($"exec {path} {_urls}", workingDirectory);
+ }
+ else
+ {
+ var file = (runtimeIdentifier == RuntimeIdentifier.Win_x64) ? $"{name}.exe" : name;
+ var path = Path.Combine(workingDirectory, PublishOutput, file);
+ return StartProcess(path, _urls, workingDirectory);
+ }
+ }
+
+ private static string RunDotNet(string arguments, string workingDirectory,
+ IEnumerable> environment = null, bool throwOnError = true)
+ {
+ var p = StartDotNet(arguments, workingDirectory, environment);
+ return WaitForExit(p, throwOnError: throwOnError);
+ }
+
+ private static (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) StartDotNet(
+ string arguments, string workingDirectory, IEnumerable> environment = null)
+ {
+ var env = _globalEnvironment.Concat(environment ?? Enumerable.Empty>());
+ return StartProcess("dotnet", arguments, workingDirectory, env);
+ }
+
+ private static (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) StartProcess(
+ string filename, string arguments, string workingDirectory, IEnumerable> environment = null)
+ {
+ var process = new Process()
+ {
+ StartInfo =
+ {
+ FileName = filename,
+ Arguments = arguments,
+ RedirectStandardInput = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ WorkingDirectory = workingDirectory,
+ },
+ };
+
+ if (environment != null)
+ {
+ foreach (var kvp in environment)
+ {
+ process.StartInfo.Environment.Add(kvp);
+ }
+ }
+
+ var outputBuilder = new ConcurrentStringBuilder();
+ process.OutputDataReceived += (_, e) =>
+ {
+ outputBuilder.AppendLine(e.Data);
+ };
+
+ var errorBuilder = new ConcurrentStringBuilder();
+ process.ErrorDataReceived += (_, e) =>
+ {
+ errorBuilder.AppendLine(e.Data);
+ };
+
+ process.Start();
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ return (process, outputBuilder, errorBuilder);
+ }
+
+ public static string StopProcess((Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) process,
+ bool throwOnError = true)
+ {
+ if (!process.Process.HasExited)
+ {
+ process.Process.KillTree();
+ }
+
+ return WaitForExit(process, throwOnError: throwOnError);
+ }
+
+ public static string WaitForExit((Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) process,
+ bool throwOnError = true)
+ {
+ // Workaround issue where WaitForExit() blocks until child processes are killed, which is problematic
+ // for the dotnet.exe NodeReuse child processes. I'm not sure why this is problematic for dotnet.exe child processes
+ // but not for MSBuild.exe child processes. The workaround is to specify a large timeout.
+ // https://stackoverflow.com/a/37983587/102052
+ process.Process.WaitForExit(int.MaxValue);
+
+ if (throwOnError && process.Process.ExitCode != 0)
+ {
+ var sb = new ConcurrentStringBuilder();
+
+ sb.AppendLine($"Command {process.Process.StartInfo.FileName} {process.Process.StartInfo.Arguments} returned exit code {process.Process.ExitCode}");
+ sb.AppendLine();
+ sb.AppendLine(process.OutputBuilder.ToString());
+
+ throw new InvalidOperationException(sb.ToString());
+ }
+
+ return process.OutputBuilder.ToString();
+ }
+ }
+}
diff --git a/test/Cli.FunctionalTests/Util/IOUtil.cs b/test/Cli.FunctionalTests/Util/IOUtil.cs
new file mode 100644
index 0000000000..71409eb633
--- /dev/null
+++ b/test/Cli.FunctionalTests/Util/IOUtil.cs
@@ -0,0 +1,85 @@
+// 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.IO;
+using System.Linq;
+using System.Threading;
+
+namespace Cli.FunctionalTests.Util
+{
+ internal static class IOUtil
+ {
+ public static void ReplaceInFile(string path, string oldValue, string newValue)
+ {
+ File.WriteAllText(path, File.ReadAllText(path).Replace(oldValue, newValue));
+ }
+
+ public static IEnumerable GetFiles(string path)
+ {
+ return Directory.GetFiles(path, "*", SearchOption.AllDirectories)
+ .Select(p => Path.GetRelativePath(path, p));
+ }
+
+ public static IEnumerable GetDirectories(string path)
+ {
+ return Directory.GetDirectories(path, "*", SearchOption.AllDirectories)
+ .Select(p => Path.GetRelativePath(path, p));
+ }
+
+ public static string GetTempDir()
+ {
+ var temp = Path.GetTempFileName();
+ File.Delete(temp);
+ Directory.CreateDirectory(temp);
+ return temp;
+ }
+
+ public static void DeleteFileIfExists(string path)
+ {
+ if (File.Exists(path))
+ {
+ File.Delete(path);
+ }
+ }
+
+ public static void DeleteDir(string path)
+ {
+ // If delete fails (e.g. due to a file in use), retry once every second up to 20 times.
+ for (var i = 0; i < 20; i++)
+ {
+ try
+ {
+ var dir = new DirectoryInfo(path) { Attributes = FileAttributes.Normal };
+ foreach (var info in dir.GetFileSystemInfos("*", SearchOption.AllDirectories))
+ {
+ info.Attributes = FileAttributes.Normal;
+ }
+ dir.Delete(recursive: true);
+ break;
+ }
+ catch (DirectoryNotFoundException)
+ {
+ break;
+ }
+ catch (FileNotFoundException)
+ {
+ break;
+ }
+ catch (Exception)
+ {
+ if (i < 19)
+ {
+ Thread.Sleep(TimeSpan.FromSeconds(1));
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+ }
+
+ }
+}
diff --git a/test/Cli.FunctionalTests/Util/ProcessHelper.cs b/test/Cli.FunctionalTests/Util/ProcessHelper.cs
new file mode 100644
index 0000000000..324f6d3dea
--- /dev/null
+++ b/test/Cli.FunctionalTests/Util/ProcessHelper.cs
@@ -0,0 +1,113 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Extensions.Internal
+{
+ internal static class ProcessExtensions
+ {
+ private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
+
+ public static void KillTree(this Process process)
+ {
+ process.KillTree(_defaultTimeout);
+ }
+
+ public static void KillTree(this Process process, TimeSpan timeout)
+ {
+ string stdout;
+ if (_isWindows)
+ {
+ RunProcessAndWaitForExit(
+ "taskkill",
+ $"/T /F /PID {process.Id}",
+ timeout,
+ out stdout);
+ }
+ else
+ {
+ var children = new HashSet();
+ GetAllChildIdsUnix(process.Id, children, timeout);
+ foreach (var childId in children)
+ {
+ KillProcessUnix(childId, timeout);
+ }
+ KillProcessUnix(process.Id, timeout);
+ }
+ }
+
+ private static void GetAllChildIdsUnix(int parentId, ISet children, TimeSpan timeout)
+ {
+ string stdout;
+ var exitCode = RunProcessAndWaitForExit(
+ "pgrep",
+ $"-P {parentId}",
+ timeout,
+ out stdout);
+
+ if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
+ {
+ using (var reader = new StringReader(stdout))
+ {
+ while (true)
+ {
+ var text = reader.ReadLine();
+ if (text == null)
+ {
+ return;
+ }
+
+ int id;
+ if (int.TryParse(text, out id))
+ {
+ children.Add(id);
+ // Recursively get the children
+ GetAllChildIdsUnix(id, children, timeout);
+ }
+ }
+ }
+ }
+ }
+
+ private static void KillProcessUnix(int processId, TimeSpan timeout)
+ {
+ string stdout;
+ RunProcessAndWaitForExit(
+ "kill",
+ $"-TERM {processId}",
+ timeout,
+ out stdout);
+ }
+
+ private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout)
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = fileName,
+ Arguments = arguments,
+ RedirectStandardOutput = true,
+ UseShellExecute = false
+ };
+
+ var process = Process.Start(startInfo);
+
+ stdout = null;
+ if (process.WaitForExit((int)timeout.TotalMilliseconds))
+ {
+ stdout = process.StandardOutput.ReadToEnd();
+ }
+ else
+ {
+ process.Kill();
+ }
+
+ return process.ExitCode;
+ }
+ }
+}
diff --git a/test/Cli.FunctionalTests/run-tests.ps1 b/test/Cli.FunctionalTests/run-tests.ps1
new file mode 100644
index 0000000000..6cdff63b0f
--- /dev/null
+++ b/test/Cli.FunctionalTests/run-tests.ps1
@@ -0,0 +1,104 @@
+<#
+.SYNOPSIS
+This script runs the tests in this project on complete build of the .NET Core CLI
+
+.PARAMETER ci
+This is a CI build
+
+.PARAMETER AccessTokenSuffix
+The access token for Azure blobs
+
+.PARAMETER AssetRootUrl
+The blob feed for the .NET Core CLI. If not specified, it will determined automatically if possible.
+
+.PARAMETER RestoreSources
+A list of additional NuGet feeds. If not specified, it will determined automatically if possible.
+
+.PARAMETER ProdConManifestUrl
+The prodcon build.xml file
+
+.PARAMETER ProcConChannel
+The prodcon channel to use if a build.xml file isn't set.
+#>
+
+param(
+ [switch]$ci,
+ $AssetRootUrl = $env:PB_AccessRootUrl,
+ $AccessTokenSuffix = $env:PB_AccessTokenSuffix,
+ $RestoreSources = $env:PB_RestoreSources,
+ $ProdConManifestUrl,
+ $ProcConChannel = 'master'
+)
+
+$ErrorActionPreference = 'Stop'
+Set-StrictMode -Version 1
+
+$repoRoot = Resolve-Path "$PSScriptRoot/../../"
+Import-Module "$repoRoot/scripts/common.psm1" -Scope Local -Force
+
+Push-Location $PSScriptRoot
+try {
+ New-Item -Type Directory "$PSScriptRoot/obj/" -ErrorAction Ignore | Out-Null
+ $sdkVersion = ''
+
+ if (-not $ci -or $ProdConManifestUrl) {
+
+ if (-not $ProdConManifestUrl) {
+ Write-Host -ForegroundColor Magenta "Running tests for the latest ProdCon build"
+ $ProdConManifestUrl = "https://raw.githubusercontent.com/dotnet/versions/master/build-info/dotnet/product/cli/$ProcConChannel/build.xml"
+ }
+
+ [xml] $prodConManifest = Invoke-RestMethod $ProdConManifestUrl
+
+ $RestoreSources = $prodConManifest.OrchestratedBuild.Endpoint `
+ | ? { $_.Type -eq 'BlobFeed' } `
+ | select -first 1 -ExpandProperty Url
+
+ $AssetRootUrl = $RestoreSources -replace '/index.json', '/assets'
+
+ $sdkVersion = $prodConManifest.OrchestratedBuild.Build `
+ | ? { $_.Name -eq 'cli' } `
+ | select -first 1 -ExpandProperty ProductVersion
+ }
+ else {
+ if (-not $AssetRootUrl) {
+ Write-Error "Missing required parameter: AssetRootUrl"
+ }
+ $AssetRootUrl = $AssetRootUrl.TrimEnd('/')
+ [xml] $cli = Invoke-RestMethod "$AssetRootUrl/orchestration-metadata/manifests/cli.xml${AccessTokenSuffix}"
+ $sdkVersion = $cli.Build.ProductVersion
+ }
+
+ Write-Host "sdkVersion: $sdkVersion"
+ Write-Host "AssetRootUrl: $AssetRootUrl"
+ Write-Host "RestoreSources: $RestoreSources"
+
+ @{ sdk = @{ version = $sdkVersion } } | ConvertTo-Json | Set-Content "$PSScriptRoot/global.json"
+
+ $dotnetRoot = "$repoRoot/.dotnet"
+ $dotnet = "$dotnetRoot/dotnet.exe"
+
+ if (-not (Test-Path "$dotnetRoot/sdk/$sdkVersion/dotnet.dll")) {
+ Remove-Item -Recurse -Force $dotnetRoot -ErrorAction Ignore | Out-Null
+ $cliUrl = "$AssetRootUrl/Sdk/$sdkVersion/dotnet-sdk-$sdkVersion-win-x64.zip"
+ Write-Host "Downloading $cliUrl"
+ Invoke-WebRequest -UseBasicParsing "${cliUrl}${AccessTokenSuffix}" -OutFile "$PSScriptRoot/obj/dotnet.zip"
+ Expand-Archive "$PSScriptRoot/obj/dotnet.zip" -DestinationPath $dotnetRoot
+ }
+
+ # Set a clean test environment
+ $env:DOTNET_ROOT = $dotnetRoot
+ $env:DOTNET_MULTILEVEL_LOOKUP = 0
+ $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 0
+ $env:MSBuildSdksPath = ''
+ $env:PATH = "$dotnetRoot;$env:PATH"
+
+ Invoke-Block { & $dotnet test `
+ --logger "console;verbosity=detailed" `
+ --logger "trx;LogFileName=$repoRoot/artifacts/logs/e2etests.trx" `
+ "-p:DotNetRestoreSources=$RestoreSources" `
+ "-bl:$repoRoot/artifacts/logs/e2etests.binlog" }
+}
+finally {
+ Pop-Location
+}
diff --git a/test/Cli.FunctionalTests/test.sh b/test/Cli.FunctionalTests/test.sh
new file mode 100644
index 0000000000..5afdebc7b9
--- /dev/null
+++ b/test/Cli.FunctionalTests/test.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+dotnet test --logger "console;verbosity=detailed" "$@"