Browse Source

Add tye generate tests (#85)

pull/86/head
Justin Kotalik 7 years ago
committed by GitHub
parent
commit
c0d533430b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      samples/single-project/tye.yaml
  2. 2
      src/Tye.Core/DockerDetector.cs
  3. 2
      src/Tye.Core/NameSanitizer.cs
  4. 2
      src/Tye.Core/ProcessResult.cs
  5. 2
      src/Tye.Core/ProcessUtil.cs
  6. 4
      src/Tye.Core/Tye.Core.csproj
  7. 4
      src/Tye.Hosting/AggregateApplicationProcessor.cs
  8. 10
      src/Tye.Hosting/DockerRunner.cs
  9. 4
      src/Tye.Hosting/EventPipeDiagnosticsRunner.cs
  10. 4
      src/Tye.Hosting/IApplicationProcessor.cs
  11. 10
      src/Tye.Hosting/ProcessRunner.cs
  12. 4
      src/Tye.Hosting/ProxyService.cs
  13. 8
      src/Tye.Hosting/Tye.Hosting.csproj
  14. 10
      src/Tye.Hosting/TyeDashboardApi.cs
  15. 6
      src/Tye.Hosting/TyeHost.cs
  16. 8
      src/shared/TempDirectory.cs
  17. 59
      src/tye/GenerateHost.cs
  18. 2
      src/tye/Program.DeployCommand.cs
  19. 41
      src/tye/Program.GenerateCommand.cs
  20. 4
      src/tye/tye.csproj
  21. 3
      test/E2ETest/E2ETest.csproj
  22. 12
      test/E2ETest/Infrastructure/ConditionalFactAttribute.cs
  23. 25
      test/E2ETest/Infrastructure/ConditionalFactDiscoverer.cs
  24. 9
      test/E2ETest/Infrastructure/ITestCondition.cs
  25. 24
      test/E2ETest/Infrastructure/SkipIfDockerNotRunningAttribute.cs
  26. 46
      test/E2ETest/Infrastructure/SkippedTestCase.cs
  27. 31
      test/E2ETest/Infrastructure/TestMethodExtensions.cs
  28. 22
      test/E2ETest/TestOutputLogEventSink.cs
  29. 106
      test/E2ETest/TyeGenerateTests.cs
  30. 6
      test/E2ETest/TyeInitTests.cs
  31. 101
      test/E2ETest/testassets/generate/frontend-backend.yaml
  32. 167
      test/E2ETest/testassets/generate/multi-project.yaml
  33. 51
      test/E2ETest/testassets/generate/single-project.yaml

6
samples/single-project/tye.yaml

@ -0,0 +1,6 @@
# tye application configuration file
# read all about it at https://github.com/dotnet/tye
name: single-project
services:
- name: test-project
project: test-project/test-project.csproj

2
src/shared/DockerDetector.cs → src/Tye.Core/DockerDetector.cs

@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Tye namespace Tye
{ {
internal class DockerDetector public class DockerDetector
{ {
public static DockerDetector Instance { get; } = new DockerDetector(); public static DockerDetector Instance { get; } = new DockerDetector();

2
src/shared/NameSanitizer.cs → src/Tye.Core/NameSanitizer.cs

@ -6,7 +6,7 @@ using System;
namespace Tye namespace Tye
{ {
internal static class NameSanitizer public static class NameSanitizer
{ {
// Converts an arbitrary string into something usable as a C# identifier. // Converts an arbitrary string into something usable as a C# identifier.
// //

2
src/shared/ProcessResult.cs → src/Tye.Core/ProcessResult.cs

@ -4,7 +4,7 @@
namespace Tye namespace Tye
{ {
internal class ProcessResult public class ProcessResult
{ {
public ProcessResult(string standardOutput, string standardError, int exitCode) public ProcessResult(string standardOutput, string standardError, int exitCode)
{ {

2
src/shared/ProcessUtil.cs → src/Tye.Core/ProcessUtil.cs

@ -13,7 +13,7 @@ using Mono.Unix.Native;
namespace Tye namespace Tye
{ {
internal static class ProcessUtil public static class ProcessUtil
{ {
public static async Task<ProcessResult> RunAsync( public static async Task<ProcessResult> RunAsync(
string filename, string filename,

4
src/Tye.Core/Tye.Core.csproj

@ -25,11 +25,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\shared\DockerDetector.cs" Link="DockerDetector.cs" />
<Compile Include="..\shared\KubectlDetector.cs" Link="KubectlDetector.cs" /> <Compile Include="..\shared\KubectlDetector.cs" Link="KubectlDetector.cs" />
<Compile Include="..\shared\NameSanitizer.cs" Link="NameSanitizer.cs" />
<Compile Include="..\shared\ProcessUtil.cs" Link="ProcessUtil.cs" />
<Compile Include="..\shared\ProcessResult.cs" Link="ProcessResult.cs" />
<Compile Include="..\shared\TempDirectory.cs" Link="TempDirectory.cs" /> <Compile Include="..\shared\TempDirectory.cs" Link="TempDirectory.cs" />
<Compile Include="..\shared\TempFile.cs" Link="TempFile.cs" /> <Compile Include="..\shared\TempFile.cs" Link="TempFile.cs" />
</ItemGroup> </ItemGroup>

4
src/Tye.Hosting/AggregateApplicationProcessor.cs

@ -18,7 +18,7 @@ namespace Tye.Hosting
_applicationProcessors = applicationProcessors; _applicationProcessors = applicationProcessors;
} }
public async Task StartAsync(Application application) public async Task StartAsync(Tye.Hosting.Model.Application application)
{ {
foreach (var processor in _applicationProcessors) foreach (var processor in _applicationProcessors)
{ {
@ -26,7 +26,7 @@ namespace Tye.Hosting
} }
} }
public async Task StopAsync(Application application) public async Task StopAsync(Tye.Hosting.Model.Application application)
{ {
// Shutdown in the opposite order // Shutdown in the opposite order
foreach (var processor in _applicationProcessors.Reverse()) foreach (var processor in _applicationProcessors.Reverse())

10
src/Tye.Hosting/DockerRunner.cs

@ -21,7 +21,7 @@ namespace Tye.Hosting
_logger = logger; _logger = logger;
} }
public Task StartAsync(Application application) public Task StartAsync(Tye.Hosting.Model.Application application)
{ {
var tasks = new Task[application.Services.Count]; var tasks = new Task[application.Services.Count];
var index = 0; var index = 0;
@ -33,7 +33,7 @@ namespace Tye.Hosting
return Task.WhenAll(tasks); return Task.WhenAll(tasks);
} }
public Task StopAsync(Application application) public Task StopAsync(Tye.Hosting.Model.Application application)
{ {
var services = application.Services; var services = application.Services;
@ -48,7 +48,7 @@ namespace Tye.Hosting
return Task.WhenAll(tasks); return Task.WhenAll(tasks);
} }
private async Task StartContainerAsync(Application application, Service service, DockerRunInfo docker) private async Task StartContainerAsync(Tye.Hosting.Model.Application application, Tye.Hosting.Model.Service service, DockerRunInfo docker)
{ {
if (!await DockerDetector.Instance.IsDockerInstalled.Value) if (!await DockerDetector.Instance.IsDockerInstalled.Value)
{ {
@ -224,7 +224,7 @@ namespace Tye.Hosting
service.Items[typeof(DockerInformation)] = dockerInfo; service.Items[typeof(DockerInformation)] = dockerInfo;
} }
private static void PrintStdOutAndErr(Service service, string replica, ProcessResult result) private static void PrintStdOutAndErr(Tye.Hosting.Model.Service service, string replica, ProcessResult result)
{ {
if (result.ExitCode != 0) if (result.ExitCode != 0)
{ {
@ -240,7 +240,7 @@ namespace Tye.Hosting
} }
} }
private async Task StopContainerAsync(Service service) private async Task StopContainerAsync(Tye.Hosting.Model.Service service)
{ {
if (service.Items.TryGetValue(typeof(DockerInformation), out var value) && value is DockerInformation di) if (service.Items.TryGetValue(typeof(DockerInformation), out var value) && value is DockerInformation di)
{ {

4
src/Tye.Hosting/EventPipeDiagnosticsRunner.cs

@ -23,7 +23,7 @@ namespace Tye.Hosting
_diagnosticsCollector = diagnosticsCollector; _diagnosticsCollector = diagnosticsCollector;
} }
public Task StartAsync(Application application) public Task StartAsync(Tye.Hosting.Model.Application application)
{ {
foreach (var service in application.Services.Values) foreach (var service in application.Services.Values)
{ {
@ -38,7 +38,7 @@ namespace Tye.Hosting
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task StopAsync(Application application) public Task StopAsync(Tye.Hosting.Model.Application application)
{ {
foreach (var service in application.Services.Values) foreach (var service in application.Services.Values)
{ {

4
src/Tye.Hosting/IApplicationProcessor.cs

@ -9,8 +9,8 @@ namespace Tye.Hosting
{ {
public interface IApplicationProcessor public interface IApplicationProcessor
{ {
Task StartAsync(Application application); Task StartAsync(Tye.Hosting.Model.Application application);
Task StopAsync(Application application); Task StopAsync(Tye.Hosting.Model.Application application);
} }
} }

10
src/Tye.Hosting/ProcessRunner.cs

@ -28,7 +28,7 @@ namespace Tye.Hosting
_buildProjects = options.BuildProjects; _buildProjects = options.BuildProjects;
} }
public Task StartAsync(Application application) public Task StartAsync(Tye.Hosting.Model.Application application)
{ {
var tasks = new Task[application.Services.Count]; var tasks = new Task[application.Services.Count];
var index = 0; var index = 0;
@ -49,12 +49,12 @@ namespace Tye.Hosting
return Task.WhenAll(tasks); return Task.WhenAll(tasks);
} }
public Task StopAsync(Application application) public Task StopAsync(Tye.Hosting.Model.Application application)
{ {
return KillRunningProcesses(application.Services); return KillRunningProcesses(application.Services);
} }
private async Task LaunchService(Application application, Service service) private async Task LaunchService(Tye.Hosting.Model.Application application, Tye.Hosting.Model.Service service)
{ {
var serviceDescription = service.Description; var serviceDescription = service.Description;
var serviceName = serviceDescription.Name; var serviceName = serviceDescription.Name;
@ -263,9 +263,9 @@ namespace Tye.Hosting
service.Items[typeof(ProcessInfo)] = processInfo; service.Items[typeof(ProcessInfo)] = processInfo;
} }
private Task KillRunningProcesses(IDictionary<string, Service> services) private Task KillRunningProcesses(IDictionary<string, Tye.Hosting.Model.Service> services)
{ {
static async Task KillProcessAsync(Service service) static async Task KillProcessAsync(Tye.Hosting.Model.Service service)
{ {
if (service.Items.TryGetValue(typeof(ProcessInfo), out var stateObj) && stateObj is ProcessInfo state) if (service.Items.TryGetValue(typeof(ProcessInfo), out var stateObj) && stateObj is ProcessInfo state)
{ {

4
src/Tye.Hosting/ProxyService.cs

@ -30,7 +30,7 @@ namespace Tye.Hosting
_logger = logger; _logger = logger;
} }
public async Task StartAsync(Application application) public async Task StartAsync(Tye.Hosting.Model.Application application)
{ {
_host = new HostBuilder() _host = new HostBuilder()
.ConfigureServer(server => .ConfigureServer(server =>
@ -172,7 +172,7 @@ namespace Tye.Hosting
await _host.StartAsync(); await _host.StartAsync();
} }
public async Task StopAsync(Application application) public async Task StopAsync(Tye.Hosting.Model.Application application)
{ {
if (_host != null) if (_host != null)
{ {

8
src/Tye.Hosting/Tye.Hosting.csproj

@ -13,13 +13,6 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="wwwroot\**" /> <EmbeddedResource Include="wwwroot\**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\shared\DockerDetector.cs" Link="DockerDetector.cs" />
<Compile Include="..\shared\NameSanitizer.cs" Link="NameSanitizer.cs" />
<Compile Include="..\shared\ProcessUtil.cs" Link="ProcessUtil.cs" />
<Compile Include="..\shared\ProcessResult.cs" Link="ProcessResult.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Bedrock.Framework" Version="0.1.38-alpha.gd25d5b37ad" /> <PackageReference Include="Bedrock.Framework" Version="0.1.38-alpha.gd25d5b37ad" />
@ -36,6 +29,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Tye.Hosting.Diagnostics\Tye.Hosting.Diagnostics.csproj" /> <ProjectReference Include="..\Tye.Hosting.Diagnostics\Tye.Hosting.Diagnostics.csproj" />
<ProjectReference Include="..\Tye.Core\Tye.Core.csproj" />
<ProjectReference Include="..\Tye.Hosting.Runtime\Tye.Hosting.Runtime.csproj" /> <ProjectReference Include="..\Tye.Hosting.Runtime\Tye.Hosting.Runtime.csproj" />
</ItemGroup> </ItemGroup>

10
src/Tye.Hosting/TyeDashboardApi.cs

@ -55,7 +55,7 @@ namespace Tye.Hosting
private async Task Services(HttpContext context) private async Task Services(HttpContext context)
{ {
var app = context.RequestServices.GetRequiredService<Application>(); var app = context.RequestServices.GetRequiredService<Tye.Hosting.Model.Application>();
context.Response.ContentType = "application/json"; context.Response.ContentType = "application/json";
@ -66,7 +66,7 @@ namespace Tye.Hosting
private async Task Service(HttpContext context) private async Task Service(HttpContext context)
{ {
var app = context.RequestServices.GetRequiredService<Application>(); var app = context.RequestServices.GetRequiredService<Tye.Hosting.Model.Application>();
var name = (string)context.Request.RouteValues["name"]; var name = (string)context.Request.RouteValues["name"];
context.Response.ContentType = "application/json"; context.Response.ContentType = "application/json";
@ -88,7 +88,7 @@ namespace Tye.Hosting
private async Task Logs(HttpContext context) private async Task Logs(HttpContext context)
{ {
var app = context.RequestServices.GetRequiredService<Application>(); var app = context.RequestServices.GetRequiredService<Tye.Hosting.Model.Application>();
var name = (string)context.Request.RouteValues["name"]; var name = (string)context.Request.RouteValues["name"];
context.Response.ContentType = "application/json"; context.Response.ContentType = "application/json";
@ -110,7 +110,7 @@ namespace Tye.Hosting
private async Task AllMetrics(HttpContext context) private async Task AllMetrics(HttpContext context)
{ {
var app = context.RequestServices.GetRequiredService<Application>(); var app = context.RequestServices.GetRequiredService<Tye.Hosting.Model.Application>();
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var s in app.Services.OrderBy(s => s.Key)) foreach (var s in app.Services.OrderBy(s => s.Key))
@ -138,7 +138,7 @@ namespace Tye.Hosting
private async Task Metics(HttpContext context) private async Task Metics(HttpContext context)
{ {
var app = context.RequestServices.GetRequiredService<Application>(); var app = context.RequestServices.GetRequiredService<Tye.Hosting.Model.Application>();
var sb = new StringBuilder(); var sb = new StringBuilder();

6
src/Tye.Hosting/TyeHost.cs

@ -26,10 +26,10 @@ namespace Tye.Hosting
private IHostApplicationLifetime? _lifetime; private IHostApplicationLifetime? _lifetime;
private AggregateApplicationProcessor? _processor; private AggregateApplicationProcessor? _processor;
private readonly Application _application; private readonly Tye.Hosting.Model.Application _application;
private readonly string[] _args; private readonly string[] _args;
public TyeHost(Application application, string[] args) public TyeHost(Tye.Hosting.Model.Application application, string[] args)
{ {
_application = application; _application = application;
_args = args; _args = args;
@ -103,7 +103,7 @@ namespace Tye.Hosting
} }
private static WebApplication BuildWebApplication( private static WebApplication BuildWebApplication(
Application application, Tye.Hosting.Model.Application application,
string[] args, string[] args,
ILogEventSink? sink) ILogEventSink? sink)
{ {

8
src/shared/TempDirectory.cs

@ -12,16 +12,18 @@ namespace Tye
public static TempDirectory Create() public static TempDirectory Create()
{ {
var directoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); var directoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(directoryPath); var directoryInfo = Directory.CreateDirectory(directoryPath);
return new TempDirectory(directoryPath); return new TempDirectory(directoryPath, directoryInfo);
} }
private TempDirectory(string directoryPath) private TempDirectory(string directoryPath, DirectoryInfo directoryInfo)
{ {
DirectoryPath = directoryPath; DirectoryPath = directoryPath;
DirectoryInfo = directoryInfo;
} }
public string DirectoryPath { get; } public string DirectoryPath { get; }
public DirectoryInfo DirectoryInfo { get; }
public void Dispose() public void Dispose()
{ {

59
src/tye/GenerateHost.cs

@ -0,0 +1,59 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.CommandLine;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Tye.ConfigModel;
namespace Tye
{
public class GenerateHost
{
public static Task GenerateAsync(IConsole console, FileInfo path, Verbosity verbosity, bool interactive)
{
var application = ConfigFactory.FromFile(path);
return ExecuteGenerateAsync(new OutputContext(console, verbosity), application, environment: "production", interactive);
}
public static async Task ExecuteGenerateAsync(OutputContext output, ConfigApplication application, string environment, bool interactive)
{
var temporaryApplication = await Program.CreateApplicationAdapterAsync(output, application, interactive);
var steps = new List<ServiceExecutor.Step>()
{
new CombineStep() { Environment = environment, },
new PublishProjectStep(),
new BuildDockerImageStep() { Environment = environment, }, // Make an image but don't push it
};
steps.Add(new GenerateKubernetesManifestStep() { Environment = environment, });
var executor = new ServiceExecutor(output, temporaryApplication, steps);
foreach (var service in temporaryApplication.Services)
{
await executor.ExecuteAsync(service);
}
await GenerateApplicationManifestAsync(output, temporaryApplication, application.Name ?? application.Source.Directory.Name, environment);
}
private static async Task GenerateApplicationManifestAsync(OutputContext output, Tye.Application application, string applicationName, string environment)
{
using var step = output.BeginStep("Generating Application Manifests...");
var outputFilePath = Path.GetFullPath(Path.Combine(application.RootDirectory, $"{applicationName}-generate-{environment}.yaml"));
output.WriteInfoLine($"Writing output to '{outputFilePath}'.");
{
using var stream = File.OpenWrite(outputFilePath);
using var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true);
await ApplicationYamlWriter.WriteAsync(output, writer, application);
}
step.MarkComplete();
}
}
}

2
src/tye/Program.DeployCommand.cs

@ -78,7 +78,7 @@ namespace Tye
await DeployApplicationManifestAsync(output, temporaryApplication, application.Source.Directory.Name, environment); await DeployApplicationManifestAsync(output, temporaryApplication, application.Source.Directory.Name, environment);
} }
private static async Task<TemporaryApplicationAdapter> CreateApplicationAdapterAsync(OutputContext output, ConfigApplication application, bool interactive) internal static async Task<TemporaryApplicationAdapter> CreateApplicationAdapterAsync(OutputContext output, ConfigApplication application, bool interactive)
{ {
var globals = new ApplicationGlobals() var globals = new ApplicationGlobals()
{ {

41
src/tye/Program.GenerateCommand.cs

@ -35,49 +35,10 @@ namespace Tye
throw new CommandException("No project or solution file was found."); throw new CommandException("No project or solution file was found.");
} }
var application = ConfigFactory.FromFile(path); return GenerateHost.GenerateAsync(console, path, verbosity, interactive);
return ExecuteGenerateAsync(new OutputContext(console, verbosity), application, environment: "production", interactive);
}); });
return command; return command;
} }
private static async Task ExecuteGenerateAsync(OutputContext output, ConfigApplication application, string environment, bool interactive)
{
var temporaryApplication = await CreateApplicationAdapterAsync(output, application, interactive);
var steps = new List<ServiceExecutor.Step>()
{
new CombineStep() { Environment = environment, },
new PublishProjectStep(),
new BuildDockerImageStep() { Environment = environment, }, // Make an image but don't push it
};
steps.Add(new GenerateKubernetesManifestStep() { Environment = environment, });
var executor = new ServiceExecutor(output, temporaryApplication, steps);
foreach (var service in temporaryApplication.Services)
{
await executor.ExecuteAsync(service);
}
await GenerateApplicationManifestAsync(output, temporaryApplication, application.Source.Directory.Name, environment);
}
private static async Task GenerateApplicationManifestAsync(OutputContext output, Tye.Application application, string applicationName, string environment)
{
using var step = output.BeginStep("Generating Application Manifests...");
var outputFilePath = Path.GetFullPath(Path.Combine(".", $"{applicationName}-{environment}.yaml"));
output.WriteInfoLine($"Writing output to '{outputFilePath}'.");
{
using var stream = File.OpenWrite(outputFilePath);
using var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true);
await ApplicationYamlWriter.WriteAsync(output, writer, application);
}
step.MarkComplete();
}
} }
} }

4
src/tye/tye.csproj

@ -11,11 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\shared\DockerDetector.cs" Link="DockerDetector.cs" />
<Compile Include="..\shared\KubectlDetector.cs" Link="KubectlDetector.cs" /> <Compile Include="..\shared\KubectlDetector.cs" Link="KubectlDetector.cs" />
<Compile Include="..\shared\NameSanitizer.cs" Link="NameSanitizer.cs" />
<Compile Include="..\shared\ProcessUtil.cs" Link="ProcessUtil.cs" />
<Compile Include="..\shared\ProcessResult.cs" Link="ProcessResult.cs" />
<Compile Include="..\shared\TempDirectory.cs" Link="TempDirectory.cs" /> <Compile Include="..\shared\TempDirectory.cs" Link="TempDirectory.cs" />
<Compile Include="..\shared\TempFile.cs" Link="TempFile.cs" /> <Compile Include="..\shared\TempFile.cs" Link="TempFile.cs" />
</ItemGroup> </ItemGroup>

3
test/E2ETest/E2ETest.csproj

@ -23,6 +23,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="testassets\generate\frontend-backend.yaml" />
<None Remove="testassets\generate\multi-project.yaml" />
<None Remove="testassets\generate\single-project.yaml" />
<None Remove="testassets\init\frontend-backend.yaml" /> <None Remove="testassets\init\frontend-backend.yaml" />
<None Remove="testassets\init\multi-project.yaml" /> <None Remove="testassets\init\multi-project.yaml" />
</ItemGroup> </ItemGroup>

12
test/E2ETest/Infrastructure/ConditionalFactAttribute.cs

@ -0,0 +1,12 @@
using System;
using Xunit;
using Xunit.Sdk;
namespace E2ETest
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("E2ETest." + nameof(ConditionalFactDiscoverer), "E2ETest")]
public class ConditionalFactAttribute : FactAttribute
{
}
}

25
test/E2ETest/Infrastructure/ConditionalFactDiscoverer.cs

@ -0,0 +1,25 @@
using Xunit.Abstractions;
using Xunit.Sdk;
// Do not change this namespace without changing the usage in ConditionalFactAttribute
namespace E2ETest
{
internal class ConditionalFactDiscoverer : FactDiscoverer
{
private readonly IMessageSink _diagnosticMessageSink;
public ConditionalFactDiscoverer(IMessageSink diagnosticMessageSink)
: base(diagnosticMessageSink)
{
_diagnosticMessageSink = diagnosticMessageSink;
}
protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
{
var skipReason = testMethod.EvaluateSkipConditions();
return skipReason != null
? new SkippedTestCase(skipReason, _diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod)
: base.CreateTestCase(discoveryOptions, testMethod, factAttribute);
}
}
}

9
test/E2ETest/Infrastructure/ITestCondition.cs

@ -0,0 +1,9 @@
namespace E2ETest
{
public interface ITestCondition
{
bool IsMet { get; }
string SkipReason { get; }
}
}

24
test/E2ETest/Infrastructure/SkipIfDockerNotRunningAttribute.cs

@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Tye;
namespace E2ETest
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
internal class SkipIfDockerNotRunningAttribute : Attribute, ITestCondition
{
public SkipIfDockerNotRunningAttribute()
{
// TODO Check performance of this.
IsMet = DockerDetector.Instance.IsDockerConnectedToDaemon.Value.GetAwaiter().GetResult();
SkipReason = "Docker is not installed or running.";
}
public bool IsMet { get; }
public string SkipReason { get; set; }
}
}

46
test/E2ETest/Infrastructure/SkippedTestCase.cs

@ -0,0 +1,46 @@
using System;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace E2ETest
{
public class SkippedTestCase : XunitTestCase
{
private string _skipReason;
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public SkippedTestCase() : base()
{
_skipReason = "";
}
public SkippedTestCase(
string skipReason,
IMessageSink diagnosticMessageSink,
TestMethodDisplay defaultMethodDisplay,
TestMethodDisplayOptions defaultMethodDisplayOptions,
ITestMethod testMethod)
: base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod)
{
_skipReason = skipReason;
}
protected override string GetSkipReason(IAttributeInfo factAttribute)
=> _skipReason ?? base.GetSkipReason(factAttribute);
public override void Deserialize(IXunitSerializationInfo data)
{
_skipReason = data.GetValue<string>(nameof(_skipReason));
// We need to call base after reading our value, because Deserialize will call
// into GetSkipReason.
base.Deserialize(data);
}
public override void Serialize(IXunitSerializationInfo data)
{
base.Serialize(data);
data.AddValue(nameof(_skipReason), _skipReason);
}
}
}

31
test/E2ETest/Infrastructure/TestMethodExtensions.cs

@ -0,0 +1,31 @@
using System.Linq;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace E2ETest
{
public static class TestMethodExtensions
{
public static string EvaluateSkipConditions(this ITestMethod testMethod)
{
var testClass = testMethod.TestClass.Class;
var assembly = testMethod.TestClass.TestCollection.TestAssembly.Assembly;
var conditionAttributes = testMethod.Method
.GetCustomAttributes(typeof(ITestCondition))
.Concat(testClass.GetCustomAttributes(typeof(ITestCondition)))
.Concat(assembly.GetCustomAttributes(typeof(ITestCondition)))
.OfType<ReflectionAttributeInfo>()
.Select(attributeInfo => attributeInfo.Attribute);
foreach (ITestCondition condition in conditionAttributes)
{
if (!condition.IsMet)
{
return condition.SkipReason;
}
}
return null!;
}
}
}

22
test/E2ETest/TestOutputLogEventSink.cs

@ -1,20 +1,38 @@
using Serilog.Core; using System.CommandLine;
using System.CommandLine.IO;
using Serilog.Core;
using Serilog.Events; using Serilog.Events;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace E2ETest namespace E2ETest
{ {
public class TestOutputLogEventSink : ILogEventSink public class TestOutputLogEventSink : ILogEventSink, IConsole, IStandardStreamWriter
{ {
private readonly ITestOutputHelper output; private readonly ITestOutputHelper output;
public TestOutputLogEventSink(ITestOutputHelper output) public TestOutputLogEventSink(ITestOutputHelper output)
{ {
this.output = output; this.output = output;
} }
public IStandardStreamWriter Out => this;
public bool IsOutputRedirected => false;
public IStandardStreamWriter Error => this;
public bool IsErrorRedirected => false;
public bool IsInputRedirected => false;
public void Emit(LogEvent logEvent) public void Emit(LogEvent logEvent)
{ {
output.WriteLine(logEvent.RenderMessage()); output.WriteLine(logEvent.RenderMessage());
} }
public void Write(string value)
{
output.WriteLine(value);
}
} }
} }

106
test/E2ETest/TyeGenerateTests.cs

@ -0,0 +1,106 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.IO;
using System.Threading.Tasks;
using Tye;
using Tye.ConfigModel;
using Xunit;
using Xunit.Abstractions;
namespace E2ETest
{
public class TyeGenerateTests
{
private readonly ITestOutputHelper output;
private readonly TestOutputLogEventSink sink;
public TyeGenerateTests(ITestOutputHelper output)
{
this.output = output;
sink = new TestOutputLogEventSink(output);
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task SingleProjectGenerateTest()
{
var projectName = "single-project";
var environment = "production";
var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", projectName));
using var tempDirectory = TempDirectory.Create();
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml"));
var application = ConfigFactory.FromFile(projectFile);
// Need to add docker registry for generate
application.Registry = "test";
await GenerateHost.ExecuteGenerateAsync(new OutputContext(sink, Verbosity.Debug), application, environment, interactive: false);
// name of application is the folder
var content = File.ReadAllText(Path.Combine(tempDirectory.DirectoryPath, $"{projectName}-generate-{environment}.yaml"));
var expectedContent = File.ReadAllText($"testassets/generate/{projectName}.yaml");
Assert.Equal(expectedContent, content);
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task FrontendBackendGenerateTest()
{
var projectName = "frontend-backend";
var environment = "production";
var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", projectName));
using var tempDirectory = TempDirectory.Create();
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml"));
var application = ConfigFactory.FromFile(projectFile);
// Need to add docker registry for generate
application.Registry = "test";
await GenerateHost.ExecuteGenerateAsync(new OutputContext(sink, Verbosity.Debug), application, environment, interactive: false);
// name of application is the folder
var content = File.ReadAllText(Path.Combine(tempDirectory.DirectoryPath, $"{projectName}-generate-{environment}.yaml"));
var expectedContent = File.ReadAllText($"testassets/generate/{projectName}.yaml");
Assert.Equal(expectedContent, content);
}
[ConditionalFact]
[SkipIfDockerNotRunning]
public async Task MultipleProjectGenerateTest()
{
var projectName = "multi-project";
var environment = "production";
var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", projectName));
using var tempDirectory = TempDirectory.Create();
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml"));
var application = ConfigFactory.FromFile(projectFile);
// Need to add docker registry for generate
application.Registry = "test";
await GenerateHost.ExecuteGenerateAsync(new OutputContext(sink, Verbosity.Debug), application, environment, interactive: false);
// name of application is the folder
var content = File.ReadAllText(Path.Combine(tempDirectory.DirectoryPath, $"{projectName}-generate-{environment}.yaml"));
var expectedContent = File.ReadAllText($"testassets/generate/{projectName}.yaml");
Assert.Equal(expectedContent, content);
}
}
}

6
test/E2ETest/TyeInitTests.cs

@ -2,12 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.IO; using System.IO;
using System.Text.Json;
using Microsoft.Build.Construction;
using Tye; using Tye;
using Tye.ConfigModel;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -31,6 +27,8 @@ namespace E2ETest
using var tempDirectory = TempDirectory.Create(); using var tempDirectory = TempDirectory.Create();
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath); DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);
File.Delete(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml"));
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "test-project.csproj")); var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "test-project.csproj"));
var (content, _) = InitHost.CreateTyeFileContent(projectFile, force: false); var (content, _) = InitHost.CreateTyeFileContent(projectFile, force: false);

101
test/E2ETest/testassets/generate/frontend-backend.yaml

@ -0,0 +1,101 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: backend
labels:
app.kubernetes.io/name: backend
app.kubernetes.io/part-of: frontend-backend
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: backend
template:
metadata:
labels:
app.kubernetes.io/name: backend
app.kubernetes.io/part-of: frontend-backend
spec:
containers:
- name: backend
image: test/backend:1.0.0
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://*:5050
- name: SERVICE__FRONTEND__PORT
value: '5051'
- name: SERVICE__FRONTEND__HOST
value: 'frontend'
ports:
- containerPort: 5050
...
---
kind: Service
apiVersion: v1
metadata:
name: backend
labels:
app.kubernetes.io/name: backend
app.kubernetes.io/part-of: frontend-backend
spec:
selector:
app.kubernetes.io/name: backend
type: ClusterIP
ports:
- name: backend
protocol: TCP
port: 5050
targetPort: 5050
...
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: frontend
labels:
app.kubernetes.io/name: frontend
app.kubernetes.io/part-of: frontend-backend
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: frontend
template:
metadata:
labels:
app.kubernetes.io/name: frontend
app.kubernetes.io/part-of: frontend-backend
spec:
containers:
- name: frontend
image: test/frontend:1.0.0
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://*:5051
- name: SERVICE__BACKEND__PORT
value: '5050'
- name: SERVICE__BACKEND__HOST
value: 'backend'
ports:
- containerPort: 5051
...
---
kind: Service
apiVersion: v1
metadata:
name: frontend
labels:
app.kubernetes.io/name: frontend
app.kubernetes.io/part-of: frontend-backend
spec:
selector:
app.kubernetes.io/name: frontend
type: ClusterIP
ports:
- name: frontend
protocol: TCP
port: 5051
targetPort: 5051
...

167
test/E2ETest/testassets/generate/multi-project.yaml

@ -0,0 +1,167 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: backend
labels:
app.kubernetes.io/name: backend
app.kubernetes.io/part-of: multi-project
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: backend
template:
metadata:
labels:
app.kubernetes.io/name: backend
app.kubernetes.io/part-of: multi-project
spec:
containers:
- name: backend
image: test/backend:1.0.0
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://*:7000
- name: SERVICE__FRONTEND__PORT
value: '8000'
- name: SERVICE__FRONTEND__HOST
value: 'frontend'
volumeMounts:
- name: rabbit-rabbit
mountPath: /var/tye/bindings/rabbit-rabbit
readOnly: true
ports:
- containerPort: 7000
volumes:
- name: rabbit-rabbit
secret:
secretName: binding-production-rabbit-rabbit-secret
items:
- key: connectionstring
path: CONNECTIONSTRING__RABBIT
...
---
kind: Service
apiVersion: v1
metadata:
name: backend
labels:
app.kubernetes.io/name: backend
app.kubernetes.io/part-of: multi-project
spec:
selector:
app.kubernetes.io/name: backend
type: ClusterIP
ports:
- name: backend
protocol: TCP
port: 7000
targetPort: 7000
...
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: frontend
labels:
app.kubernetes.io/name: frontend
app.kubernetes.io/part-of: multi-project
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: frontend
template:
metadata:
labels:
app.kubernetes.io/name: frontend
app.kubernetes.io/part-of: multi-project
spec:
containers:
- name: frontend
image: test/frontend:1.0.0
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://*:8000
- name: SERVICE__BACKEND__PORT
value: '7000'
- name: SERVICE__BACKEND__HOST
value: 'backend'
volumeMounts:
- name: rabbit-rabbit
mountPath: /var/tye/bindings/rabbit-rabbit
readOnly: true
ports:
- containerPort: 8000
volumes:
- name: rabbit-rabbit
secret:
secretName: binding-production-rabbit-rabbit-secret
items:
- key: connectionstring
path: CONNECTIONSTRING__RABBIT
...
---
kind: Service
apiVersion: v1
metadata:
name: frontend
labels:
app.kubernetes.io/name: frontend
app.kubernetes.io/part-of: multi-project
spec:
selector:
app.kubernetes.io/name: frontend
type: ClusterIP
ports:
- name: frontend
protocol: TCP
port: 8000
targetPort: 8000
...
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: worker
labels:
app.kubernetes.io/name: worker
app.kubernetes.io/part-of: multi-project
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: worker
template:
metadata:
labels:
app.kubernetes.io/name: worker
app.kubernetes.io/part-of: multi-project
spec:
containers:
- name: worker
image: test/worker:1.0.0
imagePullPolicy: Always
env:
- name: SERVICE__BACKEND__PORT
value: '7000'
- name: SERVICE__BACKEND__HOST
value: 'backend'
- name: SERVICE__FRONTEND__PORT
value: '8000'
- name: SERVICE__FRONTEND__HOST
value: 'frontend'
volumeMounts:
- name: rabbit-rabbit
mountPath: /var/tye/bindings/rabbit-rabbit
readOnly: true
volumes:
- name: rabbit-rabbit
secret:
secretName: binding-production-rabbit-rabbit-secret
items:
- key: connectionstring
path: CONNECTIONSTRING__RABBIT
...

51
test/E2ETest/testassets/generate/single-project.yaml

@ -0,0 +1,51 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: test-project
labels:
app.kubernetes.io/name: test-project
app.kubernetes.io/part-of: single-project
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: test-project
template:
metadata:
labels:
app.kubernetes.io/name: test-project
app.kubernetes.io/part-of: single-project
spec:
containers:
- name: test-project
image: test/test-project:1.0.0
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://*:5000
ports:
- containerPort: 5001
- containerPort: 5000
...
---
kind: Service
apiVersion: v1
metadata:
name: test-project
labels:
app.kubernetes.io/name: test-project
app.kubernetes.io/part-of: single-project
spec:
selector:
app.kubernetes.io/name: test-project
type: ClusterIP
ports:
- name: test-project
protocol: TCP
port: 5001
targetPort: 5001
- name: test-project
protocol: TCP
port: 5000
targetPort: 5000
...
Loading…
Cancel
Save