Skip to content

Commit ba7e7f2

Browse files
authored
Improve Native AOT acceptance tests to catch trim warnings (#7153) (#7451)
2 parents ff7482f + c140cb8 commit ba7e7f2

5 files changed

Lines changed: 259 additions & 11 deletions

File tree

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/NativeAotTests.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ namespace MSTest.Acceptance.IntegrationTests;
99
[TestClass]
1010
public class NativeAotTests : AcceptanceTestBase<NopAssetFixture>
1111
{
12+
// Source code for a project that validates MSTest supporting Native AOT.
13+
// Because MSTest is built on top of Microsoft.Testing.Platform, this also exercises
14+
// additional MTP code paths beyond what the MTP-only NativeAOT test covers.
1215
private const string SourceCode = """
13-
#file NativeAotTests.csproj
16+
#file MSTestNativeAotTests.csproj
1417
<Project Sdk="Microsoft.NET.Sdk">
1518
<PropertyGroup>
1619
<TargetFramework>$TargetFramework$</TargetFramework>
@@ -20,6 +23,8 @@ public class NativeAotTests : AcceptanceTestBase<NopAssetFixture>
2023
<UseAppHost>true</UseAppHost>
2124
<LangVersion>preview</LangVersion>
2225
<PublishAot>true</PublishAot>
26+
<!-- Show individual trim/AOT warnings instead of a single IL2104 per assembly -->
27+
<TrimmerSingleWarn>false</TrimmerSingleWarn>
2328
</PropertyGroup>
2429
<ItemGroup>
2530
<PackageReference Include="Microsoft.Testing.Platform" Version="$MicrosoftTestingPlatformVersion$" />
@@ -41,7 +46,7 @@ public class NativeAotTests : AcceptanceTestBase<NopAssetFixture>
4146
using Microsoft.Testing.Platform.Extensions.Messages;
4247
using Microsoft.Testing.Platform.Extensions.TestFramework;
4348
44-
using NativeAotTests;
49+
using MSTestNativeAotTests;
4550
4651
ITestApplicationBuilder builder = await TestApplication.CreateBuilderAsync(args);
4752
builder.AddTestFramework(new SourceGeneratedTestNodesBuilder());
@@ -84,13 +89,14 @@ public void TestMethod3(int a, int b)
8489
[TestMethod]
8590
// The hosted AzDO agents for Mac OS don't have the required tooling for us to test Native AOT.
8691
[OSCondition(ConditionMode.Exclude, OperatingSystems.OSX)]
87-
public async Task NativeAotTests_WillRunWithExitCodeZero()
92+
[DynamicData(nameof(TargetFrameworks.NetForDynamicData), typeof(TargetFrameworks))]
93+
public async Task NativeAotTests_WillRunWithExitCodeZero(string tfm)
8894
{
8995
using TestAsset generator = await TestAsset.GenerateAssetAsync(
90-
"NativeAotTests",
96+
$"MSTestNativeAotTests_{tfm}",
9197
SourceCode
9298
.PatchCodeWithReplace("$MicrosoftTestingPlatformVersion$", MicrosoftTestingPlatformVersion)
93-
.PatchCodeWithReplace("$TargetFramework$", TargetFrameworks.NetCurrent)
99+
.PatchCodeWithReplace("$TargetFramework$", tfm)
94100
.PatchCodeWithReplace("$MSTestVersion$", MSTestVersion)
95101
.PatchCodeWithReplace("$MSTestEngineVersion$", MSTestEngineVersion),
96102
addPublicFeeds: true);
@@ -101,13 +107,13 @@ await DotnetCli.RunAsync(
101107
retryCount: 0,
102108
cancellationToken: TestContext.CancellationToken);
103109
DotnetMuxerResult compilationResult = await DotnetCli.RunAsync(
104-
$"publish {generator.TargetAssetPath} -r {RID}",
110+
$"publish {generator.TargetAssetPath} -r {RID} -f {tfm}",
105111
AcceptanceFixture.NuGetGlobalPackagesFolder.Path,
106112
retryCount: 0,
107113
cancellationToken: TestContext.CancellationToken);
108114
compilationResult.AssertOutputContains("Generating native code");
109115

110-
var testHost = TestHost.LocateFrom(generator.TargetAssetPath, "NativeAotTests", TargetFrameworks.NetCurrent, RID, Verb.publish);
116+
var testHost = TestHost.LocateFrom(generator.TargetAssetPath, "MSTestNativeAotTests", tfm, RID, Verb.publish);
111117

112118
TestHostResult result = await testHost.ExecuteAsync(cancellationToken: TestContext.CancellationToken);
113119
result.AssertOutputContains($"MSTest.Engine v{MSTestEngineVersion}");

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SdkTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,25 +291,25 @@ public async Task Invalid_TestingProfile_Name_Should_Fail(string multiTfm, Build
291291
}
292292

293293
[TestMethod]
294-
[OSCondition(OperatingSystems.Windows)]
295-
public async Task NativeAot_Smoke_Test_Windows()
294+
[OSCondition(ConditionMode.Exclude, OperatingSystems.OSX)]
295+
public async Task NativeAot_Smoke_Test()
296296
{
297297
using TestAsset testAsset = await TestAsset.GenerateAssetAsync(
298298
AssetName,
299299
SingleTestSourceCode
300300
.PatchCodeWithReplace("$MSTestVersion$", MSTestVersion)
301-
// temporarily set test to be on net10.0 as older TFMs are broken until https://github.com/dotnet/runtime/pull/115951 is serviced.
302301
.PatchCodeWithReplace("$TargetFramework$", TargetFrameworks.NetCurrent)
303302
.PatchCodeWithReplace("$ExtraProperties$", """
304303
<PublishAot>true</PublishAot>
305304
<EnableMicrosoftTestingExtensionsCodeCoverage>false</EnableMicrosoftTestingExtensionsCodeCoverage>
305+
<!-- Show individual trim/AOT warnings instead of a single IL2104 per assembly -->
306+
<TrimmerSingleWarn>false</TrimmerSingleWarn>
306307
"""),
307308
addPublicFeeds: true);
308309

309310
DotnetMuxerResult compilationResult = await DotnetCli.RunAsync(
310311
$"publish -r {RID} -f {TargetFrameworks.NetCurrent} {testAsset.TargetAssetPath}",
311312
AcceptanceFixture.NuGetGlobalPackagesFolder.Path,
312-
// We prefer to use the outer retry mechanism as we need some extra checks
313313
retryCount: 0, cancellationToken: TestContext.CancellationToken);
314314
compilationResult.AssertOutputContains("Generating native code");
315315

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.Testing.Platform.Acceptance.IntegrationTests;
5+
6+
namespace MSTest.Acceptance.IntegrationTests;
7+
8+
[TestClass]
9+
public class TrimTests : AcceptanceTestBase<NopAssetFixture>
10+
{
11+
// Source code for a minimal project that deeply validates trim/AOT compatibility of
12+
// MSTest assemblies by using TrimmerRootAssembly to force the trimmer to analyze
13+
// all code paths in each assembly, not just those reachable from the test entry point.
14+
// See https://learn.microsoft.com/dotnet/core/deploying/trimming/prepare-libraries-for-trimming
15+
private const string TrimAnalysisSourceCode = """
16+
#file MSTestTrimAnalysisTest.csproj
17+
<Project Sdk="Microsoft.NET.Sdk">
18+
<PropertyGroup>
19+
<TargetFramework>$TargetFramework$</TargetFramework>
20+
<OutputType>Exe</OutputType>
21+
<PublishTrimmed>true</PublishTrimmed>
22+
<!-- Show individual trim warnings instead of a single IL2104 per assembly -->
23+
<TrimmerSingleWarn>false</TrimmerSingleWarn>
24+
</PropertyGroup>
25+
<ItemGroup>
26+
<PackageReference Include="Microsoft.Testing.Platform" Version="$MicrosoftTestingPlatformVersion$" />
27+
<PackageReference Include="MSTest.Engine" Version="$MSTestEngineVersion$" />
28+
<PackageReference Include="MSTest.SourceGeneration" Version="$MSTestEngineVersion$" />
29+
<PackageReference Include="MSTest.TestFramework" Version="$MSTestVersion$" />
30+
</ItemGroup>
31+
<!-- Force the trimmer to analyze the full assembly surface, not just reachable code paths.
32+
MSTest.SourceGeneration is a source generator with no runtime assembly so it cannot be a trimmer root.
33+
MSTest.TestFramework has known reflection-heavy code paths (DynamicData, etc.) that are not yet trim-safe. -->
34+
<ItemGroup>
35+
<TrimmerRootAssembly Include="MSTest.Engine" />
36+
</ItemGroup>
37+
</Project>
38+
39+
#file Program.cs
40+
System.Console.WriteLine("This project validates trim/AOT compatibility via dotnet publish.");
41+
""";
42+
43+
[TestMethod]
44+
[DynamicData(nameof(TargetFrameworks.NetForDynamicData), typeof(TargetFrameworks))]
45+
public async Task Publish_ShouldNotProduceTrimWarnings(string tfm)
46+
{
47+
// See https://github.com/microsoft/testfx/issues/7153
48+
// This test forces deep trim analysis of MSTest assemblies using TrimmerRootAssembly
49+
// to catch trim warnings that would not be caught by only testing reachable code paths.
50+
using TestAsset generator = await TestAsset.GenerateAssetAsync(
51+
$"MSTestTrimAnalysisTest_{tfm}",
52+
TrimAnalysisSourceCode
53+
.PatchCodeWithReplace("$MicrosoftTestingPlatformVersion$", MicrosoftTestingPlatformVersion)
54+
.PatchCodeWithReplace("$MSTestVersion$", MSTestVersion)
55+
.PatchCodeWithReplace("$MSTestEngineVersion$", MSTestEngineVersion)
56+
.PatchCodeWithReplace("$TargetFramework$", tfm),
57+
addPublicFeeds: true);
58+
59+
await DotnetCli.RunAsync(
60+
$"restore {generator.TargetAssetPath} -r {RID}",
61+
AcceptanceFixture.NuGetGlobalPackagesFolder.Path,
62+
retryCount: 0,
63+
cancellationToken: TestContext.CancellationToken);
64+
await DotnetCli.RunAsync(
65+
$"publish {generator.TargetAssetPath} -r {RID} -f {tfm}",
66+
AcceptanceFixture.NuGetGlobalPackagesFolder.Path,
67+
retryCount: 0,
68+
cancellationToken: TestContext.CancellationToken);
69+
}
70+
71+
public TestContext TestContext { get; set; }
72+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Microsoft.Testing.Platform.Acceptance.IntegrationTests;
5+
6+
[TestClass]
7+
public class NativeAotTests : AcceptanceTestBase<NopAssetFixture>
8+
{
9+
// Source code for a minimal NativeAOT test project using a locally defined test framework
10+
// (not MSTest) to validate that Microsoft.Testing.Platform itself supports Native AOT.
11+
private const string SourceCode = """
12+
#file NativeAotTests.csproj
13+
<Project Sdk="Microsoft.NET.Sdk">
14+
<PropertyGroup>
15+
<TargetFramework>$TargetFramework$</TargetFramework>
16+
<ImplicitUsings>enable</ImplicitUsings>
17+
<Nullable>enable</Nullable>
18+
<OutputType>Exe</OutputType>
19+
<UseAppHost>true</UseAppHost>
20+
<LangVersion>preview</LangVersion>
21+
<PublishAot>true</PublishAot>
22+
<!-- Show individual trim/AOT warnings instead of a single IL2104 per assembly -->
23+
<TrimmerSingleWarn>false</TrimmerSingleWarn>
24+
</PropertyGroup>
25+
<ItemGroup>
26+
<PackageReference Include="Microsoft.Testing.Platform" Version="$MicrosoftTestingPlatformVersion$" />
27+
</ItemGroup>
28+
</Project>
29+
30+
#file Program.cs
31+
using Microsoft.Testing.Platform.Builder;
32+
using Microsoft.Testing.Platform.Capabilities.TestFramework;
33+
using Microsoft.Testing.Platform.Extensions.Messages;
34+
using Microsoft.Testing.Platform.Extensions.TestFramework;
35+
36+
ITestApplicationBuilder builder = await TestApplication.CreateBuilderAsync(args);
37+
builder.RegisterTestFramework(_ => new TestFrameworkCapabilities(), (_, __) => new DummyTestFramework());
38+
using ITestApplication app = await builder.BuildAsync();
39+
return await app.RunAsync();
40+
41+
internal class DummyTestFramework : ITestFramework, IDataProducer
42+
{
43+
public string Uid => nameof(DummyTestFramework);
44+
45+
public string Version => "1.0.0";
46+
47+
public string DisplayName => nameof(DummyTestFramework);
48+
49+
public string Description => nameof(DummyTestFramework);
50+
51+
public Task<bool> IsEnabledAsync() => Task.FromResult(true);
52+
53+
public Type[] DataTypesProduced => new[] { typeof(TestNodeUpdateMessage) };
54+
55+
public Task<CreateTestSessionResult> CreateTestSessionAsync(CreateTestSessionContext context)
56+
=> Task.FromResult(new CreateTestSessionResult() { IsSuccess = true });
57+
58+
public Task<CloseTestSessionResult> CloseTestSessionAsync(CloseTestSessionContext context)
59+
=> Task.FromResult(new CloseTestSessionResult() { IsSuccess = true });
60+
61+
public async Task ExecuteRequestAsync(ExecuteRequestContext context)
62+
{
63+
await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, new TestNode()
64+
{
65+
Uid = "Test1",
66+
DisplayName = "Test1",
67+
Properties = new PropertyBag(PassedTestNodeStateProperty.CachedInstance),
68+
}));
69+
70+
context.Complete();
71+
}
72+
}
73+
""";
74+
75+
[TestMethod]
76+
// The hosted AzDO agents for Mac OS don't have the required tooling for us to test Native AOT.
77+
[OSCondition(ConditionMode.Exclude, OperatingSystems.OSX)]
78+
[DynamicData(nameof(TargetFrameworks.NetForDynamicData), typeof(TargetFrameworks))]
79+
public async Task NativeAotTests_WillRunWithExitCodeZero(string tfm)
80+
{
81+
using TestAsset generator = await TestAsset.GenerateAssetAsync(
82+
$"NativeAotTests_{tfm}",
83+
SourceCode
84+
.PatchCodeWithReplace("$MicrosoftTestingPlatformVersion$", MicrosoftTestingPlatformVersion)
85+
.PatchCodeWithReplace("$TargetFramework$", tfm),
86+
addPublicFeeds: true);
87+
88+
await DotnetCli.RunAsync(
89+
$"restore {generator.TargetAssetPath} -r {RID}",
90+
AcceptanceFixture.NuGetGlobalPackagesFolder.Path,
91+
retryCount: 0,
92+
cancellationToken: TestContext.CancellationToken);
93+
DotnetMuxerResult compilationResult = await DotnetCli.RunAsync(
94+
$"publish {generator.TargetAssetPath} -r {RID} -f {tfm}",
95+
AcceptanceFixture.NuGetGlobalPackagesFolder.Path,
96+
retryCount: 0,
97+
cancellationToken: TestContext.CancellationToken);
98+
compilationResult.AssertOutputContains("Generating native code");
99+
100+
var testHost = TestInfrastructure.TestHost.LocateFrom(generator.TargetAssetPath, "NativeAotTests", tfm, RID, Verb.publish);
101+
102+
TestHostResult result = await testHost.ExecuteAsync(cancellationToken: TestContext.CancellationToken);
103+
result.AssertExitCodeIs(0);
104+
}
105+
106+
public TestContext TestContext { get; set; }
107+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Microsoft.Testing.Platform.Acceptance.IntegrationTests;
5+
6+
[TestClass]
7+
public class TrimTests : AcceptanceTestBase<NopAssetFixture>
8+
{
9+
// Source code for a minimal project that deeply validates trim/AOT compatibility of
10+
// Microsoft.Testing.Platform by using TrimmerRootAssembly to force the trimmer to analyze
11+
// all code paths in the assembly, not just those reachable from the test entry point.
12+
// See https://learn.microsoft.com/dotnet/core/deploying/trimming/prepare-libraries-for-trimming
13+
private const string TrimAnalysisSourceCode = """
14+
#file TrimAnalysisTest.csproj
15+
<Project Sdk="Microsoft.NET.Sdk">
16+
<PropertyGroup>
17+
<TargetFramework>$TargetFramework$</TargetFramework>
18+
<OutputType>Exe</OutputType>
19+
<PublishTrimmed>true</PublishTrimmed>
20+
<!-- Show individual trim warnings instead of a single IL2104 per assembly -->
21+
<TrimmerSingleWarn>false</TrimmerSingleWarn>
22+
</PropertyGroup>
23+
<ItemGroup>
24+
<PackageReference Include="Microsoft.Testing.Platform" Version="$MicrosoftTestingPlatformVersion$" />
25+
</ItemGroup>
26+
<!-- Force the trimmer to analyze the full assembly surface, not just reachable code paths -->
27+
<ItemGroup>
28+
<TrimmerRootAssembly Include="Microsoft.Testing.Platform" />
29+
</ItemGroup>
30+
</Project>
31+
32+
#file Program.cs
33+
System.Console.WriteLine("This project validates trim/AOT compatibility via dotnet publish.");
34+
""";
35+
36+
[TestMethod]
37+
[DynamicData(nameof(TargetFrameworks.NetForDynamicData), typeof(TargetFrameworks))]
38+
public async Task Publish_ShouldNotProduceTrimWarnings(string tfm)
39+
{
40+
// See https://github.com/microsoft/testfx/issues/7153
41+
// This test forces deep trim analysis of Microsoft.Testing.Platform using TrimmerRootAssembly
42+
// to catch trim warnings that would not be caught by only testing reachable code paths.
43+
using TestAsset generator = await TestAsset.GenerateAssetAsync(
44+
$"TrimAnalysisTest_{tfm}",
45+
TrimAnalysisSourceCode
46+
.PatchCodeWithReplace("$MicrosoftTestingPlatformVersion$", MicrosoftTestingPlatformVersion)
47+
.PatchCodeWithReplace("$TargetFramework$", tfm),
48+
addPublicFeeds: true);
49+
50+
await DotnetCli.RunAsync(
51+
$"restore {generator.TargetAssetPath} -r {RID}",
52+
AcceptanceFixture.NuGetGlobalPackagesFolder.Path,
53+
retryCount: 0,
54+
cancellationToken: TestContext.CancellationToken);
55+
await DotnetCli.RunAsync(
56+
$"publish {generator.TargetAssetPath} -r {RID} -f {tfm}",
57+
AcceptanceFixture.NuGetGlobalPackagesFolder.Path,
58+
retryCount: 0,
59+
cancellationToken: TestContext.CancellationToken);
60+
}
61+
62+
public TestContext TestContext { get; set; }
63+
}

0 commit comments

Comments
 (0)