diff --git a/test/E2ETests/RemoteDeploymentConfig.cs b/test/E2ETests/RemoteDeploymentConfig.cs
index ba33a5ae0d..49465131fd 100644
--- a/test/E2ETests/RemoteDeploymentConfig.cs
+++ b/test/E2ETests/RemoteDeploymentConfig.cs
@@ -2,14 +2,42 @@
{
public class RemoteDeploymentConfig
{
+ ///
+ /// Name or IP address of the server to deploy to
+ ///
public string ServerName { get; set; }
+ ///
+ /// Account name for the credentials required to setup a powershell session to the target server
+ ///
public string AccountName { get; set; }
+ ///
+ /// Account password for the credentials required to setup a powershell session to the target server
+ ///
public string AccountPassword { get; set; }
+ ///
+ /// The file share on the target server to copy the files to
+ ///
public string FileSharePath { get; set; }
- public string ExecutableRelativePath { get; set; }
+ ///
+ /// Location of the dotnet runtime zip file which is required for testing portable apps.
+ /// When both and properties
+ /// are provided, the property is preferred.
+ ///
+ public string DotnetRuntimeZipFilePath { get; set; }
+
+ ///
+ /// Location of the dotnet runtime folder which is required for testing portable apps.
+ /// This property is probably more useful for users as they can point their local 'dotnet' runtime folder.
+ ///
+ public string DotnetRuntimeFolderPath { get; set; }
+
+ ///
+ /// Path to the parent folder containing 'dotnet.exe' on remote server's file share
+ ///
+ public string DotnetRuntimePathOnShare { get; set; }
}
}
diff --git a/test/E2ETests/SmokeTestsOnNanoServer.cs b/test/E2ETests/SmokeTestsOnNanoServer.cs
index 20221f2c10..d6b8d9b0e3 100644
--- a/test/E2ETests/SmokeTestsOnNanoServer.cs
+++ b/test/E2ETests/SmokeTestsOnNanoServer.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.IO.Compression;
using System.Threading.Tasks;
using E2ETests.Common;
using Microsoft.AspNetCore.Server.Testing;
@@ -12,24 +13,55 @@ using Xunit.Abstractions;
namespace E2ETests
{
- public class SmokeTestsOnNanoServer : IDisposable
+ public class SmokeTestsOnNanoServerUsingStandaloneRuntime : IDisposable
{
+ private readonly SmokeTestsOnNanoServer _smokeTestsOnNanoServer;
private readonly XunitLogger _logger;
private readonly RemoteDeploymentConfig _remoteDeploymentConfig;
- public SmokeTestsOnNanoServer(ITestOutputHelper output)
+ public SmokeTestsOnNanoServerUsingStandaloneRuntime(ITestOutputHelper output)
{
_logger = new XunitLogger(output, LogLevel.Information);
+ _remoteDeploymentConfig = RemoteDeploymentConfigHelper.GetConfiguration();
+ _smokeTestsOnNanoServer = new SmokeTestsOnNanoServer(output, _remoteDeploymentConfig, _logger);
+ }
- var configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("remoteDeploymentConfig.json")
- .AddUserSecrets()
- .AddEnvironmentVariables()
- .Build();
+ [ConditionalTheory, Trait("E2Etests", "NanoServer")]
+ [OSSkipCondition(OperatingSystems.Linux)]
+ [OSSkipCondition(OperatingSystems.MacOSX)]
+ [SkipIfEnvironmentVariableNotEnabled("RUN_TESTS_ON_NANO")]
+ [InlineData(ServerType.Kestrel, 5000, ApplicationType.Standalone)]
+ [InlineData(ServerType.WebListener, 5000, ApplicationType.Standalone)]
+ public async Task Test(ServerType serverType, int portToListen, ApplicationType applicationType)
+ {
+ var applicationBaseUrl = $"http://{_remoteDeploymentConfig.ServerName}:{portToListen}/";
+ await _smokeTestsOnNanoServer.RunTestsAsync(serverType, applicationBaseUrl, applicationType);
+ }
- _remoteDeploymentConfig = new RemoteDeploymentConfig();
- configuration.GetSection("NanoServer").Bind(_remoteDeploymentConfig);
+ public void Dispose()
+ {
+ _logger.Dispose();
+ }
+ }
+
+ // Tests here test portable app scenario, so we copy the dotnet runtime onto the
+ // target server's file share and after setting up a remote session to the server, we update the PATH environment
+ // to have the path to this copied dotnet runtime folder in the share.
+ // The dotnet runtime is copied only once for all the tests in this class.
+ public class SmokeTestsOnNanoServerUsingSharedRuntime
+ : IClassFixture, IDisposable
+ {
+ private readonly SmokeTestsOnNanoServer _smokeTestsOnNanoServer;
+ private readonly RemoteDeploymentConfig _remoteDeploymentConfig;
+ private readonly XunitLogger _logger;
+
+ public SmokeTestsOnNanoServerUsingSharedRuntime(
+ DotnetRuntimeSetupTestFixture dotnetRuntimeSetupTestFixture, ITestOutputHelper output)
+ {
+ _logger = new XunitLogger(output, LogLevel.Information);
+ _remoteDeploymentConfig = RemoteDeploymentConfigHelper.GetConfiguration();
+ _remoteDeploymentConfig.DotnetRuntimePathOnShare = dotnetRuntimeSetupTestFixture.DotnetRuntimePathOnShare;
+ _smokeTestsOnNanoServer = new SmokeTestsOnNanoServer(output, _remoteDeploymentConfig, _logger);
}
[ConditionalTheory, Trait("E2Etests", "NanoServer")]
@@ -37,21 +69,169 @@ namespace E2ETests
[OSSkipCondition(OperatingSystems.MacOSX)]
[SkipIfEnvironmentVariableNotEnabled("RUN_TESTS_ON_NANO")]
[InlineData(ServerType.Kestrel, 5000, ApplicationType.Portable)]
- [InlineData(ServerType.Kestrel, 5000, ApplicationType.Standalone)]
[InlineData(ServerType.WebListener, 5000, ApplicationType.Portable)]
- [InlineData(ServerType.WebListener, 5000, ApplicationType.Standalone)]
public async Task Test(ServerType serverType, int portToListen, ApplicationType applicationType)
{
var applicationBaseUrl = $"http://{_remoteDeploymentConfig.ServerName}:{portToListen}/";
- await RunTestsAsync(serverType, applicationBaseUrl, applicationType);
+ await _smokeTestsOnNanoServer.RunTestsAsync(serverType, applicationBaseUrl, applicationType);
}
- private async Task RunTestsAsync(ServerType serverType, string applicationBaseUrl, ApplicationType applicationType)
+ public void Dispose()
{
- using (_logger.BeginScope("SmokeTestSuite"))
+ _logger.Dispose();
+ }
+
+ // Copies dotnet runtime to the target server's file share.
+ public class DotnetRuntimeSetupTestFixture : IDisposable
+ {
+ public DotnetRuntimeSetupTestFixture()
+ {
+ RemoteDeploymentConfig = RemoteDeploymentConfigHelper.GetConfiguration();
+
+ DotnetRuntimePathOnShare = Path.Combine(RemoteDeploymentConfig.FileSharePath, "dotnet");
+
+ // Prefer copying the zip file to fileshare and extracting on file share over copying the extracted
+ // dotnet runtime folder from source to file share as the size could be significantly huge.
+ if (!string.IsNullOrEmpty(RemoteDeploymentConfig.DotnetRuntimeZipFilePath))
+ {
+ if (!File.Exists(RemoteDeploymentConfig.DotnetRuntimeZipFilePath))
+ {
+ throw new InvalidOperationException(
+ $"Expected dotnet runtime zip file at '{RemoteDeploymentConfig.DotnetRuntimeFolderPath}', but didn't find one.");
+ }
+
+ ZippedDotnetRuntimePathOnShare = Path.Combine(
+ RemoteDeploymentConfig.FileSharePath,
+ Path.GetFileName(RemoteDeploymentConfig.DotnetRuntimeZipFilePath));
+
+ if (!File.Exists(ZippedDotnetRuntimePathOnShare))
+ {
+ File.Copy(RemoteDeploymentConfig.DotnetRuntimeZipFilePath, ZippedDotnetRuntimePathOnShare, overwrite: true);
+ Console.WriteLine($"Copied the local dotnet zip folder '{RemoteDeploymentConfig.DotnetRuntimeZipFilePath}' " +
+ $"to the file share path '{RemoteDeploymentConfig.FileSharePath}'");
+ }
+
+ if (Directory.Exists(DotnetRuntimePathOnShare))
+ {
+ Directory.Delete(DotnetRuntimePathOnShare, recursive: true);
+ }
+
+ ZipFile.ExtractToDirectory(ZippedDotnetRuntimePathOnShare, DotnetRuntimePathOnShare);
+ Console.WriteLine($"Extracted dotnet runtime to folder '{DotnetRuntimePathOnShare}'");
+ }
+ else if (!string.IsNullOrEmpty(RemoteDeploymentConfig.DotnetRuntimeFolderPath))
+ {
+ if (!Directory.Exists(RemoteDeploymentConfig.DotnetRuntimeFolderPath))
+ {
+ throw new InvalidOperationException(
+ $"Expected dotnet runtime folder at '{RemoteDeploymentConfig.DotnetRuntimeFolderPath}', but didn't find one.");
+ }
+
+ Console.WriteLine($"Copying dotnet runtime folder from '{RemoteDeploymentConfig.DotnetRuntimeFolderPath}' to '{DotnetRuntimePathOnShare}'.");
+ Console.WriteLine("This could take some time.");
+
+ DirectoryCopy(RemoteDeploymentConfig.DotnetRuntimeFolderPath, DotnetRuntimePathOnShare, copySubDirs: true);
+ }
+ else
+ {
+ throw new InvalidOperationException("Dotnet runtime is required to be copied for testing portable apps scenario. " +
+ $"Either supply '{nameof(RemoteDeploymentConfig.DotnetRuntimeFolderPath)}' containing the unzipped dotnet runtime content or " +
+ $"supply the dotnet runtime zip file path via '{nameof(RemoteDeploymentConfig.DotnetRuntimeZipFilePath)}'.");
+ }
+ }
+
+ public RemoteDeploymentConfig RemoteDeploymentConfig { get; }
+
+ public string ZippedDotnetRuntimePathOnShare { get; }
+
+ public string DotnetRuntimePathOnShare { get; }
+
+ private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
+ {
+ var dir = new DirectoryInfo(sourceDirName);
+
+ if (!dir.Exists)
+ {
+ throw new DirectoryNotFoundException(
+ "Source directory does not exist or could not be found: "
+ + sourceDirName);
+ }
+
+ var dirs = dir.GetDirectories();
+ if (!Directory.Exists(destDirName))
+ {
+ Directory.CreateDirectory(destDirName);
+ }
+
+ var files = dir.GetFiles();
+ foreach (var file in files)
+ {
+ var temppath = Path.Combine(destDirName, file.Name);
+ file.CopyTo(temppath, false);
+ }
+
+ if (copySubDirs)
+ {
+ foreach (var subdir in dirs)
+ {
+ var temppath = Path.Combine(destDirName, subdir.Name);
+ DirectoryCopy(subdir.FullName, temppath, copySubDirs);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ // In case the source is provided as a folder itself, then we wouldn't have the zip file to begin with.
+ if (!string.IsNullOrEmpty(ZippedDotnetRuntimePathOnShare))
+ {
+ try
+ {
+ Console.WriteLine($"Deleting the dotnet runtime zip file '{ZippedDotnetRuntimePathOnShare}'");
+ File.Delete(ZippedDotnetRuntimePathOnShare);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to delete the dotnet runtime zip file '{ZippedDotnetRuntimePathOnShare}'. Exception: "
+ + ex.ToString());
+ }
+ }
+
+ try
+ {
+ Console.WriteLine($"Deleting the dotnet runtime folder '{DotnetRuntimePathOnShare}'");
+ Directory.Delete(DotnetRuntimePathOnShare, recursive: true);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to delete the dotnet runtime folder '{DotnetRuntimePathOnShare}'. Exception: "
+ + ex.ToString());
+ }
+ }
+ }
+ }
+
+ class SmokeTestsOnNanoServer
+ {
+ private readonly XunitLogger _logger;
+ private readonly RemoteDeploymentConfig _remoteDeploymentConfig;
+
+ public SmokeTestsOnNanoServer(ITestOutputHelper output, RemoteDeploymentConfig config, XunitLogger logger)
+ {
+ _logger = logger;
+ _remoteDeploymentConfig = config;
+ }
+
+ public async Task RunTestsAsync(
+ ServerType serverType,
+ string applicationBaseUrl,
+ ApplicationType applicationType)
+ {
+ using (_logger.BeginScope(nameof(SmokeTestsOnNanoServerUsingStandaloneRuntime)))
{
var deploymentParameters = new RemoteWindowsDeploymentParameters(
Helpers.GetApplicationPath(applicationType),
+ _remoteDeploymentConfig.DotnetRuntimePathOnShare,
serverType,
RuntimeFlavor.CoreClr,
RuntimeArchitecture.x64,
@@ -75,10 +255,28 @@ namespace E2ETests
}
}
}
+ }
- public void Dispose()
+ static class RemoteDeploymentConfigHelper
+ {
+ private static RemoteDeploymentConfig _remoteDeploymentConfig;
+
+ public static RemoteDeploymentConfig GetConfiguration()
{
- _logger.Dispose();
+ if (_remoteDeploymentConfig == null)
+ {
+ var configuration = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("remoteDeploymentConfig.json")
+ .AddUserSecrets()
+ .AddEnvironmentVariables()
+ .Build();
+
+ _remoteDeploymentConfig = new RemoteDeploymentConfig();
+ configuration.GetSection("NanoServer").Bind(_remoteDeploymentConfig);
+ }
+
+ return _remoteDeploymentConfig;
}
}
}
diff --git a/test/E2ETests/remoteDeploymentConfig.json b/test/E2ETests/remoteDeploymentConfig.json
index 3bdc799e85..1d2a55f1d1 100644
--- a/test/E2ETests/remoteDeploymentConfig.json
+++ b/test/E2ETests/remoteDeploymentConfig.json
@@ -4,6 +4,7 @@
"AccountName": "",
"AccountPassword": "",
"FileSharePath": "",
- "ExecutableRelativePath": ""
+ "DotnetRuntimeFolderPath": "",
+ "DotnetRuntimeZipFilePath": ""
}
}