diff --git a/src/Package/MSTest.Sdk/README.md b/src/Package/MSTest.Sdk/README.md index 9047d20f80..ea909a9213 100644 --- a/src/Package/MSTest.Sdk/README.md +++ b/src/Package/MSTest.Sdk/README.md @@ -18,3 +18,41 @@ 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 `` +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 + + + + + + net10.0 + + + + + +``` + +Because the version cannot be specified on an ``'s `Sdk` attribute the same way it can on +``, pin the `MSTest.Sdk` version through `global.json`: + +```json +{ + "msbuild-sdks": { + "MSTest.Sdk": "x.y.z" + } +} +``` + +`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. diff --git a/src/Package/MSTest.Sdk/Sdk/Sdk.props.template b/src/Package/MSTest.Sdk/Sdk/Sdk.props.template index 004df17238..12cc054cfa 100644 --- a/src/Package/MSTest.Sdk/Sdk/Sdk.props.template +++ b/src/Package/MSTest.Sdk/Sdk/Sdk.props.template @@ -8,8 +8,33 @@ true + + + <_MSTestSdkImportsMicrosoftNETSdk Condition=" '$(_MSTestSdkImportsMicrosoftNETSdk)' == '' AND '$(UsingMicrosoftNETSdk)' != 'true' ">true + <_MSTestSdkImportsMicrosoftNETSdk Condition=" '$(_MSTestSdkImportsMicrosoftNETSdk)' == '' ">false + + - + false diff --git a/src/Package/MSTest.Sdk/Sdk/Sdk.targets b/src/Package/MSTest.Sdk/Sdk/Sdk.targets index eb0409a9af..f71aae3f4b 100644 --- a/src/Package/MSTest.Sdk/Sdk/Sdk.targets +++ b/src/Package/MSTest.Sdk/Sdk/Sdk.targets @@ -13,6 +13,13 @@ - - + + diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SdkTests.cs b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SdkTests.cs index 4bbac6557f..00238c0018 100644 --- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SdkTests.cs +++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SdkTests.cs @@ -667,4 +667,71 @@ 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 + + + + + + + true + $TargetFramework$ + x64 + $(NoWarn);NU1507 + + + + + + + +#file global.json +{ + "msbuild-sdks": { + "MSTest.Sdk": "$MSTestVersion$" + } +} + +#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); + } }