Browse Source

Add tye run test (#63)

pull/66/head
Justin Kotalik 7 years ago
committed by GitHub
parent
commit
4b6918ca12
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 127
      src/Tye.Hosting/TyeHost.cs
  2. 2
      src/tye/ConfigModel/ConfigApplication.cs
  3. 2
      src/tye/ConfigModel/ConfigConfigurationSource.cs
  4. 2
      src/tye/ConfigModel/ConfigFactory.cs
  5. 2
      src/tye/ConfigModel/ConfigService.cs
  6. 5
      src/tye/ConfigModel/ConfigServiceBinding.cs
  7. 3
      src/tye/Program.RunCommand.cs
  8. 8
      test/E2ETest/E2ETest.csproj
  9. 53
      test/E2ETest/RetryHandler.cs
  10. 84
      test/E2ETest/TyeRunTest.cs
  11. 14
      test/E2ETest/UnitTest1.cs
  12. 25
      tye.sln

127
src/Tye.Hosting/TyeHost.cs

@ -9,12 +9,89 @@ using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace Tye.Hosting
{
public class TyeHost
{
public static async Task RunAsync(Application application, string[] args)
private Microsoft.Extensions.Logging.ILogger? _logger;
private IHostApplicationLifetime? _lifetime;
private AggregateApplicationProcessor? _processor;
private WebApplication? _app;
private readonly Application _application;
private readonly string[] _args;
public TyeHost(Application application, string[] args)
{
_application = application;
_args = args;
}
public async Task RunAsync()
{
await StartAsync();
var waitForStop = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
_lifetime?.ApplicationStopping.Register(obj => waitForStop.TrySetResult(null), null);
await waitForStop.Task;
await StopAsync();
}
public async Task<WebApplication> StartAsync()
{
var app = BuildWebApplication(_application, _args);
_app = app;
ConfigureApplication(app);
_logger = app.Logger;
_lifetime = app.ApplicationLifetime;
_logger.LogInformation("Executing application from {Source}", _application.Source);
var configuration = app.Configuration;
_processor = CreateApplicationProcessor(_args, _logger, configuration);
await app.StartAsync();
_logger.LogInformation("Dashboard running on {Address}", app.Addresses.First());
try
{
await _processor.StartAsync(_application);
}
catch (Exception ex)
{
_logger.LogError(0, ex, "Failed to launch application");
}
return app;
}
public async Task StopAsync()
{
try
{
if (_processor != null)
{
await _processor.StopAsync(_application);
}
}
finally
{
if (_app != null)
{
// Stop the host after everything else has been shutdown
await _app.StopAsync();
}
}
}
private static WebApplication BuildWebApplication(Application application, string[] args)
{
var builder = WebApplication.CreateBuilder(args);
@ -42,9 +119,12 @@ namespace Tye.Hosting
});
builder.Services.AddSingleton(application);
var app = builder.Build();
return app;
}
using var app = builder.Build();
private static void ConfigureApplication(WebApplication app)
{
var port = app.Configuration["port"] ?? "0";
app.Listen($"http://127.0.0.1:{port}");
@ -61,14 +141,10 @@ namespace Tye.Hosting
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
}
var logger = app.Logger;
logger.LogInformation("Executing application from {Source}", application.Source);
var lifetime = app.ApplicationLifetime;
var configuration = app.Configuration;
private static AggregateApplicationProcessor CreateApplicationProcessor(string[] args, Microsoft.Extensions.Logging.ILogger logger, IConfiguration configuration)
{
var diagnosticOptions = DiagnosticOptions.FromConfiguration(configuration);
var diagnosticsCollector = new DiagnosticsCollector(logger, diagnosticOptions);
@ -81,36 +157,7 @@ namespace Tye.Hosting
new DockerRunner(logger),
new ProcessRunner(logger, ProcessRunnerOptions.FromArgs(args)),
});
await app.StartAsync();
logger.LogInformation("Dashboard running on {Address}", app.Addresses.First());
try
{
await processor.StartAsync(application);
}
catch (Exception ex)
{
logger.LogError(0, ex, "Failed to launch application");
}
var waitForStop = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
lifetime.ApplicationStopping.Register(obj => waitForStop.TrySetResult(null), null);
await waitForStop.Task;
logger.LogInformation("Shutting down...");
try
{
await processor.StopAsync(application);
}
finally
{
// Stop the host after everything else has been shutdown
await app.StopAsync();
}
return processor;
}
}
}

2
src/tye/ConfigModel/ConfigApplication.cs

@ -6,7 +6,7 @@ using YamlDotNet.Serialization;
namespace Tye.ConfigModel
{
internal class ConfigApplication
public class ConfigApplication
{
// This gets set by all of the code paths that read the application
[YamlIgnore]

2
src/tye/ConfigModel/ConfigConfigurationSource.cs

@ -2,7 +2,7 @@
namespace Tye.ConfigModel
{
internal class ConfigConfigurationSource
public class ConfigConfigurationSource
{
[Required]
public string Name { get; set; } = default!;

2
src/tye/ConfigModel/ConfigFactory.cs

@ -7,7 +7,7 @@ using YamlDotNet.Serialization.NamingConventions;
namespace Tye.ConfigModel
{
internal static class ConfigFactory
public static class ConfigFactory
{
public static ConfigApplication FromFile(FileInfo file)
{

2
src/tye/ConfigModel/ConfigService.cs

@ -4,7 +4,7 @@ using YamlDotNet.Serialization;
namespace Tye.ConfigModel
{
internal class ConfigService
public class ConfigService
{
[Required]
public string Name { get; set; } = default!;

5
src/tye/ConfigModel/ConfigServiceBinding.cs

@ -1,8 +1,9 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.ComponentModel.DataAnnotations;
namespace Tye.ConfigModel
{
internal class ConfigServiceBinding
public class ConfigServiceBinding
{
public string? Name { get; set; }
public string? ConnectionString { get; set; }

3
src/tye/Program.RunCommand.cs

@ -59,7 +59,8 @@ namespace Tye
}
var application = ConfigFactory.FromFile(path);
return TyeHost.RunAsync(application.ToHostingApplication(), args);
var host = new TyeHost(application.ToHostingApplication(), args);
return host.RunAsync();
});
return command;

8
test/E2ETest/E2ETest.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
@ -13,4 +13,10 @@
<PackageReference Include="coverlet.collector" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Tye.Hosting\Tye.Hosting.csproj" />
<ProjectReference Include="..\..\src\Tye.Core\Tye.Core.csproj" />
<ProjectReference Include="..\..\src\tye\tye.csproj" />
</ItemGroup>
</Project>

53
test/E2ETest/RetryHandler.cs

@ -0,0 +1,53 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace E2ETest
{
public class RetryHandler : DelegatingHandler
{
private static readonly int MaxRetries = 5;
private static readonly TimeSpan InitialRetryDelay = TimeSpan.FromMilliseconds(100);
public RetryHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage? response = null;
var delay = InitialRetryDelay;
for (var i = 0; i < MaxRetries; i++)
{
try
{
response = await base.SendAsync(request, cancellationToken);
}
catch (Exception)
{
if (i == MaxRetries - 1)
{
throw;
}
}
if (response != null &&
(response.IsSuccessStatusCode || response.StatusCode != (HttpStatusCode)503))
{
return response;
}
await Task.Delay(delay, cancellationToken);
delay *= 2;
}
throw new TimeoutException("Could not reach response after ");
}
}
}

84
test/E2ETest/TyeRunTest.cs

@ -0,0 +1,84 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Tye.ConfigModel;
using Tye.Hosting;
using Xunit;
namespace E2ETest
{
public class TyeRunTest
{
[Fact]
public async Task SingleProjectTest()
{
var application = ConfigFactory.FromFile(new FileInfo(Path.Combine(GetSolutionRootDirectory("tye"), "samples", "single-project", "test-project", "test-project.csproj")));
var host = new TyeHost(application.ToHostingApplication(), new string[0]);
var webApplication = await host.StartAsync();
try
{
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(new RetryHandler(handler));
// Make sure dashboard and applications are up.
// Dashboard should be hosted in same process.
var dashboardResponse = await client.GetStringAsync(new Uri(webApplication.Addresses.First()));
// Only one service for single application.
var service = application.Services.First();
var binding = service.Bindings.First();
var protocol = binding.Protocol?.Length != 0 ? binding.Protocol : "http";
var hostName = binding.Host != null && binding.Host.Length != 0 ? binding.Host : "localhost";
var uriString = $"{protocol}://{hostName}:{binding.Port}";
// Confirm that the uri is in the dashboard response.
Assert.Contains(uriString, dashboardResponse);
var uriBackendProcess = new Uri(uriString);
// This isn't reliable right now because micronetes only guarantees the process starts, not that
// that kestrel started.
var appResponse = await client.GetAsync(uriBackendProcess);
Assert.Equal(HttpStatusCode.OK, appResponse.StatusCode);
}
finally
{
await host.StopAsync();
}
}
// https://github.com/dotnet/aspnetcore/blob/5a0526dfd991419d5bce0d8ea525b50df2e37b04/src/Testing/src/TestPathUtilities.cs
// This can get into a bad pattern for having crazy paths in places. Eventually, especially if we use helix,
// we may want to avoid relying on sln position.
public static string GetSolutionRootDirectory(string solution)
{
var applicationBasePath = AppContext.BaseDirectory;
var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
var projectFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, $"{solution}.sln"));
if (projectFileInfo.Exists)
{
return projectFileInfo.DirectoryName;
}
directoryInfo = directoryInfo.Parent;
}
while (directoryInfo.Parent != null);
throw new Exception($"Solution file {solution}.sln could not be found in {applicationBasePath} or its parent directories.");
}
}
}

14
test/E2ETest/UnitTest1.cs

@ -1,14 +0,0 @@
using System;
using Xunit;
namespace E2ETest
{
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
}

25
tye.sln

@ -1,23 +1,23 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
# Visual Studio Version 16
VisualStudioVersion = 16.0.29613.14
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8C662D59-A3CB-466F-8E85-A8E6BA5E7601}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tye", "src\tye\tye.csproj", "{9BFE4070-D6D8-41F8-B92E-F85C3DCD7AB8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tye", "src\tye\tye.csproj", "{9BFE4070-D6D8-41F8-B92E-F85C3DCD7AB8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F19B02EB-A372-417A-B2C2-EA0D5A3C76D5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E2ETest", "test\E2ETest\E2ETest.csproj", "{D15E5FF6-C1E7-4110-A2BE-06ADA7ACA82B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2ETest", "test\E2ETest\E2ETest.csproj", "{D15E5FF6-C1E7-4110-A2BE-06ADA7ACA82B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tye.Hosting", "src\Tye.Hosting\Tye.Hosting.csproj", "{0F4F5A86-DD27-4AF9-BF97-221EAD5040E7}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tye.Hosting", "src\Tye.Hosting\Tye.Hosting.csproj", "{0F4F5A86-DD27-4AF9-BF97-221EAD5040E7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tye.Hosting.Diagnostics", "src\Tye.Hosting.Diagnostics\Tye.Hosting.Diagnostics.csproj", "{CEBFC149-8162-4A0A-9AD4-40498B9172CD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tye.Hosting.Diagnostics", "src\Tye.Hosting.Diagnostics\Tye.Hosting.Diagnostics.csproj", "{CEBFC149-8162-4A0A-9AD4-40498B9172CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tye.Hosting.Runtime", "src\Tye.Hosting.Runtime\Tye.Hosting.Runtime.csproj", "{34719884-1338-4965-BA2A-F98DB03733C2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tye.Hosting.Runtime", "src\Tye.Hosting.Runtime\Tye.Hosting.Runtime.csproj", "{34719884-1338-4965-BA2A-F98DB03733C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tye.Core", "src\Tye.Core\Tye.Core.csproj", "{D0359C69-6EA9-4B03-9455-90E8E04F1CB0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tye.Core", "src\Tye.Core\Tye.Core.csproj", "{D0359C69-6EA9-4B03-9455-90E8E04F1CB0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -28,9 +28,6 @@ Global
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9BFE4070-D6D8-41F8-B92E-F85C3DCD7AB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9BFE4070-D6D8-41F8-B92E-F85C3DCD7AB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
@ -105,6 +102,9 @@ Global
{D0359C69-6EA9-4B03-9455-90E8E04F1CB0}.Release|x86.ActiveCfg = Release|Any CPU
{D0359C69-6EA9-4B03-9455-90E8E04F1CB0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9BFE4070-D6D8-41F8-B92E-F85C3DCD7AB8} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
{D15E5FF6-C1E7-4110-A2BE-06ADA7ACA82B} = {F19B02EB-A372-417A-B2C2-EA0D5A3C76D5}
@ -113,4 +113,7 @@ Global
{34719884-1338-4965-BA2A-F98DB03733C2} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
{D0359C69-6EA9-4B03-9455-90E8E04F1CB0} = {8C662D59-A3CB-466F-8E85-A8E6BA5E7601}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D8002603-BB27-4500-BF86-274A8E72D302}
EndGlobalSection
EndGlobal

Loading…
Cancel
Save