|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
extern alias sdkResolver;
using System.Runtime.CompilerServices;
using Microsoft.Build.Framework;
using Microsoft.DotNet.DotNetSdkResolver;
using Microsoft.DotNet.MSBuildSdkResolver;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace Microsoft.DotNet.Cli.Utils.Tests
{
public class GivenAnMSBuildSdkResolver : SdkTest
{
private const string DotnetHostExperimentalKey = "DOTNET_EXPERIMENTAL_HOST_PATH";
private const string MSBuildTaskHostRuntimeVersion = "SdkResolverMSBuildTaskHostRuntimeVersion";
public GivenAnMSBuildSdkResolver(ITestOutputHelper logger) : base(logger)
{
}
[Fact]
public void ItHasCorrectNameAndPriority()
{
var resolver = new DotNetMSBuildSdkResolver();
Assert.Equal(5000, resolver.Priority);
Assert.Equal("Microsoft.DotNet.MSBuildSdkResolver", resolver.Name);
}
[Fact]
public void ItDoesNotFindMSBuildSdkThatIsMissingFromLocatedNETCoreSdk()
{
var environment = new TestEnvironment(_testAssetsManager);
var expected = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.97");
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.SdkThatDoesNotExist", null, null),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeFalse();
result.Path.Should().BeNull();
result.AdditionalPaths.Should().BeNull();
result.Version.Should().BeNull();
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().NotBeEmpty();
}
[Fact]
public void ItFindsTheVersionSpecifiedInGlobalJson()
{
var environment = new TestEnvironment(_testAssetsManager);
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.97");
var expected = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.98");
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.99");
environment.CreateGlobalJson(environment.TestDirectory, "99.99.98");
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be(expected.FullName);
result.AdditionalPaths.Should().BeNull();
result.Version.Should().Be("99.99.98");
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void ItUsesProjectDirectoryIfSolutionFilePathIsNullOrWhitespace(string? solutionFilePath)
{
const string version = "99.0.0";
var environment = new TestEnvironment(_testAssetsManager, identifier: solutionFilePath ?? "NULL");
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", version);
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, version),
new MockContext { ProjectFileDirectory = environment.TestDirectory, SolutionFilePath = solutionFilePath },
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().StartWith(environment.TestDirectory.FullName);
result.AdditionalPaths.Should().BeNull();
result.Version.Should().Be(version);
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
}
[Theory]
[InlineData(null, null)]
[InlineData("", null)]
[InlineData("", "")]
[InlineData(null, "")]
public void ItUsesCurrentDirectoryIfSolutionFilePathAndProjectFilePathIsNullOrWhitespace(string? solutionFilePath, string? projectFilePath)
{
const string version = "99.0.0";
var environment = new TestEnvironment(_testAssetsManager, identifier: $"{solutionFilePath ?? "NULL"}-{projectFilePath ?? "NULL"}");
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", version);
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, version),
new MockContext { ProjectFilePath = projectFilePath, SolutionFilePath = solutionFilePath },
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().StartWith(environment.TestDirectory.FullName);
result.AdditionalPaths.Should().BeNull();
result.Version.Should().Be(version);
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
}
[Fact]
public void ItReturnsNullIfTheVersionFoundDoesNotSatisfyTheMinVersion()
{
var environment = new TestEnvironment(_testAssetsManager);
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.99");
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, "999.99.99"),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeFalse();
result.Path.Should().BeNull();
result.AdditionalPaths.Should().BeNull();
result.Version.Should().BeNull();
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().Contain(string.Format(Strings.NETCoreSDKSmallerThanMinimumRequestedVersion, "99.99.99", "999.99.99"));
}
[Fact]
public void ItReturnsNullWhenTheSDKRequiresAHigherVersionOfMSBuildThanAnyOneAvailable()
{
var environment = new TestEnvironment(_testAssetsManager);
var expected =
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.99", new Version(2, 0));
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, "99.99.99"),
new MockContext
{
MSBuildVersion = new Version(1, 0),
ProjectFileDirectory = environment.TestDirectory
},
new MockFactory());
result.Success.Should().BeFalse();
result.Path.Should().BeNull();
result.AdditionalPaths.Should().BeNull();
result.Version.Should().BeNull();
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().Contain(string.Format(Strings.MSBuildSmallerThanMinimumVersion, "99.99.99", "2.0", "1.0"));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ItReturnsHighestSdkAvailableThatIsCompatibleWithMSBuild(bool disallowPreviews)
{
var environment = new TestEnvironment(_testAssetsManager, identifier: disallowPreviews.ToString())
{
DisallowPrereleaseByDefault = disallowPreviews
};
var compatibleRtm = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "98.98.98", new Version(19, 0, 0, 0));
var compatiblePreview = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.99-preview", new Version(20, 0, 0, 0));
var incompatible = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "100.100.100", new Version(21, 0, 0, 0));
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
new MockContext
{
MSBuildVersion = new Version(20, 0, 0, 0),
ProjectFileDirectory = environment.TestDirectory,
},
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be((disallowPreviews ? compatibleRtm : compatiblePreview).FullName);
result.AdditionalPaths.Should().BeNull();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// DotnetHost is the path to dotnet.exe. Can be only on Windows.
result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(2);
result.PropertiesToAdd.Should().ContainKey(DotnetHostExperimentalKey);
}
else
{
result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(1);
}
result.PropertiesToAdd.Should().ContainKey(MSBuildTaskHostRuntimeVersion);
result.PropertiesToAdd[MSBuildTaskHostRuntimeVersion].Should().Be("mockRuntimeVersion");
result.Version.Should().Be(disallowPreviews ? "98.98.98" : "99.99.99-preview");
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
}
[Fact]
public void WhenALocalSdkIsResolvedItReturnsHostFromThatSDKInsteadOfAmbientGlobalSdk()
{
// create a test that sets up a TestEnvironment with
// * an ambient global SDK
// * a different-versioned SDK that's in a different location
// * a global.json with sdk.paths that prefers the different-versioned SDK
// assert that when we resolve, we return the path to the different-versioned SDK's dotnet.exe
var environment = new TestEnvironment(_testAssetsManager);
var localSdkRoot = Path.Combine("some", "local", "dir");
var localSdkDotnetRoot = Path.Combine(environment.TestDirectory.FullName, localSdkRoot, "dotnet");
var ambientSdkDotnetRoot = Path.Combine(environment.GetProgramFilesDirectory(ProgramFiles.X64).FullName, "dotnet");
var ambientMSBuildSkRoot = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "1.2.3");
var localPathMSBuildSdkRoot = environment.CreateSdkDirectory(localSdkRoot, "Some.Test.Sdk", "1.2.4");
var ambientDotnetBinary = environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var localDotnetBinary = environment.CreateMuxer(localSdkRoot);
environment.CreateGlobalJson(environment.TestDirectory, "1.2.3", [localSdkDotnetRoot, ambientSdkDotnetRoot]);
var resolver = environment.CreateResolver();
var context = new MockContext(Log)
{
MSBuildVersion = new Version(20, 0, 0, 0),
ProjectFileDirectory = environment.TestDirectory,
IsRunningInVisualStudio = false
};
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
context,
new MockFactory());
result.Success.Should().BeTrue();
result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(2);
result.PropertiesToAdd.Should().ContainKey(DotnetHostExperimentalKey);
result.PropertiesToAdd[DotnetHostExperimentalKey].Should().Be(localDotnetBinary);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ItDoesNotReturnHighestSdkAvailableThatIsCompatibleWithMSBuildWhenVersionInGlobalJsonCannotBeFoundOutsideOfVisualStudio(bool disallowPreviews)
{
var environment = new TestEnvironment(_testAssetsManager, callingMethod: "ItDoesNotReturnHighest___", identifier: disallowPreviews.ToString())
{
DisallowPrereleaseByDefault = disallowPreviews
};
var compatibleRtm = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "98.98.98", new Version(19, 0, 0, 0));
var compatiblePreview = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.99-preview", new Version(20, 0, 0, 0));
var incompatible = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "100.100.100", new Version(21, 0, 0, 0));
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
environment.CreateGlobalJson(environment.TestDirectory, "1.2.3");
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
new MockContext
{
MSBuildVersion = new Version(20, 0, 0, 0),
ProjectFileDirectory = environment.TestDirectory,
IsRunningInVisualStudio = false
},
new MockFactory());
result.Success.Should().BeFalse();
result.Path.Should().BeNull();
result.AdditionalPaths.Should().BeNull();
result.PropertiesToAdd.Should().BeNull();
result.Version.Should().BeNull();
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().NotBeEmpty();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ItReturnsHighestSdkAvailableThatIsCompatibleWithMSBuildWhenVersionInGlobalJsonCannotBeFoundAndRunningInVisualStudio(bool disallowPreviews)
{
var environment = new TestEnvironment(_testAssetsManager, callingMethod: "ItReturnsHighest___", identifier: disallowPreviews.ToString())
{
DisallowPrereleaseByDefault = disallowPreviews
};
var compatibleRtm = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "98.98.98", new Version(19, 0, 0, 0));
var compatiblePreview = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.99-preview", new Version(20, 0, 0, 0));
var incompatible = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "100.100.100", new Version(21, 0, 0, 0));
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
environment.CreateGlobalJson(environment.TestDirectory, "1.2.3");
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
new MockContext
{
MSBuildVersion = new Version(20, 0, 0, 0),
ProjectFileDirectory = environment.TestDirectory,
IsRunningInVisualStudio = true
},
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be((disallowPreviews ? compatibleRtm : compatiblePreview).FullName);
result.AdditionalPaths.Should().BeNull();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// DotnetHost is the path to dotnet.exe. Can be only on Windows.
result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(4);
result.PropertiesToAdd.Should().ContainKey(DotnetHostExperimentalKey);
}
else
{
result.PropertiesToAdd.Should().NotBeNull().And.HaveCount(3);
}
result.PropertiesToAdd.Should().ContainKey(MSBuildTaskHostRuntimeVersion);
result.PropertiesToAdd[MSBuildTaskHostRuntimeVersion].Should().Be("mockRuntimeVersion");
result.PropertiesToAdd.Should().ContainKey("SdkResolverHonoredGlobalJson");
result.PropertiesToAdd.Should().ContainKey("SdkResolverGlobalJsonPath");
result.PropertiesToAdd["SdkResolverHonoredGlobalJson"].Should().Be("false");
result.Version.Should().Be(disallowPreviews ? "98.98.98" : "99.99.99-preview");
result.Warnings.Should().BeEquivalentTo(new[] { "Unable to locate the .NET SDK version '1.2.3' as specified by global.json, please check that the specified version is installed." });
result.Errors.Should().BeNullOrEmpty();
}
[Fact]
public void ItReturnsNullWhenTheDefaultVSRequiredSDKVersionIsHigherThanTheSDKVersionAvailable()
{
var environment = new TestEnvironment(_testAssetsManager);
var expected =
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "1.0.1");
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, "1.0.0"),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeFalse();
result.Path.Should().BeNull();
result.AdditionalPaths.Should().BeNull();
result.Version.Should().BeNull();
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().Contain(string.Format(Strings.NETCoreSDKSmallerThanMinimumVersionRequiredByVisualStudio, "1.0.1", "1.0.4"));
}
[Fact]
public void ItReturnsNullWhenTheTheVSRequiredSDKVersionIsHigherThanTheSDKVersionAvailable()
{
var environment = new TestEnvironment(_testAssetsManager);
var expected =
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "1.0.1");
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
environment.CreateMinimumVSDefinedSDKVersionFile("2.0.0");
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, "1.0.0"),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeFalse();
result.Path.Should().BeNull();
result.AdditionalPaths.Should().BeNull();
result.Version.Should().BeNull();
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().Contain(string.Format(Strings.NETCoreSDKSmallerThanMinimumVersionRequiredByVisualStudio, "1.0.1", "2.0.0"));
}
[Fact]
public void ItReturnsTheVersionIfItIsEqualToTheMinVersionAndTheVSDefinedMinVersion()
{
var environment = new TestEnvironment(_testAssetsManager);
var expected = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.99");
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
environment.CreateMinimumVSDefinedSDKVersionFile("99.99.99");
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, "99.99.99"),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be(expected.FullName);
result.AdditionalPaths.Should().BeNull();
result.Version.Should().Be("99.99.99");
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
}
[Fact]
public void ItReturnsTheVersionIfItIsHigherThanTheMinVersionAndTheVSDefinedMinVersion()
{
var environment = new TestEnvironment(_testAssetsManager);
var expected = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "999.99.99");
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
environment.CreateMinimumVSDefinedSDKVersionFile("999.99.98");
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, "99.99.99"),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be(expected.FullName);
result.AdditionalPaths.Should().BeNull();
result.Version.Should().Be("999.99.99");
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ItDisallowsPreviewsBasedOnDefault(bool disallowPreviewsByDefault)
{
var environment = new TestEnvironment(_testAssetsManager, identifier: disallowPreviewsByDefault.ToString());
var rtm = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "10.0.0");
var preview = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "11.0.0-preview1");
var expected = disallowPreviewsByDefault ? rtm : preview;
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
environment.DisallowPrereleaseByDefault = disallowPreviewsByDefault;
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be(expected.FullName);
result.AdditionalPaths.Should().BeNull();
result.Version.Should().Be(disallowPreviewsByDefault ? "10.0.0" : "11.0.0-preview1");
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ItDisallowsPreviewsBasedOnFile(bool disallowPreviews)
{
var environment = new TestEnvironment(_testAssetsManager, identifier: disallowPreviews.ToString());
var rtm = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "10.0.0");
var preview = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "11.0.0-preview1");
var expected = disallowPreviews ? rtm : preview;
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
environment.DisallowPrereleaseByDefault = !disallowPreviews;
environment.CreateVSSettingsFile(disallowPreviews);
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be(expected.FullName);
result.AdditionalPaths.Should().BeNull();
result.Version.Should().Be(disallowPreviews ? "10.0.0" : "11.0.0-preview1");
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
}
[Fact]
public void ItObservesChangesToVSSettingsFile()
{
var environment = new TestEnvironment(_testAssetsManager);
var rtm = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "10.0.0");
var preview = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "11.0.0-preview1");
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
environment.CreateVSSettingsFile(disallowPreviews: true);
var resolver = environment.CreateResolver();
void Check(bool disallowPreviews, string message)
{
// check twice because file-up-to-date is a separate code path
for (int i = 0; i < 2; i++)
{
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
string m = $"{message} ({i})";
var expected = disallowPreviews ? rtm : preview;
result.Success.Should().BeTrue(m);
result.Path.Should().Be(expected.FullName, m);
result.AdditionalPaths.Should().BeNull();
result.Version.Should().Be(disallowPreviews ? "10.0.0" : "11.0.0-preview1", m);
result.Warnings.Should().BeNullOrEmpty(m);
result.Errors.Should().BeNullOrEmpty(m);
}
}
environment.DeleteVSSettingsFile();
Check(disallowPreviews: false, message: "default with no file");
environment.CreateVSSettingsFile(disallowPreviews: true);
Check(disallowPreviews: true, message: "file changed to disallow previews");
environment.CreateVSSettingsFile(disallowPreviews: false);
Check(disallowPreviews: false, message: "file changed to not disallow previews");
environment.CreateVSSettingsFile(disallowPreviews: true);
Check(disallowPreviews: true, message: "file changed back to disallow previews");
environment.DeleteVSSettingsFile();
Check(disallowPreviews: false, message: "file deleted to return to default");
}
[Fact]
public void ItAllowsPreviewWhenGlobalJsonHasPreviewIrrespectiveOfSetting()
{
var environment = new TestEnvironment(_testAssetsManager);
var rtm = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "10.0.0");
var preview = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "11.0.0-preview1");
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
environment.DisallowPrereleaseByDefault = true;
environment.CreateGlobalJson(environment.TestDirectory, "11.0.0-preview1");
var resolver = environment.CreateResolver();
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be(preview.FullName);
result.AdditionalPaths.Should().BeNull();
result.Version.Should().Be("11.0.0-preview1");
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
}
[Fact]
public void ItRespectsAmbientVSSettings()
{
// When run in test explorer in VS, this will actually locate the settings for the current VS instance
// based on location of testhost executable. This gives us some coverage threw that path but we cannot
// fix our expectations since the behavior will vary (by design) based on the current VS instance's settings.
var vsSettings = VSSettings.Ambient;
var environment = new TestEnvironment(_testAssetsManager);
var rtm = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "10.0.0");
var preview = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "11.0.0-preview1");
var expected = vsSettings.DisallowPrerelease() ? rtm : preview;
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = environment.CreateResolver(useAmbientSettings: true);
var result = (MockResult)resolver.Resolve(
new SdkReference("Some.Test.Sdk", null, null),
new MockContext { ProjectFileDirectory = environment.TestDirectory },
new MockFactory());
result.Success.Should().BeTrue($"No error expected. Error encountered: {string.Join(Environment.NewLine, result.Errors ?? new string[] { })}. Mocked Process Path: {environment.ProcessPath}. Mocked Path: {environment.PathEnvironmentVariable}");
result.Path.Should().Be(expected.FullName);
result.AdditionalPaths.Should().BeNull();
result.Version.Should().Be(vsSettings.DisallowPrerelease() ? "10.0.0" : "11.0.0-preview1");
result.Warnings.Should().BeNullOrEmpty();
result.Errors.Should().BeNullOrEmpty();
}
[Fact]
public void GivenTemplateLocatorItCanResolveSdkVersion()
{
var environment = new TestEnvironment(_testAssetsManager);
const string sdkVersion = "99.99.97";
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", sdkVersion);
environment.CreateMuxerAndAddToPath(ProgramFiles.X64);
var resolver = new TemplateLocator.TemplateLocator(
environment.GetEnvironmentVariable,
() => environment.ProcessPath,
new sdkResolver::Microsoft.DotNet.DotNetSdkResolver.VSSettings(environment.VSSettingsFile?.FullName, environment.DisallowPrereleaseByDefault), null, null);
resolver.TryGetDotnetSdkVersionUsedInVs("15.8", out var version).Should().BeTrue();
version.Should().Be(sdkVersion);
}
private enum ProgramFiles
{
X64,
X86,
Default,
}
private sealed class TestEnvironment : SdkResolverContext
{
public string Muxer => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet";
public string PathEnvironmentVariable { get; set; }
public string ProcessPath
{
get
{
if (field is null)
{
throw new ArgumentException("ProcessPath must be set before accessing it, usually by CreateMuxerAndAddToPath()");
}
return field;
}
set;
}
public DirectoryInfo TestDirectory { get; }
public FileInfo? VSSettingsFile { get; set; }
public bool DisallowPrereleaseByDefault { get; set; }
public TestEnvironment(TestAssetsManager testAssets, string identifier = "", [CallerMemberName] string callingMethod = "")
{
TestDirectory = new DirectoryInfo(testAssets.CreateTestDirectory(
// Add FrameworkDescription to identifier so that instances of tests running on different frameworks
// (.NET Core vs .NET Framework) don't conflict
identifier: identifier + RuntimeInformation.FrameworkDescription,
testName: callingMethod).Path);
DeleteMinimumVSDefinedSDKVersionFile();
PathEnvironmentVariable = string.Empty;
}
public SdkResolver CreateResolver(bool useAmbientSettings = false)
=> new DotNetMSBuildSdkResolver(
GetEnvironmentVariable,
// force current executable location to be the mocked dotnet executable location
() => ProcessPath,
(x, y) => "mockRuntimeVersion",
useAmbientSettings
? VSSettings.Ambient
: new VSSettings(VSSettingsFile?.FullName, DisallowPrereleaseByDefault));
public DirectoryInfo GetProgramFilesDirectory(ProgramFiles programFiles)
=> new(Path.Combine(TestDirectory.FullName, $"ProgramFiles{programFiles}"));
/// <returns>the directory containing the MSBuild SDK you specified</returns>
public DirectoryInfo CreateSdkDirectory(
ProgramFiles programFiles,
string sdkName,
string sdkVersion,
Version? minimumMSBuildVersion = null)
{
var netSdkDirectory = Path.Combine(TestDirectory.FullName,
GetProgramFilesDirectory(programFiles).FullName,
"dotnet",
"sdk",
sdkVersion);
new DirectoryInfo(netSdkDirectory).Create();
// hostfxr now checks for the existence of dotnet.dll in an SDK directory: https://github.com/dotnet/runtime/pull/89333
// So create that file
var dotnetDllPath = Path.Combine(netSdkDirectory, "dotnet.dll");
new FileInfo(dotnetDllPath).Create();
var sdkDir = new DirectoryInfo(Path.Combine(netSdkDirectory,
"Sdks",
sdkName,
"Sdk"));
sdkDir.Create();
if (minimumMSBuildVersion != null)
{
CreateMSBuildRequiredVersionFile(programFiles, sdkVersion, minimumMSBuildVersion);
}
return sdkDir;
}
/// <param name="environmentLocalPath">A relative path within the test environment to create an SDK layout within</param>
/// <returns>the directory containing the MSBuild SDK you specified</returns>
public DirectoryInfo CreateSdkDirectory(
string environmentLocalPath,
string sdkName,
string sdkVersion,
Version? minimumMSBuildVersion = null)
{
var netSdkDirectory = Path.Combine(TestDirectory.FullName,
environmentLocalPath,
"dotnet",
"sdk",
sdkVersion);
new DirectoryInfo(netSdkDirectory).Create();
// hostfxr now checks for the existence of dotnet.dll in an SDK directory: https://github.com/dotnet/runtime/pull/89333
// So create that file
var dotnetDllPath = Path.Combine(netSdkDirectory, "dotnet.dll");
new FileInfo(dotnetDllPath).Create();
var sdkDir = new DirectoryInfo(Path.Combine(netSdkDirectory,
"Sdks",
sdkName,
"Sdk"));
sdkDir.Create();
if (minimumMSBuildVersion != null)
{
CreateMSBuildRequiredVersionFile(environmentLocalPath, sdkVersion, minimumMSBuildVersion);
}
return sdkDir;
}
/// <param name="setEnvironmentProps">If true, sets the ProcessPath and PathEnvironmentVariable properties.
/// <returns>The path to the newly-generated dotnet binary</returns>
public string CreateMuxerAndAddToPath(ProgramFiles programFiles, bool setEnvironmentProps = true)
{
var dotnetPath = CreateMuxer(GetProgramFilesDirectory(programFiles).FullName);
if (setEnvironmentProps)
{
ProcessPath = dotnetPath;
PathEnvironmentVariable = $"{Path.GetDirectoryName(dotnetPath)}{Path.PathSeparator}{PathEnvironmentVariable}";
}
return dotnetPath;
}
public string CreateMuxer(string localRootWithinEnvironment)
{
var muxerDirectory = new DirectoryInfo(Path.Combine(TestDirectory.FullName, localRootWithinEnvironment, "dotnet"));
var dotnetPath = Path.Combine(muxerDirectory.FullName, Muxer);
new FileInfo(dotnetPath).Create();
return dotnetPath;
}
private void CreateMSBuildRequiredVersionFile(
ProgramFiles programFiles,
string sdkVersion,
Version minimumMSBuildVersion)
{
if (minimumMSBuildVersion == null)
{
minimumMSBuildVersion = new Version(1, 0);
}
var cliDirectory = new DirectoryInfo(Path.Combine(
TestDirectory.FullName,
GetProgramFilesDirectory(programFiles).FullName,
"dotnet",
"sdk",
sdkVersion));
File.WriteAllText(
Path.Combine(cliDirectory.FullName, "minimumMSBuildVersion"),
minimumMSBuildVersion.ToString());
}
/// <param name="environmentLocalPath">A relative path within the test environment to create the required version file</param>
private void CreateMSBuildRequiredVersionFile(
string environmentLocalPath,
string sdkVersion,
Version minimumMSBuildVersion)
{
if (minimumMSBuildVersion == null)
{
minimumMSBuildVersion = new Version(1, 0);
}
var cliDirectory = new DirectoryInfo(Path.Combine(
TestDirectory.FullName,
environmentLocalPath,
"dotnet",
"sdk",
sdkVersion));
File.WriteAllText(
Path.Combine(cliDirectory.FullName, "minimumMSBuildVersion"),
minimumMSBuildVersion.ToString());
}
public void CreateGlobalJson(DirectoryInfo directory, string version, string[]? paths = null)
{
var builder = new StringBuilder();
builder.AppendLine("{");
builder.AppendLine("\t\"sdk\": {");
builder.Append($"\t\"version\": \"{version}\"");
if (paths is not null)
{
builder.Append(',');
builder.AppendLine("\t\"paths\" : [");
var first = true;
foreach (var path in paths)
{
if (!first)
{
builder.Append(',');
builder.AppendLine();
}
builder.Append($"\t\t\"{path.Replace("\\", "\\\\")}\"");
if (first)
{
first = false;
}
}
builder.AppendLine("\t]");
}
builder.AppendLine("\t}");
builder.AppendLine("}");
var globalJsonContent = builder.ToString();
File.WriteAllText(Path.Combine(directory.FullName, "global.json"), globalJsonContent);
}
public string? GetEnvironmentVariable(string variable)
{
switch (variable)
{
case "PATH":
return PathEnvironmentVariable;
case "DOTNET_MSBUILD_SDK_RESOLVER_ENABLE_LOG":
return "true";
default:
return null;
}
}
public void CreateMinimumVSDefinedSDKVersionFile(string version)
{
File.WriteAllText(GetMinimumVSDefinedSDKVersionFilePath(), version);
}
private void DeleteMinimumVSDefinedSDKVersionFile()
{
File.Delete(GetMinimumVSDefinedSDKVersionFilePath());
}
private string GetMinimumVSDefinedSDKVersionFilePath()
{
string baseDirectory = AppContext.BaseDirectory;
return Path.Combine(baseDirectory, "minimumVSDefinedSDKVersion");
}
public void CreateVSSettingsFile(bool disallowPreviews)
{
VSSettingsFile = new FileInfo(Path.Combine(TestDirectory.FullName, "sdk.txt"));
// Guard against tests writing too fast for the up-to-date check
// It happens more often on Unix due to https://github.com/dotnet/corefx/issues/12403
var lastWriteTimeUtc = VSSettingsFile.Exists ? VSSettingsFile.LastWriteTimeUtc : DateTime.MinValue;
for (int sleep = 10; sleep < 3000; sleep *= 2)
{
File.WriteAllText(VSSettingsFile.FullName, $"UsePreviews={!disallowPreviews}");
VSSettingsFile.Refresh();
if (VSSettingsFile.LastWriteTimeUtc > lastWriteTimeUtc)
{
return;
}
Thread.Sleep(sleep);
}
throw new InvalidOperationException("LastWriteTime is not changing.");
}
public void DeleteVSSettingsFile()
{
VSSettingsFile?.Delete();
}
}
private sealed class MockContext : SdkResolverContext
{
public new string? ProjectFilePath { get => base.ProjectFilePath; set => base.ProjectFilePath = value; }
public new string? SolutionFilePath { get => base.SolutionFilePath; set => base.SolutionFilePath = value; }
public new Version MSBuildVersion { get => base.MSBuildVersion; set => base.MSBuildVersion = value; }
public new bool IsRunningInVisualStudio { get => base.IsRunningInVisualStudio; set => base.IsRunningInVisualStudio = value; }
public DirectoryInfo ProjectFileDirectory
{
get => new(Path.GetDirectoryName(ProjectFilePath));
set => ProjectFilePath = Path.Combine(value.FullName, "test.csproj");
}
public override SdkLogger Logger { get; protected set; }
public MockContext(ITestOutputHelper? logger = null)
{
MSBuildVersion = new Version(15, 3, 0);
Logger = new MockLogger(logger);
}
}
private sealed class MockFactory : SdkResultFactory
{
public override SdkResult IndicateFailure(IEnumerable<string> errors, IEnumerable<string>? warnings = null)
=> new MockResult(success: false, path: null, version: null, warnings: warnings, errors: errors);
public override SdkResult IndicateSuccess(string path, string? version, IEnumerable<string>? warnings = null)
=> new MockResult(success: true, path: path, version: version, warnings: warnings);
public override SdkResult IndicateSuccess(string path, string? version, IDictionary<string, string>? propertiesToAdd, IDictionary<string, SdkResultItem>? itemsToAdd, IEnumerable<string>? warnings = null)
=> new MockResult(success: true, path: path, version: version, warnings: warnings, propertiesToAdd: propertiesToAdd, itemsToAdd: itemsToAdd);
public override SdkResult IndicateSuccess(IEnumerable<string> paths, string? version,
IDictionary<string, string>? propertiesToAdd = null, IDictionary<string, SdkResultItem>? itemsToAdd = null,
IEnumerable<string>? warnings = null) => new MockResult(success: true, paths: paths, version: version, propertiesToAdd, itemsToAdd, warnings);
}
private sealed class MockResult : SdkResult
{
public MockResult(bool success, string? path, string? version, IEnumerable<string>? warnings = null,
IEnumerable<string>? errors = null, IDictionary<string, string>? propertiesToAdd = null, IDictionary<string, SdkResultItem>? itemsToAdd = null)
{
Success = success;
Path = path;
Version = version;
Warnings = warnings;
Errors = errors;
PropertiesToAdd = propertiesToAdd;
ItemsToAdd = itemsToAdd;
}
public MockResult(bool success, IEnumerable<string>? paths, string? version,
IDictionary<string, string>? propertiesToAdd, IDictionary<string, SdkResultItem>? itemsToAdd, IEnumerable<string>? warnings)
{
Success = success;
if (paths != null)
{
var firstPath = paths.FirstOrDefault();
if (firstPath != null)
{
Path = firstPath;
}
if (paths.Count() > 1)
{
AdditionalPaths = paths.Skip(1).ToList();
}
}
Version = version;
Warnings = warnings;
PropertiesToAdd = propertiesToAdd;
ItemsToAdd = itemsToAdd;
}
public override bool Success { get; protected set; }
public override string? Version { get; protected set; }
public override string? Path { get; protected set; }
public override IList<string>? AdditionalPaths { get; set; }
public override IDictionary<string, string>? PropertiesToAdd { get; protected set; }
public override IDictionary<string, SdkResultItem>? ItemsToAdd { get; protected set; }
public IEnumerable<string>? Errors { get; }
public IEnumerable<string>? Warnings { get; }
}
private sealed class MockLogger(ITestOutputHelper? logger = null) : SdkLogger
{
public override void LogMessage(string message, MessageImportance messageImportance = MessageImportance.Low)
{
logger?.WriteLine($"{messageImportance}:\t{message}");
}
}
}
}
|