Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/Package/MSTest.Sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,30 @@ Instead, the `.targets` files should split the version specification depending o
matching `PackageVersion` item carrying the `Version` metadata so CPM resolves the version.

This approach works regardless of `CentralPackageVersionOverrideEnabled` and does not produce `NU1009` warnings.

## Layering on top of a different base SDK

`MSTest.Sdk` implicitly imports `Microsoft.NET.Sdk` as its base SDK, so `<Project Sdk="MSTest.Sdk">`
is enough for the common case. To combine it with a different base SDK (for example
`Microsoft.NET.Sdk.Web` for an ASP.NET Core integration test project), import both SDKs manually and
list `Microsoft.NET.Sdk.Web` first so it owns the base `Microsoft.NET.Sdk` import:

```xml
<Project>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk.Web" />
<Import Project="Sdk.props" Sdk="MSTest.Sdk" />

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

<Import Project="Sdk.targets" Sdk="MSTest.Sdk" />
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk.Web" />
</Project>
```

`Sdk.props`/`Sdk.targets` guard their `Microsoft.NET.Sdk` import behind the
`_MSTestSdkImportsMicrosoftNETSdk` property: `MSTest.Sdk` only imports the base SDK when no other SDK
has already done so (detected via the `UsingMicrosoftNETSdk` property that `Microsoft.NET.Sdk` sets
very early). This keeps the manual/mixed layering scenario free of `MSB4011` duplicate-import
warnings.
27 changes: 26 additions & 1 deletion src/Package/MSTest.Sdk/Sdk/Sdk.props.template
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,33 @@
<UsingMSTestSdk>true</UsingMSTestSdk>
</PropertyGroup>

<!--
MSTest.Sdk layers on top of Microsoft.NET.Sdk. In the common case, MSTest.Sdk is the outer
SDK (`<Project Sdk="MSTest.Sdk">`) and is responsible for importing the base .NET SDK.

However, MSTest.Sdk can also be layered on top of a different base SDK (for example
Microsoft.NET.Sdk.Web) using manual SDK imports:

<Project>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk.Web" />
<Import Project="Sdk.props" Sdk="MSTest.Sdk" />
...
<Import Project="Sdk.targets" Sdk="MSTest.Sdk" />
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk.Web" />
</Project>

In that case the outer SDK has already imported Microsoft.NET.Sdk (which sets
'UsingMicrosoftNETSdk' to 'true' before anything else), so importing it again here would emit
MSB4011 duplicate-import warnings. We only import the base SDK when nobody else has, and we
remember that decision so Sdk.targets knows whether it should also import the base SDK targets.
-->
<PropertyGroup>
<_MSTestSdkImportsMicrosoftNETSdk Condition=" '$(_MSTestSdkImportsMicrosoftNETSdk)' == '' AND '$(UsingMicrosoftNETSdk)' != 'true' ">true</_MSTestSdkImportsMicrosoftNETSdk>
<_MSTestSdkImportsMicrosoftNETSdk Condition=" '$(_MSTestSdkImportsMicrosoftNETSdk)' == '' ">false</_MSTestSdkImportsMicrosoftNETSdk>
</PropertyGroup>

<!-- Implicit top import -->
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" Condition=" '$(_MSTestSdkImportsMicrosoftNETSdk)' == 'true' " />

<PropertyGroup>
<EnableAspireTesting Condition=" '$(EnableAspireTesting)' == '' ">false</EnableAspireTesting>
Expand Down
11 changes: 9 additions & 2 deletions src/Package/MSTest.Sdk/Sdk/Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
<!-- Import VSTest.targets -->
<Import Project="$(MSBuildThisFileDirectory)VSTest/VSTest.targets" Condition=" '$(UseVSTest)' == 'true' " />

<!-- Implicit bottom import -->
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
<!--
Implicit bottom import.

Only import the base .NET SDK targets when MSTest.Sdk was the SDK that imported the base .NET
SDK props (see the detailed comment in Sdk.props). When MSTest.Sdk is layered on top of another
base SDK, that outer SDK imports the base targets itself, so importing them here would emit
MSB4011 duplicate-import warnings.
-->
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" Condition=" '$(_MSTestSdkImportsMicrosoftNETSdk)' == 'true' " />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -667,4 +667,64 @@ public void TestMethod1()
testHostResult.AssertOutputContains("Test Parallelization enabled");
testHostResult.AssertOutputContains("(Workers: 3, Scope: MethodLevel)");
}

// Verifies that MSTest.Sdk can be layered on top of a different base SDK (here Microsoft.NET.Sdk.Web)
// using manual SDK imports without emitting MSB4011 duplicate-import warnings. See https://github.com/microsoft/testfx/issues/9562.
[TestMethod]
public async Task MSTestSdk_LayeredOnTopOfWebSdk_BuildsWithoutDuplicateImportWarnings()
{
const string MixedSdkSource = """
#file MSTestWeb.csproj
<Project>

<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk.Web" />
<Import Project="Sdk.props" Sdk="MSTest.Sdk/$MSTestVersion$" />

<PropertyGroup>
<EnableMicrosoftTestingPlatform>true</EnableMicrosoftTestingPlatform>
<TargetFramework>$TargetFramework$</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<NoWarn>$(NoWarn);NU1507</NoWarn>
</PropertyGroup>

<Import Project="Sdk.targets" Sdk="MSTest.Sdk/$MSTestVersion$" />
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk.Web" />

</Project>

#file UnitTest1.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MSTestWebTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
WebApplicationBuilder builder = WebApplication.CreateBuilder();
Assert.IsNotNull(builder);
}
}
}
""";

const string MixedAssetName = "MSTestWeb";

using TestAsset testAsset = await TestAsset.GenerateAssetAsync(
MixedAssetName,
MixedSdkSource
.PatchCodeWithReplace("$MSTestVersion$", MSTestVersion)
.PatchCodeWithReplace("$TargetFramework$", TargetFrameworks.NetCurrent));

DotnetMuxerResult compilationResult = await DotnetCli.RunAsync($"build -c {BuildConfiguration.Release} {testAsset.TargetAssetPath}", cancellationToken: TestContext.CancellationToken);
compilationResult.AssertExitCodeIs(0);
compilationResult.AssertOutputDoesNotContain("MSB4011");

var testHost = TestHost.LocateFrom(testAsset.TargetAssetPath, MixedAssetName, TargetFrameworks.NetCurrent, buildConfiguration: BuildConfiguration.Release);
TestHostResult testHostResult = await testHost.ExecuteAsync(cancellationToken: TestContext.CancellationToken);
testHostResult.AssertOutputContainsSummary(0, 1, 0);
}
}
Loading