diff --git a/AspNetCoreSdkTests.sln b/AspNetCoreSdkTests.sln
new file mode 100644
index 0000000000..09f6e24a82
--- /dev/null
+++ b/AspNetCoreSdkTests.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27428.2037
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreSdkTests", "AspNetCoreSdkTests\AspNetCoreSdkTests.csproj", "{982F3170-BE98-4B2F-B36F-4C036BCDB2E6}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {982F3170-BE98-4B2F-B36F-4C036BCDB2E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {982F3170-BE98-4B2F-B36F-4C036BCDB2E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {982F3170-BE98-4B2F-B36F-4C036BCDB2E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {982F3170-BE98-4B2F-B36F-4C036BCDB2E6}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3B64DDE7-BAD8-4DB3-A294-669059DA3334}
+ EndGlobalSection
+EndGlobal
diff --git a/AspNetCoreSdkTests/AspNetCoreSdkTests.csproj b/AspNetCoreSdkTests/AspNetCoreSdkTests.csproj
new file mode 100644
index 0000000000..3028413b33
--- /dev/null
+++ b/AspNetCoreSdkTests/AspNetCoreSdkTests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ netcoreapp2.0
+ false
+
+
+
+ latest
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/AspNetCoreSdkTests/NuGetConfig.cs b/AspNetCoreSdkTests/NuGetConfig.cs
new file mode 100644
index 0000000000..8443084754
--- /dev/null
+++ b/AspNetCoreSdkTests/NuGetConfig.cs
@@ -0,0 +1,9 @@
+namespace AspNetCoreSdkTests
+{
+ public enum NuGetConfig
+ {
+ Empty,
+ DotNetCore,
+ NuGetOrg
+ }
+}
diff --git a/AspNetCoreSdkTests/NuGetConfig/NuGet.DotNetCore.config b/AspNetCoreSdkTests/NuGetConfig/NuGet.DotNetCore.config
new file mode 100644
index 0000000000..fc7c007517
--- /dev/null
+++ b/AspNetCoreSdkTests/NuGetConfig/NuGet.DotNetCore.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/AspNetCoreSdkTests/NuGetConfig/NuGet.Empty.config b/AspNetCoreSdkTests/NuGetConfig/NuGet.Empty.config
new file mode 100644
index 0000000000..4bb3170917
--- /dev/null
+++ b/AspNetCoreSdkTests/NuGetConfig/NuGet.Empty.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/AspNetCoreSdkTests/NuGetConfig/NuGet.NuGetOrg.config b/AspNetCoreSdkTests/NuGetConfig/NuGet.NuGetOrg.config
new file mode 100644
index 0000000000..b0743e46f8
--- /dev/null
+++ b/AspNetCoreSdkTests/NuGetConfig/NuGet.NuGetOrg.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/AspNetCoreSdkTests/Template.cs b/AspNetCoreSdkTests/Template.cs
new file mode 100644
index 0000000000..2d751fb93c
--- /dev/null
+++ b/AspNetCoreSdkTests/Template.cs
@@ -0,0 +1,14 @@
+namespace AspNetCoreSdkTests
+{
+ public enum Template
+ {
+ Web,
+ Mvc,
+ Razor,
+ Angular,
+ React,
+ ReactRedux,
+ RazorClassLib,
+ WebApi
+ }
+}
diff --git a/AspNetCoreSdkTests/TemplateTests.cs b/AspNetCoreSdkTests/TemplateTests.cs
new file mode 100644
index 0000000000..c56d0fc453
--- /dev/null
+++ b/AspNetCoreSdkTests/TemplateTests.cs
@@ -0,0 +1,20 @@
+using AspNetCoreSdkTests.Util;
+using NUnit.Framework;
+
+namespace AspNetCoreSdkTests
+{
+ [TestFixture]
+ public class TemplateTests
+ {
+ [Test]
+ [TestCaseSource(typeof(TestData), nameof(TestData.AllTemplates))]
+ public void Restore(Template template, NuGetConfig nuGetConfig)
+ {
+ using (var context = new DotNetContext())
+ {
+ context.New(template, restore: false);
+ context.Restore(nuGetConfig);
+ }
+ }
+ }
+}
diff --git a/AspNetCoreSdkTests/TestData.cs b/AspNetCoreSdkTests/TestData.cs
new file mode 100644
index 0000000000..66fe9ae817
--- /dev/null
+++ b/AspNetCoreSdkTests/TestData.cs
@@ -0,0 +1,24 @@
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AspNetCoreSdkTests
+{
+ public static class TestData
+ {
+ public static IEnumerable AllTemplates { get; } =
+ from t in Enum.GetValues(typeof(Template)).Cast()
+ from c in Enum.GetValues(typeof(NuGetConfig)).Cast()
+ let data = new TestCaseData(t, c)
+ select (
+ c == NuGetConfig.NuGetOrg ?
+ data.Ignore("RC1 not yet published to nuget.org") :
+ data);
+
+ public static IEnumerable ApplicationTemplates { get; } =
+ from d in AllTemplates
+ where ((Template)d.Arguments[0] != Template.RazorClassLib)
+ select d;
+ }
+}
diff --git a/AspNetCoreSdkTests/Util/AssemblyInfo.cs b/AspNetCoreSdkTests/Util/AssemblyInfo.cs
new file mode 100644
index 0000000000..e48b387886
--- /dev/null
+++ b/AspNetCoreSdkTests/Util/AssemblyInfo.cs
@@ -0,0 +1,4 @@
+using NUnit.Framework;
+
+// Run all test cases in parallel
+[assembly: Parallelizable(ParallelScope.Children)]
diff --git a/AspNetCoreSdkTests/Util/DotNetContext.cs b/AspNetCoreSdkTests/Util/DotNetContext.cs
new file mode 100644
index 0000000000..12209a2d1d
--- /dev/null
+++ b/AspNetCoreSdkTests/Util/DotNetContext.cs
@@ -0,0 +1,15 @@
+namespace AspNetCoreSdkTests.Util
+{
+ public class DotNetContext : TempDir
+ {
+ public string New(Template template, bool restore)
+ {
+ return DotNet.New(template.ToString(), Path, restore);
+ }
+
+ public string Restore(NuGetConfig config)
+ {
+ return DotNet.Restore(Path, config);
+ }
+ }
+}
diff --git a/AspNetCoreSdkTests/Util/DotNetUtil.cs b/AspNetCoreSdkTests/Util/DotNetUtil.cs
new file mode 100644
index 0000000000..ac1f7bd946
--- /dev/null
+++ b/AspNetCoreSdkTests/Util/DotNetUtil.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+
+namespace AspNetCoreSdkTests.Util
+{
+ internal static class DotNet
+ {
+ private static IEnumerable> GetEnvironment(string workingDirectory)
+ {
+ // Set NUGET_PACKAGES to an empty folder to ensure all packages are loaded from either NuGetFallbackFolder or configured sources,
+ // and *not* loaded from the default per-user global-packages folder.
+ yield return new KeyValuePair("NUGET_PACKAGES", Path.Combine(workingDirectory, ".nuget", "packages"));
+ }
+
+ public static string New(string template, string workingDirectory, bool restore)
+ {
+ var arguments = $"new {template} --name {template} --output ." + (restore ? "" : " --no-restore");
+ return RunDotNet(arguments, workingDirectory, GetEnvironment(workingDirectory));
+ }
+
+ public static string Restore(string workingDirectory, NuGetConfig config)
+ {
+ var configPath = Path.GetFullPath(Path.Combine("NuGetConfig", $"NuGet.{config}.config"));
+ return RunDotNet($"restore --no-cache --configfile {configPath}", workingDirectory, GetEnvironment(workingDirectory));
+ }
+
+ private static string RunDotNet(string arguments, string workingDirectory,
+ IEnumerable> environment = null, bool throwOnError = true)
+ {
+ var p = StartDotNet(arguments, workingDirectory, environment);
+ return WaitForExit(p.Process, p.OutputBuilder, p.ErrorBuilder, throwOnError: throwOnError);
+ }
+
+ private static (Process Process, StringBuilder OutputBuilder, StringBuilder ErrorBuilder) StartDotNet(
+ string arguments, string workingDirectory, IEnumerable> environment = null)
+ {
+ return StartProcess("dotnet", arguments, workingDirectory, environment);
+ }
+
+ private static (Process Process, StringBuilder OutputBuilder, StringBuilder 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 StringBuilder();
+ process.OutputDataReceived += (_, e) =>
+ {
+ outputBuilder.AppendLine(e.Data);
+ };
+
+ var errorBuilder = new StringBuilder();
+ process.ErrorDataReceived += (_, e) =>
+ {
+ errorBuilder.AppendLine(e.Data);
+ };
+
+ process.Start();
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ return (process, outputBuilder, errorBuilder);
+ }
+
+ public static string WaitForExit(Process process, StringBuilder outputBuilder, StringBuilder errorBuilder,
+ 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.WaitForExit(int.MaxValue);
+
+ if (throwOnError && process.ExitCode != 0)
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine($"Command {process.StartInfo.FileName} {process.StartInfo.Arguments} returned exit code {process.ExitCode}");
+ sb.AppendLine();
+ sb.AppendLine(outputBuilder.ToString());
+
+ throw new InvalidOperationException(sb.ToString());
+ }
+
+ return outputBuilder.ToString();
+ }
+
+
+ }
+}
diff --git a/AspNetCoreSdkTests/Util/IOUtil.cs b/AspNetCoreSdkTests/Util/IOUtil.cs
new file mode 100644
index 0000000000..493538ec07
--- /dev/null
+++ b/AspNetCoreSdkTests/Util/IOUtil.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+
+namespace AspNetCoreSdkTests.Util
+{
+ internal static class IOUtil
+ {
+ 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 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/AspNetCoreSdkTests/Util/TempDir.cs b/AspNetCoreSdkTests/Util/TempDir.cs
new file mode 100644
index 0000000000..273589e0dd
--- /dev/null
+++ b/AspNetCoreSdkTests/Util/TempDir.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace AspNetCoreSdkTests.Util
+{
+ public class TempDir : IDisposable
+ {
+ public string Path { get; }
+
+ public TempDir()
+ {
+ Path = IOUtil.GetTempDir();
+ }
+
+ public void Dispose()
+ {
+ IOUtil.DeleteDir(Path);
+ }
+ }
+}