Browse Source

Fix tests

davidfowl/dependencies
Ryan Nowak 7 years ago
parent
commit
adf05b230e
  1. 2
      src/Microsoft.Tye.Core/Microsoft.Tye.Core.csproj
  2. 136
      src/Microsoft.Tye.Core/ProjectReader.cs
  3. 4
      src/Microsoft.Tye.Hosting/DockerRunner.cs
  4. 1
      src/Microsoft.Tye.Hosting/Microsoft.Tye.Hosting.csproj
  5. 22
      src/shared/TempDirectory.cs
  6. 22
      src/tye/ConfigModel/ConfigFactory.cs
  7. 10
      test/E2ETest/E2ETest.csproj
  8. 2
      test/E2ETest/Infrastructure/ConditionalFactAttribute.cs
  9. 23
      test/E2ETest/Infrastructure/SkipOnLinuxAttribute.cs
  10. 3
      test/E2ETest/TestOutputLogEventSink.cs
  11. 28
      test/E2ETest/TyeRunTests.cs
  12. 4
      test/E2ETest/testassets/generate/frontend-backend.yaml
  13. 4
      test/E2ETest/testassets/generate/multi-project.yaml
  14. 2
      test/E2ETest/testassets/generate/single-project-noregistry.yaml
  15. 2
      test/E2ETest/testassets/generate/single-project.yaml

2
src/Microsoft.Tye.Core/Microsoft.Tye.Core.csproj

@ -16,7 +16,7 @@
The Microsoft.Build.Locator package takes care of dynamically loading these assemblies
at runtime. We don't need/want to ship them, just to have them as references.
-->
<PackageReference Include="Microsoft.Build" Version="16.5.0" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build" Version="16.3.0" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
<!-- Hoisted to avoid a conflict with Microsoft.Build -->
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />

136
src/Microsoft.Tye.Core/ProjectReader.cs

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
@ -10,7 +11,9 @@ using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.Build.Construction;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Locator;
@ -21,17 +24,46 @@ namespace Microsoft.Tye
{
public static class ProjectReader
{
private static object @lock = new object();
private static bool registered;
public static Task ReadProjectDetailsAsync(OutputContext output, FileInfo projectFile, Project project)
public static IEnumerable<FileInfo> EnumerateProjects(FileInfo solutionFile)
{
if (output is null)
EnsureMSBuildRegistered(null, solutionFile);
return EnumerateProjectsCore(solutionFile);
}
// Do not load MSBuild types before using EnsureMSBuildRegistered.
[MethodImpl(MethodImplOptions.NoInlining)]
private static IEnumerable<FileInfo> EnumerateProjectsCore(FileInfo solutionFile)
{
var solution = SolutionFile.Parse(solutionFile.FullName);
foreach (var project in solution.ProjectsInOrder)
{
throw new ArgumentNullException(nameof(output));
if (project.ProjectType != SolutionProjectType.KnownToBeMSBuildFormat)
{
continue;
}
var extension = Path.GetExtension(project.AbsolutePath).ToLower();
switch (extension)
{
case ".csproj":
case ".fsproj":
break;
default:
continue;
}
yield return new FileInfo(project.AbsolutePath.Replace('\\', '/'));
}
}
if (projectFile is null)
public static Task ReadProjectDetailsAsync(OutputContext output, FileInfo projectFile, Project project)
{
if (output is null)
{
throw new ArgumentNullException(nameof(projectFile));
throw new ArgumentNullException(nameof(output));
}
if (project is null)
@ -39,54 +71,63 @@ namespace Microsoft.Tye
throw new ArgumentNullException(nameof(project));
}
EnsureMSBuildRegistered(projectFile);
EnsureMSBuildRegistered(output, projectFile);
using (var step = output.BeginStep("Reading Project Details..."))
{
EvaluateProject(output, projectFile, project);
EvaluateProject(output, projectFile, project);
if (!SemVersion.TryParse(project.Version, out var version))
{
output.WriteInfoLine($"No version or invalid version '{project.Version}' found, using default.");
version = new SemVersion(0, 1, 0);
project.Version = version.ToString();
}
step.MarkComplete();
if (!SemVersion.TryParse(project.Version, out var version))
{
output.WriteInfoLine($"No version or invalid version '{project.Version}' found, using default.");
version = new SemVersion(0, 1, 0);
project.Version = version.ToString();
}
return Task.CompletedTask;
}
private static void EnsureMSBuildRegistered(FileInfo projectFile)
private static void EnsureMSBuildRegistered(OutputContext? output, FileInfo projectFile)
{
if (!registered)
{
// It says VisualStudio - but we'll just use .NET SDK
var instances = MSBuildLocator.QueryVisualStudioInstances(new VisualStudioInstanceQueryOptions()
lock (@lock)
{
DiscoveryTypes = DiscoveryType.DotNetSdk,
// Using the project as the working directory. We're making the assumption that
// all of the projects want to use the same SDK version. This library is going
// load a single version of the SDK's assemblies into our process, so we can't
// use supprt SDKs at once without getting really tricky.
//
// The .NET SDK-based discovery uses `dotnet --info` and returns the SDK
// in use for the directory.
//
// https://github.com/microsoft/MSBuildLocator/blob/master/src/MSBuildLocator/MSBuildLocator.cs#L320
WorkingDirectory = projectFile.DirectoryName,
});
output?.WriteDebugLine("Locating .NET SDK...");
var instance = instances.SingleOrDefault();
if (instance == null)
{
throw new CommandException("Failed to find dotnet. Make sure the .NET SDK is installed and on the PATH.");
}
// It says VisualStudio - but we'll just use .NET SDK
var instances = MSBuildLocator.QueryVisualStudioInstances(new VisualStudioInstanceQueryOptions()
{
DiscoveryTypes = DiscoveryType.DotNetSdk,
// Using the project as the working directory. We're making the assumption that
// all of the projects want to use the same SDK version. This library is going
// load a single version of the SDK's assemblies into our process, so we can't
// use supprt SDKs at once without getting really tricky.
//
// The .NET SDK-based discovery uses `dotnet --info` and returns the SDK
// in use for the directory.
//
// https://github.com/microsoft/MSBuildLocator/blob/master/src/MSBuildLocator/MSBuildLocator.cs#L320
WorkingDirectory = projectFile.DirectoryName,
});
var instance = instances.SingleOrDefault();
if (instance == null)
{
throw new CommandException("Failed to find dotnet. Make sure the .NET SDK is installed and on the PATH.");
}
MSBuildLocator.RegisterInstance(instance);
registered = true;
output?.WriteDebugLine("Found .NET SDK at: " + instance.MSBuildPath);
try
{
MSBuildLocator.RegisterInstance(instance);
output?.WriteDebug("Registered .NET SDK.");
}
finally
{
registered = true;
}
}
}
}
@ -96,16 +137,25 @@ namespace Microsoft.Tye
{
var sw = Stopwatch.StartNew();
// We need to isolate projects from each other for testing. MSBuild does not support
// loading the same project twice in the same collection.
var projectCollection = new ProjectCollection();
ProjectInstance projectInstance;
try
{
output.WriteDebugLine($"Loading project '{projectFile.FullName}'.");
projectInstance = ProjectInstance.FromFile(projectFile.FullName, new ProjectOptions());
var msbuildProject = Microsoft.Build.Evaluation.Project.FromFile(projectFile.FullName, new ProjectOptions()
{
ProjectCollection = projectCollection,
});
projectInstance = msbuildProject.CreateProjectInstance();
output.WriteDebugLine($"Loaded project '{projectFile.FullName}'.");
}
catch
catch (Exception ex)
{
throw new CommandException($"Failed to load project: '{projectFile.FullName}'.");
throw new CommandException($"Failed to load project: '{projectFile.FullName}'.", ex);
}
// Currently we only log at debug level.

4
src/Microsoft.Tye.Hosting/DockerRunner.cs

@ -129,6 +129,10 @@ namespace Microsoft.Tye.Hosting
environment["PORT"] = string.Join(";", ports.Select(p => $"{p.InternalPort ?? p.Port}"));
}
// See: https://github.com/docker/for-linux/issues/264
//
// The way we do proxying here doesn't really work for multi-container scenarios on linux
// without some more setup.
application.PopulateEnvironment(service, (key, value) => environment[key] = value, "host.docker.internal");
environment["APP_INSTANCE"] = replica;

1
src/Microsoft.Tye.Hosting/Microsoft.Tye.Hosting.csproj

@ -19,7 +19,6 @@
<ItemGroup>
<PackageReference Include="Bedrock.Framework" Version="0.1.38-alpha.gd25d5b37ad" />
<PackageReference Include="FeatherHttp" Version="0.1.42-alpha.gf06a8747e7" />
<PackageReference Include="Microsoft.Build" Version="16.5.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />

22
src/shared/TempDirectory.cs

@ -4,16 +4,30 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Microsoft.Tye
{
internal class TempDirectory : IDisposable
{
public static TempDirectory Create()
public static TempDirectory Create(bool preferUserDirectoryOnMacOS = false)
{
var directoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var directoryInfo = Directory.CreateDirectory(directoryPath);
return new TempDirectory(directoryPath, directoryInfo);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && preferUserDirectoryOnMacOS)
{
var baseDirectory = Environment.GetEnvironmentVariable("HOME") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
baseDirectory = Path.Combine(baseDirectory, ".tye");
Directory.CreateDirectory(baseDirectory);
var directoryPath = Path.Combine(baseDirectory, Path.GetRandomFileName());
var directoryInfo = Directory.CreateDirectory(directoryPath);
return new TempDirectory(directoryPath, directoryInfo);
}
else
{
var directoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var directoryInfo = Directory.CreateDirectory(directoryPath);
return new TempDirectory(directoryPath, directoryInfo);
}
}
private TempDirectory(string directoryPath, DirectoryInfo directoryInfo)

22
src/tye/ConfigModel/ConfigFactory.cs

@ -57,25 +57,11 @@ namespace Microsoft.Tye.ConfigModel
Source = file,
};
var solution = SolutionFile.Parse(file.FullName);
foreach (var project in solution.ProjectsInOrder)
// BE CAREFUL modifying this code. Avoid proliferating MSBuild types
// throughout the code, because we load them dynamically.
foreach (var projectFile in ProjectReader.EnumerateProjects(file))
{
if (project.ProjectType != SolutionProjectType.KnownToBeMSBuildFormat)
{
continue;
}
var extension = Path.GetExtension(project.AbsolutePath).ToLower();
switch (extension)
{
case ".csproj":
case ".fsproj":
break;
default:
continue;
}
var description = CreateService(new FileInfo(project.AbsolutePath.Replace('\\', '/')));
var description = CreateService(projectFile);
if (description != null)
{
application.Services.Add(description);

10
test/E2ETest/E2ETest.csproj

@ -13,6 +13,16 @@
<Compile Include="..\..\src\shared\TempDirectory.cs" Link="TempDirectory.cs" />
</ItemGroup>
<ItemGroup>
<!--
The Microsoft.Build.Locator package takes care of dynamically loading these assemblies
at runtime. We don't need/want to ship them, just to have them as references.
-->
<PackageReference Include="Microsoft.Build" Version="16.3.0" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Framework" Version="16.3.0" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Tye.Hosting\Microsoft.Tye.Hosting.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Tye.Core\Microsoft.Tye.Core.csproj" />

2
test/E2ETest/Infrastructure/ConditionalFactAttribute.cs

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

23
test/E2ETest/Infrastructure/SkipOnLinuxAttribute.cs

@ -0,0 +1,23 @@
// 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.Runtime.InteropServices;
namespace E2ETest
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
internal class SkipOnLinuxAttribute : Attribute, ITestCondition
{
public SkipOnLinuxAttribute()
{
IsMet = !RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
SkipReason = "This is linux. Here's a free bus-ticket to skipsville.";
}
public bool IsMet { get; }
public string SkipReason { get; set; }
}
}

3
test/E2ETest/TestOutputLogEventSink.cs

@ -32,7 +32,8 @@ namespace E2ETest
public void Write(string value)
{
output.WriteLine(value);
// our usage of IConsole includes newlines, so strip them out.
output.WriteLine(value.TrimEnd('\r', '\n'));
}
}
}

28
test/E2ETest/TyeRunTests.cs

@ -137,10 +137,11 @@ namespace E2ETest
[ConditionalFact]
[SkipIfDockerNotRunning]
[SkipOnLinux]
public async Task FrontendBackendRunTestWithDocker()
{
var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", "frontend-backend"));
using var tempDirectory = TempDirectory.Create();
using var tempDirectory = TempDirectory.Create(preferUserDirectoryOnMacOS: true);
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath);
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml"));
@ -165,8 +166,8 @@ namespace E2ETest
var dashboardUri = new Uri(host.DashboardWebApplication!.Addresses.First());
await CheckServiceIsUp(host.Application, client, "backend", dashboardUri);
await CheckServiceIsUp(host.Application, client, "frontend", dashboardUri);
await CheckServiceIsUp(host.Application, client, "backend", dashboardUri, timeout: TimeSpan.FromSeconds(60));
await CheckServiceIsUp(host.Application, client, "frontend", dashboardUri, timeout: TimeSpan.FromSeconds(60));
}
finally
{
@ -174,7 +175,7 @@ namespace E2ETest
}
}
private async Task CheckServiceIsUp(Microsoft.Tye.Hosting.Model.Application application, HttpClient client, string serviceName, Uri dashboardUri)
private async Task CheckServiceIsUp(Microsoft.Tye.Hosting.Model.Application application, HttpClient client, string serviceName, Uri dashboardUri, TimeSpan? timeout = default)
{
// make sure backend is up before frontend
var dashboardString = await client.GetStringAsync($"{dashboardUri}api/v1/services/{serviceName}");
@ -183,11 +184,28 @@ namespace E2ETest
var binding = service.Description!.Bindings.Where(b => b.Protocol == "http").Single();
var uriBackendProcess = new Uri($"{binding.Protocol}://localhost:{binding.Port}");
var startTime = DateTime.UtcNow;
try
{
// Wait up until the timeout to see if we can access the service.
// For instance if we have to pull a base-image it can take a while.
while (timeout.HasValue && startTime + timeout.Value > DateTime.UtcNow)
{
try
{
await client.GetAsync(uriBackendProcess);
break;
}
catch (HttpRequestException)
{
await Task.Delay(TimeSpan.FromSeconds(3));
}
}
var appResponse = await client.GetAsync(uriBackendProcess);
Assert.Equal(HttpStatusCode.OK, appResponse.StatusCode);
var content = await appResponse.Content.ReadAsStringAsync();
output.WriteLine(content);
Assert.Equal(HttpStatusCode.OK, appResponse.StatusCode);
if (serviceName == "frontend")
{
Assert.Matches("Frontend Listening IP: (.+)\n", content);

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

@ -23,6 +23,8 @@ spec:
env:
- name: ASPNETCORE_URLS
value: 'http://*'
- name: PORT
value: '80'
- name: SERVICE__FRONTEND__PROTOCOL
value: 'http'
- name: SERVICE__FRONTEND__PORT
@ -76,6 +78,8 @@ spec:
env:
- name: ASPNETCORE_URLS
value: 'http://*'
- name: PORT
value: '80'
- name: SERVICE__BACKEND__PROTOCOL
value: 'http'
- name: SERVICE__BACKEND__PORT

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

@ -23,6 +23,8 @@ spec:
env:
- name: ASPNETCORE_URLS
value: 'http://*:7000'
- name: PORT
value: '7000'
- name: SERVICE__FRONTEND__PROTOCOL
value: 'http'
- name: SERVICE__FRONTEND__PORT
@ -87,6 +89,8 @@ spec:
env:
- name: ASPNETCORE_URLS
value: 'http://*:8000'
- name: PORT
value: '8000'
- name: SERVICE__BACKEND__PROTOCOL
value: 'http'
- name: SERVICE__BACKEND__PORT

2
test/E2ETest/testassets/generate/single-project-noregistry.yaml

@ -23,6 +23,8 @@ spec:
env:
- name: ASPNETCORE_URLS
value: 'http://*'
- name: PORT
value: '80'
ports:
- containerPort: 80
...

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

@ -23,6 +23,8 @@ spec:
env:
- name: ASPNETCORE_URLS
value: 'http://*'
- name: PORT
value: '80'
ports:
- containerPort: 80
...

Loading…
Cancel
Save