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
{
internal class DockerDetector
public class 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
{
internal static class NameSanitizer
public static class NameSanitizer
{
// 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
{
internal class ProcessResult
public class ProcessResult
{
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
{
internal static class ProcessUtil
public static class ProcessUtil
{
public static async Task<ProcessResult> RunAsync(
string filename,

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

@ -25,11 +25,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\shared\DockerDetector.cs" Link="DockerDetector.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\TempFile.cs" Link="TempFile.cs" />
</ItemGroup>

4
src/Tye.Hosting/AggregateApplicationProcessor.cs

@ -18,7 +18,7 @@ namespace Tye.Hosting
_applicationProcessors = applicationProcessors;
}
public async Task StartAsync(Application application)
public async Task StartAsync(Tye.Hosting.Model.Application application)
{
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
foreach (var processor in _applicationProcessors.Reverse())

10
src/Tye.Hosting/DockerRunner.cs

@ -21,7 +21,7 @@ namespace Tye.Hosting
_logger = logger;
}
public Task StartAsync(Application application)
public Task StartAsync(Tye.Hosting.Model.Application application)
{
var tasks = new Task[application.Services.Count];
var index = 0;
@ -33,7 +33,7 @@ namespace Tye.Hosting
return Task.WhenAll(tasks);
}
public Task StopAsync(Application application)
public Task StopAsync(Tye.Hosting.Model.Application application)
{
var services = application.Services;
@ -48,7 +48,7 @@ namespace Tye.Hosting
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)
{
@ -224,7 +224,7 @@ namespace Tye.Hosting
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)
{
@ -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)
{

4
src/Tye.Hosting/EventPipeDiagnosticsRunner.cs

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

4
src/Tye.Hosting/IApplicationProcessor.cs

@ -9,8 +9,8 @@ namespace Tye.Hosting
{
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;
}
public Task StartAsync(Application application)
public Task StartAsync(Tye.Hosting.Model.Application application)
{
var tasks = new Task[application.Services.Count];
var index = 0;
@ -49,12 +49,12 @@ namespace Tye.Hosting
return Task.WhenAll(tasks);
}
public Task StopAsync(Application application)
public Task StopAsync(Tye.Hosting.Model.Application application)
{
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 serviceName = serviceDescription.Name;
@ -263,9 +263,9 @@ namespace Tye.Hosting
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)
{

4
src/Tye.Hosting/ProxyService.cs

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

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

@ -13,13 +13,6 @@
<ItemGroup>
<EmbeddedResource Include="wwwroot\**" />
</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>
<PackageReference Include="Bedrock.Framework" Version="0.1.38-alpha.gd25d5b37ad" />
@ -36,6 +29,7 @@
<ItemGroup>
<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" />
</ItemGroup>

10
src/Tye.Hosting/TyeDashboardApi.cs

@ -55,7 +55,7 @@ namespace Tye.Hosting
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";
@ -66,7 +66,7 @@ namespace Tye.Hosting
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"];
context.Response.ContentType = "application/json";
@ -88,7 +88,7 @@ namespace Tye.Hosting
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"];
context.Response.ContentType = "application/json";
@ -110,7 +110,7 @@ namespace Tye.Hosting
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();
foreach (var s in app.Services.OrderBy(s => s.Key))
@ -138,7 +138,7 @@ namespace Tye.Hosting
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();

6
src/Tye.Hosting/TyeHost.cs

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

8
src/shared/TempDirectory.cs

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

41
src/tye/Program.GenerateCommand.cs

@ -35,49 +35,10 @@ namespace Tye
throw new CommandException("No project or solution file was found.");
}
var application = ConfigFactory.FromFile(path);
return ExecuteGenerateAsync(new OutputContext(console, verbosity), application, environment: "production", interactive);
return GenerateHost.GenerateAsync(console, path, verbosity, interactive);
});
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>
<ItemGroup>
<Compile Include="..\shared\DockerDetector.cs" Link="DockerDetector.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\TempFile.cs" Link="TempFile.cs" />
</ItemGroup>

3
test/E2ETest/E2ETest.csproj

@ -23,6 +23,9 @@
</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\multi-project.yaml" />
</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 Xunit.Abstractions;
namespace E2ETest
{
public class TestOutputLogEventSink : ILogEventSink
public class TestOutputLogEventSink : ILogEventSink, IConsole, IStandardStreamWriter
{
private readonly ITestOutputHelper output;
public TestOutputLogEventSink(ITestOutputHelper 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)
{
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.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Text.Json;
using Microsoft.Build.Construction;
using Tye;
using Tye.ConfigModel;
using Xunit;
using Xunit.Abstractions;
@ -31,6 +27,8 @@ namespace E2ETest
using var tempDirectory = TempDirectory.Create();
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 (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