Tye is a tool that makes developing, testing, and deploying microservices and distributed applications easier. Project Tye includes a local orchestrator to make developing microservices easier and the ability to deploy microservices to Kubernetes with min
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

198 lines
7.3 KiB

// 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 System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Tye.Hosting;
using Microsoft.Tye.Hosting.Model;
using Xunit;
using Microsoft.Tye;
using System.Diagnostics;
namespace Test.Infrastructure
{
public static class TestHelpers
{
private static readonly TimeSpan WaitForServicesTimeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(20);
// 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.");
}
public static DirectoryInfo GetTestAssetsDirectory()
{
return new DirectoryInfo(Path.Combine(
TestHelpers.GetSolutionRootDirectory("tye"),
"test",
"E2ETest",
"testassets"));
}
public static DirectoryInfo GetTestProjectDirectory(string projectName)
{
var directory = new DirectoryInfo(Path.Combine(
TestHelpers.GetSolutionRootDirectory("tye"),
"test",
"E2ETest",
"testassets",
"projects",
projectName));
Assert.True(directory.Exists, $"Project {projectName} not found.");
return directory;
}
public static DirectoryInfo GetSampleProjectDirectory(string projectName)
{
var directory = new DirectoryInfo(Path.Combine(
TestHelpers.GetSolutionRootDirectory("tye"),
"samples",
projectName));
Assert.True(directory.Exists, $"Project {projectName} not found.");
return directory;
}
public static TempDirectory CopyTestProjectDirectory(string projectName)
{
var temp = TempDirectory.Create(preferUserDirectoryOnMacOS: true);
DirectoryCopy.Copy(GetTestProjectDirectory(projectName).FullName, temp.DirectoryPath);
// We need to hijack any P2P references to Tye libraries.
// Test projects must use $(TyeLibrariesPath) to find their references.
var libraryPath = Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "src");
if (!libraryPath.EndsWith(Path.DirectorySeparatorChar))
{
libraryPath += Path.DirectorySeparatorChar;
}
File.WriteAllText(
Path.Combine(temp.DirectoryPath, "Directory.Build.props"),
$@"
<Project>
<PropertyGroup>
<TyeLibrariesPath>{libraryPath}</TyeLibrariesPath>
</PropertyGroup>
</Project>");
return temp;
}
public static async Task StartHostAndWaitForReplicasToStart(TyeHost host)
{
var startedTask = new TaskCompletionSource<bool>();
var alreadyStarted = 0;
var totalReplicas = host.Application.Services.Sum(s => s.Value.Description.Replicas);
void OnReplicaChange(ReplicaEvent ev)
{
if (ev.State == ReplicaState.Started)
{
Interlocked.Increment(ref alreadyStarted);
}
else if (ev.State == ReplicaState.Stopped)
{
Interlocked.Decrement(ref alreadyStarted);
}
if (alreadyStarted == totalReplicas)
{
startedTask.TrySetResult(true);
}
}
var servicesStateObserver = host.Application.Services.Select(srv => srv.Value.ReplicaEvents.Subscribe(OnReplicaChange)).ToList();
await host.StartAsync();
using var cancellation = new CancellationTokenSource(WaitForServicesTimeout);
try
{
await using (cancellation.Token.Register(() => startedTask.TrySetCanceled()))
{
await startedTask.Task;
}
}
finally
{
foreach (var observer in servicesStateObserver)
{
observer.Dispose();
}
}
}
public static async Task PurgeHostAndWaitForGivenReplicasToStop(TyeHost host, string[] replicas)
{
static async Task Purge(TyeHost host)
{
var logger = host.DashboardWebApplication!.Logger;
var replicaRegistry = new ReplicaRegistry(host.Application.ContextDirectory, logger);
var processRunner = new ProcessRunner(logger, replicaRegistry, new ProcessRunnerOptions());
var dockerRunner = new DockerRunner(logger, replicaRegistry);
await processRunner.StartAsync(new Application(new FileInfo(host.Application.Source), new Dictionary<string, Service>()));
await dockerRunner.StartAsync(new Application(new FileInfo(host.Application.Source), new Dictionary<string, Service>()));
}
var stoppedTask = new TaskCompletionSource<bool>();
var remaining = replicas.Length;
void OnReplicaChange(ReplicaEvent ev)
{
if (replicas.Contains(ev.Replica.Name) && ev.State == ReplicaState.Stopped)
{
Interlocked.Decrement(ref remaining);
}
if (remaining == 0)
{
stoppedTask.TrySetResult(true);
}
}
var servicesStateObserver = host.Application.Services.Select(srv => srv.Value.ReplicaEvents.Subscribe(OnReplicaChange)).ToList();
// We purge existing replicas by restarting the host which will initiate the purging process
await Purge(host);
using var cancellation = new CancellationTokenSource(WaitForServicesTimeout);
try
{
await using (cancellation.Token.Register(() => stoppedTask.TrySetCanceled()))
{
await stoppedTask.Task;
}
}
finally
{
foreach (var observer in servicesStateObserver)
{
observer.Dispose();
}
}
}
}
}