Skip to content

Commit fb97eda

Browse files
radicaleerhardtdotnet-maestro[bot]danegstamitchdenny
authored
[tests] Enable Aspire.Hosting.Testing, and Aspire.Hosting tests that use docker to run on the build machine (dotnet#4500)
There is work in progress to get tests like `Aspire.Hosting.Testing`, and `Aspire.Hosting` running on helix. Meanwhile, we can have the tests run on the build machine, at least for linux which has `docker` installed, by simply removing/updating the test attributes blocking them. - Remove `[LocalOnlyFact/Theory]` attributes in favor of `[RequiresDocker]` - Remove unncessary `SkipOnHelix` attributes from `SchemaTests` - Install devcerts on Linux/build machine - Fix path construction for `nodeapp` so it correctly works on linux - Split, and fix node/npm tests to work on CI whenever the relevant tool is available. Co-authored-by: Eric Erhardt <[email protected]> Co-authored-by: dotnet-maestro[bot] <42748379+dotnet-maestro[bot]@users.noreply.github.com> Co-authored-by: David Negstad <[email protected]> Co-authored-by: Mitch Denny <[email protected]>
1 parent 80a54dc commit fb97eda

24 files changed

+209
-212
lines changed

eng/pipelines/templates/BuildAndTest.yml

+9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ steps:
3131
displayName: Build
3232

3333
- ${{ if ne(parameters.skipTests, 'true') }}:
34+
- ${{ if ne(parameters.isWindows, 'true') }}:
35+
- script: mkdir ${{ parameters.repoArtifactsPath }}/devcert-scripts &&
36+
cd ${{ parameters.repoArtifactsPath }}/devcert-scripts &&
37+
wget https://raw.githubusercontent.com/BorisWilhelms/create-dotnet-devcert/main/scripts/ubuntu-create-dotnet-devcert.sh &&
38+
wget https://raw.githubusercontent.com/BorisWilhelms/create-dotnet-devcert/main/scripts/common.sh &&
39+
chmod +x ubuntu-create-dotnet-devcert.sh &&
40+
./ubuntu-create-dotnet-devcert.sh
41+
displayName: Install devcerts
42+
3443
- script: ${{ parameters.dotnetScript }} dotnet-coverage collect
3544
--settings $(Build.SourcesDirectory)/eng/CodeCoverage.config
3645
--output ${{ parameters.repoTestResultsPath }}/NonHelix.cobertura.xml

tests/Aspire.Hosting.Tests/Utils/FileUtil.cs tests/Aspire.Components.Common.Tests/FileUtil.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33

44
using System.Diagnostics;
55

6-
namespace Aspire.Hosting.Utils;
6+
namespace Aspire.Components.Common.Tests;
77

88
internal static class FileUtil
99
{
10-
public static string FindFullPathFromPath(string command) => FindFullPathFromPath(command, Environment.GetEnvironmentVariable("PATH"), Path.PathSeparator, File.Exists);
10+
public static string? FindFullPathFromPath(string command) => FindFullPathFromPath(command, Environment.GetEnvironmentVariable("PATH"), Path.PathSeparator, File.Exists);
1111

12-
internal static string FindFullPathFromPath(string command, string? pathVariable, char pathSeparator, Func<string, bool> fileExists)
12+
internal static string? FindFullPathFromPath(string command, string? pathVariable, char pathSeparator, Func<string, bool> fileExists)
1313
{
1414
Debug.Assert(!string.IsNullOrWhiteSpace(command));
1515

@@ -28,6 +28,6 @@ internal static string FindFullPathFromPath(string command, string? pathVariable
2828
}
2929
}
3030

31-
return command;
31+
return null;
3232
}
3333
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Components.Common.Tests;
5+
6+
public static class PlatformDetection
7+
{
8+
public static bool IsRunningOnBuildMachine => Environment.GetEnvironmentVariable("BUILD_BUILDID") is not null;
9+
public static bool IsRunningOnHelix => Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT") is not null;
10+
public static bool IsRunningOnCI => IsRunningOnBuildMachine || IsRunningOnHelix;
11+
}

tests/Aspire.Components.Common.Tests/RequiresDockerAttribute.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ public class RequiresDockerAttribute : Attribute, ITraitAttribute
2222
// - Linux - Local, or CI: always assume that docker is installed
2323
public static bool IsSupported =>
2424
!OperatingSystem.IsWindows() ||
25-
(Environment.GetEnvironmentVariable("BUILD_BUILDID") is null && // NOT CI - build machine or helix
26-
Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT") is null);
25+
!PlatformDetection.IsRunningOnCI;
2726

2827
public string? Reason { get; init; }
2928
public RequiresDockerAttribute(string? reason = null)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Xunit.Sdk;
5+
6+
namespace Aspire.Components.Common.Tests;
7+
8+
[TraitDiscoverer("Aspire.Components.Common.Tests.RequiresToolsDiscoverer", "Aspire.Components.Common.Tests")]
9+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
10+
public class RequiresToolsAttribute : Attribute, ITraitAttribute
11+
{
12+
public RequiresToolsAttribute(string[] executablesOnPath)
13+
{
14+
if (executablesOnPath.Length == 0)
15+
{
16+
throw new ArgumentException("At least one executable must be provided", nameof(executablesOnPath));
17+
}
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.DotNet.XUnitExtensions;
5+
using Xunit.Abstractions;
6+
using Xunit.Sdk;
7+
8+
namespace Aspire.Components.Common.Tests;
9+
10+
public class RequiresToolsDiscoverer : ITraitDiscoverer
11+
{
12+
public IEnumerable<KeyValuePair<string, string>> GetTraits(IAttributeInfo traitAttribute)
13+
{
14+
string[] executablesOnPath = (string[])traitAttribute.GetConstructorArguments().First();
15+
if (!executablesOnPath.All(executable => FileUtil.FindFullPathFromPath(executable) is not null))
16+
{
17+
yield return new KeyValuePair<string, string>(XunitConstants.Category, "failing");
18+
}
19+
}
20+
}

tests/Aspire.Hosting.Testing.Tests/DistributedApplicationFixtureOfT.cs

-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using Microsoft.Extensions.Hosting;
55
using Xunit;
6-
using Xunit.Sdk;
76

87
namespace Aspire.Hosting.Testing.Tests;
98

@@ -12,10 +11,6 @@ public class DistributedApplicationFixture<TEntryPoint> : DistributedApplication
1211
public DistributedApplicationFixture()
1312
: base(typeof(TEntryPoint), [])
1413
{
15-
if (Environment.GetEnvironmentVariable("BUILD_BUILDID") != null)
16-
{
17-
throw new SkipException("These tests can only run in local environments.");
18-
}
1914
}
2015

2116
protected override void OnBuilderCreating(DistributedApplicationOptions applicationOptions, HostApplicationBuilderSettings hostOptions)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.Extensions.DependencyInjection;
5+
6+
namespace Aspire.Hosting.Testing.Tests;
7+
8+
internal static class DistributedApplicationHttpClientExtensionsForTests
9+
{
10+
private static readonly Lazy<IHttpClientFactory> s_httpClientFactory = new(CreateHttpClientFactoryWithResilience);
11+
public static HttpClient CreateHttpClientWithResilience(this DistributedApplication app, string resourceName, string? endpointName = default)
12+
{
13+
var baseUri = app.GetEndpoint(resourceName, endpointName);
14+
var client = s_httpClientFactory.Value.CreateClient();
15+
client.BaseAddress = baseUri;
16+
return client;
17+
}
18+
19+
private static IHttpClientFactory CreateHttpClientFactoryWithResilience()
20+
{
21+
var services = new ServiceCollection();
22+
services.AddHttpClient()
23+
.ConfigureHttpClientDefaults(b =>
24+
{
25+
b.AddStandardResilienceHandler();
26+
});
27+
28+
return services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();
29+
}
30+
}

tests/Aspire.Hosting.Testing.Tests/TestingBuilderTests.cs

+16-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Net.Http.Json;
5-
using Aspire.Hosting.Tests.Helpers;
5+
using Aspire.Components.Common.Tests;
66
using Microsoft.Extensions.Configuration;
77
using Microsoft.Extensions.DependencyInjection;
88
using Microsoft.Extensions.Hosting;
@@ -12,7 +12,8 @@ namespace Aspire.Hosting.Testing.Tests;
1212

1313
public class TestingBuilderTests
1414
{
15-
[LocalOnlyTheory]
15+
[Theory]
16+
[RequiresDocker]
1617
[InlineData(false)]
1718
[InlineData(true)]
1819
public async Task HasEndPoints(bool genericEntryPoint)
@@ -34,7 +35,8 @@ public async Task HasEndPoints(bool genericEntryPoint)
3435
Assert.True(pgConnectionString.Length > 0);
3536
}
3637

37-
[LocalOnlyTheory]
38+
[Theory]
39+
[RequiresDocker]
3840
[InlineData(false)]
3941
[InlineData(true)]
4042
public async Task CanGetResources(bool genericEntryPoint)
@@ -51,7 +53,8 @@ public async Task CanGetResources(bool genericEntryPoint)
5153
Assert.Contains(appModel.GetProjectResources(), p => p.Name == "myworker1");
5254
}
5355

54-
[LocalOnlyTheory]
56+
[Theory]
57+
[RequiresDocker]
5558
[InlineData(false)]
5659
[InlineData(true)]
5760
public async Task HttpClientGetTest(bool genericEntryPoint)
@@ -62,13 +65,14 @@ public async Task HttpClientGetTest(bool genericEntryPoint)
6265
await using var app = await appHost.BuildAsync();
6366
await app.StartAsync();
6467

65-
var httpClient = app.CreateHttpClient("mywebapp1");
68+
var httpClient = app.CreateHttpClientWithResilience("mywebapp1");
6669
var result1 = await httpClient.GetFromJsonAsync<WeatherForecast[]>("/weatherforecast");
6770
Assert.NotNull(result1);
6871
Assert.True(result1.Length > 0);
6972
}
7073

71-
[LocalOnlyTheory]
74+
[Theory]
75+
[RequiresDocker]
7276
[InlineData(false)]
7377
[InlineData(true)]
7478
public async Task GetHttpClientBeforeStart(bool genericEntryPoint)
@@ -80,7 +84,8 @@ public async Task GetHttpClientBeforeStart(bool genericEntryPoint)
8084
Assert.Throws<InvalidOperationException>(() => app.CreateHttpClient("mywebapp1"));
8185
}
8286

83-
[LocalOnlyTheory]
87+
[Theory]
88+
[RequiresDocker]
8489
[InlineData(false)]
8590
[InlineData(true)]
8691
public async Task SetsCorrectContentRoot(bool genericEntryPoint)
@@ -94,7 +99,8 @@ public async Task SetsCorrectContentRoot(bool genericEntryPoint)
9499
Assert.Contains("TestingAppHost1", hostEnvironment.ContentRootPath);
95100
}
96101

97-
[LocalOnlyTheory]
102+
[Theory]
103+
[RequiresDocker]
98104
[InlineData(false)]
99105
[InlineData(true)]
100106
public async Task SelectsFirstLaunchProfile(bool genericEntryPoint)
@@ -116,7 +122,8 @@ public async Task SelectsFirstLaunchProfile(bool genericEntryPoint)
116122
}
117123

118124
// Tests that DistributedApplicationTestingBuilder throws exceptions at the right times when the app crashes.
119-
[LocalOnlyTheory]
125+
[Theory]
126+
[RequiresDocker]
120127
[InlineData(true, "before-build")]
121128
[InlineData(true, "after-build")]
122129
[InlineData(true, "after-start")]

tests/Aspire.Hosting.Testing.Tests/TestingFactoryCrashTests.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using Aspire.Hosting.Tests.Helpers;
4+
using Aspire.Components.Common.Tests;
55
using Xunit;
66

77
namespace Aspire.Hosting.Testing.Tests;
88

99
// Tests that DistributedApplicationTestingBuilder throws exceptions at the right times when the app crashes.
1010
public class TestingFactoryCrashTests
1111
{
12-
[LocalOnlyTheory]
12+
[Theory]
13+
[RequiresDocker]
1314
[InlineData("before-build")]
1415
[InlineData("after-build")]
1516
[InlineData("after-start")]

tests/Aspire.Hosting.Testing.Tests/TestingFactoryTests.cs

+13-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Net.Http.Json;
5-
using Aspire.Hosting.Tests.Helpers;
5+
using Aspire.Components.Common.Tests;
66
using Microsoft.Extensions.Configuration;
77
using Microsoft.Extensions.DependencyInjection;
88
using Microsoft.Extensions.Hosting;
@@ -14,7 +14,8 @@ public class TestingFactoryTests(DistributedApplicationFixture<Projects.TestingA
1414
{
1515
private readonly DistributedApplication _app = fixture.Application;
1616

17-
[LocalOnlyFact]
17+
[Fact]
18+
[RequiresDocker]
1819
public async Task HasEndPoints()
1920
{
2021
// Get an endpoint from a resource
@@ -28,39 +29,43 @@ public async Task HasEndPoints()
2829
Assert.True(pgConnectionString.Length > 0);
2930
}
3031

31-
[LocalOnlyFact]
32+
[Fact]
33+
[RequiresDocker]
3234
public void CanGetResources()
3335
{
3436
var appModel = _app.Services.GetRequiredService<DistributedApplicationModel>();
3537
Assert.Contains(appModel.GetContainerResources(), c => c.Name == "redis1");
3638
Assert.Contains(appModel.GetProjectResources(), p => p.Name == "myworker1");
3739
}
3840

39-
[LocalOnlyFact]
41+
[Fact]
42+
[RequiresDocker]
4043
public async Task HttpClientGetTest()
4144
{
42-
var httpClient = _app.CreateHttpClient("mywebapp1");
45+
var httpClient = _app.CreateHttpClientWithResilience("mywebapp1");
4346
var result1 = await httpClient.GetFromJsonAsync<WeatherForecast[]>("/weatherforecast");
4447
Assert.NotNull(result1);
4548
Assert.True(result1.Length > 0);
4649
}
4750

48-
[LocalOnlyFact]
51+
[Fact]
52+
[RequiresDocker]
4953
public void SetsCorrectContentRoot()
5054
{
5155
var appModel = _app.Services.GetRequiredService<IHostEnvironment>();
5256
Assert.Contains("TestingAppHost1", appModel.ContentRootPath);
5357
}
5458

55-
[LocalOnlyFact]
59+
[Fact]
60+
[RequiresDocker]
5661
public async Task SelectsFirstLaunchProfile()
5762
{
5863
var config = _app.Services.GetRequiredService<IConfiguration>();
5964
var profileName = config["AppHost:DefaultLaunchProfileName"];
6065
Assert.Equal("https", profileName);
6166

6267
// Explicitly get the HTTPS endpoint - this is only available on the "https" launch profile.
63-
var httpClient = _app.CreateHttpClient("mywebapp1", "https");
68+
var httpClient = _app.CreateHttpClientWithResilience("mywebapp1", "https");
6469
var result = await httpClient.GetFromJsonAsync<WeatherForecast[]>("/weatherforecast");
6570
Assert.NotNull(result);
6671
Assert.True(result.Length > 0);

tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
<ProjectReference Include="..\..\src\Components\Aspire.Npgsql\Aspire.Npgsql.csproj" IsAspireProjectResource="false" />
4848
<ProjectReference Include="..\..\src\Components\Aspire.Oracle.EntityFrameworkCore\Aspire.Oracle.EntityFrameworkCore.csproj" IsAspireProjectResource="false" />
4949

50+
<ProjectReference Include="..\Aspire.Components.Common.Tests\Aspire.Components.Common.Tests.csproj" />
51+
5052
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
5153
<PackageReference Include="Newtonsoft.Json.Schema" />
5254

@@ -63,6 +65,8 @@
6365
<Compile Include="$(RepoRoot)src\Aspire.Hosting.Valkey\ValkeyContainerImageTags.cs" />
6466
<Compile Include="$(RepoRoot)src\Aspire.Hosting.Azure.EventHubs\EventHubsEmulatorContainerImageTags.cs" />
6567

68+
<Compile Include="$(RepoRoot)tests\Aspire.Hosting.Testing.Tests\DistributedApplicationHttpClientExtensionsForTests.cs" />
69+
6670
<Content Include="$(RepoRoot)src\Schema\aspire-8.0.json" Link="Schema\aspire-8.0.json">
6771
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
6872
</Content>

0 commit comments

Comments
 (0)