From 416bc26c4f62e758df42889ea9767a2ccdad1c66 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 20 Apr 2021 15:35:40 -0700 Subject: [PATCH] Fix symbolic link tests Use IDisposable and `File.Delete` to ensure that symlinks get created and cleaned up properly on all platforms. If all symlinks aren't deleted, the later test cleanup phases have problems deleting them. Fixes #51502 --- .../AppHostUsedWithSymbolicLinks.cs | 64 +++++++---------- .../tests/TestUtils/SymbolicLinking.cs | 70 ++++++++++++------- 2 files changed, 69 insertions(+), 65 deletions(-) diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.AppHost.Tests/AppHostUsedWithSymbolicLinks.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.AppHost.Tests/AppHostUsedWithSymbolicLinks.cs index f5e7a32a440b5..c0b6667d2b347 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.AppHost.Tests/AppHostUsedWithSymbolicLinks.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.AppHost.Tests/AppHostUsedWithSymbolicLinks.cs @@ -16,12 +16,6 @@ public class AppHostUsedWithSymbolicLinks : IClassFixture apphost - string secondSymbolicLink = Path.Combine(testDir, secondSymlinkRelativePath); - Directory.CreateDirectory(Path.GetDirectoryName(secondSymbolicLink)); - CreateSymbolicLink(secondSymbolicLink, appExe); + string symlink2Path = Path.Combine(testDir, secondSymlinkRelativePath); + Directory.CreateDirectory(Path.GetDirectoryName(symlink2Path)); + using var symlink2 = new SymLink(symlink2Path, appExe); // first symlink -> second symlink - string firstSymbolicLink = Path.Combine(testDir, firstSymlinkRelativePath); - Directory.CreateDirectory(Path.GetDirectoryName(firstSymbolicLink)); - CreateSymbolicLink(firstSymbolicLink, secondSymbolicLink); + string symlink1Path = Path.Combine(testDir, firstSymlinkRelativePath); + Directory.CreateDirectory(Path.GetDirectoryName(symlink1Path)); + using var symlink1 = new SymLink(symlink1Path, symlink2Path); - Command.Create(firstSymbolicLink) + Command.Create(symlink1.SrcPath) .CaptureStdErr() .CaptureStdOut() .Execute() @@ -88,19 +82,15 @@ public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePa .And.HaveStdOutContaining("Hello World"); } - //[Theory] - //[InlineData("a/b/SymlinkToFrameworkDependentApp")] - //[InlineData("a/SymlinkToFrameworkDependentApp")] - [Fact(Skip = "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " + - "CI failing to use stat on symbolic links on Linux (permission denied).")] - public void Run_framework_dependent_app_behind_symlink(/* string symlinkRelativePath */) + [Theory] + [InlineData("a/b/SymlinkToFrameworkDependentApp")] + [InlineData("a/SymlinkToFrameworkDependentApp")] + public void Run_framework_dependent_app_behind_symlink(string symlinkRelativePath) { // Creating symbolic links requires administrative privilege on Windows, so skip test. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; - string symlinkRelativePath = string.Empty; - var fixture = sharedTestState.FrameworkDependentAppFixture_Published .Copy(); @@ -108,10 +98,9 @@ public void Run_framework_dependent_app_behind_symlink(/* string symlinkRelative var builtDotnet = fixture.BuiltDotnet.BinPath; var testDir = Directory.GetParent(fixture.TestProject.Location).ToString(); Directory.CreateDirectory(Path.Combine(testDir, Path.GetDirectoryName(symlinkRelativePath))); - var symlinkFullPath = Path.Combine(testDir, symlinkRelativePath); - CreateSymbolicLink(symlinkFullPath, appExe); - Command.Create(symlinkFullPath) + using var symlink = new SymLink(Path.Combine(testDir, symlinkRelativePath), appExe); + Command.Create(symlink.SrcPath) .CaptureStdErr() .CaptureStdOut() .EnvironmentVariable("DOTNET_ROOT", builtDotnet) @@ -121,8 +110,7 @@ public void Run_framework_dependent_app_behind_symlink(/* string symlinkRelative .And.HaveStdOutContaining("Hello World"); } - [Fact(Skip = "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " + - "CI failing to use stat on symbolic links on Linux (permission denied).")] + [Fact] public void Run_framework_dependent_app_with_runtime_behind_symlink() { // Creating symbolic links requires administrative privilege on Windows, so skip test. @@ -137,9 +125,9 @@ public void Run_framework_dependent_app_with_runtime_behind_symlink() var dotnetSymlink = Path.Combine(testDir, "dotnet"); var dotnetDir = fixture.BuiltDotnet.BinPath; - CreateSymbolicLink(dotnetSymlink, dotnetDir); + using var symlink = new SymLink(dotnetSymlink, dotnetDir); Command.Create(appExe) - .EnvironmentVariable("DOTNET_ROOT", dotnetSymlink) + .EnvironmentVariable("DOTNET_ROOT", symlink.SrcPath) .CaptureStdErr() .CaptureStdOut() .Execute() @@ -162,7 +150,7 @@ public void Put_app_directory_behind_symlink() var binDirNewPath = Path.Combine(Directory.GetParent(fixture.TestProject.Location).ToString(), "PutTheBinDirSomewhereElse"); Directory.Move(binDir, binDirNewPath); - CreateSymbolicLink(binDir, binDirNewPath); + using var symlink = new SymLink(binDir, binDirNewPath); Command.Create(appExe) .CaptureStdErr() .CaptureStdOut() @@ -186,8 +174,8 @@ public void Put_dotnet_behind_symlink() var testDir = Directory.GetParent(fixture.TestProject.Location).ToString(); var dotnetSymlink = Path.Combine(testDir, "dotnet"); - CreateSymbolicLink(dotnetSymlink, dotnetExe); - Command.Create(dotnetSymlink, fixture.TestProject.AppDll) + using var symlink = new SymLink(dotnetSymlink, dotnetExe); + Command.Create(symlink.SrcPath, fixture.TestProject.AppDll) .CaptureStdErr() .CaptureStdOut() .Execute() @@ -210,7 +198,7 @@ public void Put_app_directory_behind_symlink_and_use_dotnet() var binDirNewPath = Path.Combine(Directory.GetParent(fixture.TestProject.Location).ToString(), "PutTheBinDirSomewhereElse"); Directory.Move(binDir, binDirNewPath); - CreateSymbolicLink(binDir, binDirNewPath); + using var symlink = new SymLink(binDir, binDirNewPath); dotnet.Exec(fixture.TestProject.AppDll) .CaptureStdErr() .CaptureStdOut() @@ -231,10 +219,10 @@ public void Put_app_directory_behind_symlink_and_use_dotnet_run() var dotnet = fixture.SdkDotnet; var binDir = fixture.TestProject.OutputDirectory; - var binDirNewPath = Path.Combine(Directory.GetParent(fixture.TestProject.Location).ToString(), "PutTheBinDirSomewhereElse"); + var binDirNewPath = Path.Combine(Directory.GetParent(fixture.TestProject.Location).ToString(), "PutTheBinDirSomewhereElse"); Directory.Move(binDir, binDirNewPath); - CreateSymbolicLink(binDir, binDirNewPath); + using var symlink = new SymLink(binDir, binDirNewPath); dotnet.Exec("run") .WorkingDirectory(fixture.TestProject.Location) .CaptureStdErr() @@ -264,12 +252,12 @@ public void Put_satellite_assembly_behind_symlink() var firstSatelliteDir = Directory.GetDirectories(binDir).Single(dir => dir.Contains("kn-IN")); var firstSatelliteNewDir = Path.Combine(satellitesDir, "kn-IN"); Directory.Move(firstSatelliteDir, firstSatelliteNewDir); - CreateSymbolicLink(firstSatelliteDir, firstSatelliteNewDir); + using var symlink1 = new SymLink(firstSatelliteDir, firstSatelliteNewDir); var secondSatelliteDir = Directory.GetDirectories(binDir).Single(dir => dir.Contains("ta-IN")); var secondSatelliteNewDir = Path.Combine(satellitesDir, "ta-IN"); Directory.Move(secondSatelliteDir, secondSatelliteNewDir); - CreateSymbolicLink(secondSatelliteDir, secondSatelliteNewDir); + using var symlink2 = new SymLink(secondSatelliteDir, secondSatelliteNewDir); Command.Create(appExe) .CaptureStdErr() diff --git a/src/installer/tests/TestUtils/SymbolicLinking.cs b/src/installer/tests/TestUtils/SymbolicLinking.cs index 564a25f714650..81ed64bf115d7 100644 --- a/src/installer/tests/TestUtils/SymbolicLinking.cs +++ b/src/installer/tests/TestUtils/SymbolicLinking.cs @@ -2,63 +2,79 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.IO; using System.Runtime.InteropServices; +#nullable enable + namespace Microsoft.DotNet.CoreSetup.Test { - public static class SymbolicLinking + public sealed class SymLink : IDisposable { - static class Kernel32 + public string SrcPath { get; private set; } + public SymLink(string src, string dest) { - [Flags] - internal enum SymbolicLinkFlag + if (!MakeSymbolicLink(src, dest, out var errorMessage)) { - IsFile = 0x0, - IsDirectory = 0x1, - AllowUnprivilegedCreate = 0x2 + throw new IOException($"Error creating symbolic link at {src} pointing to {dest}: {errorMessage}"); } - - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern bool CreateSymbolicLink( - string symbolicLinkName, - string targetFileName, - SymbolicLinkFlag flags); + SrcPath = src; } - static class libc + public void Dispose() { - [DllImport("libc", SetLastError = true)] - internal static extern int symlink( - string targetFileName, - string linkPath); - - [DllImport("libc", CharSet = CharSet.Ansi)] - internal static extern IntPtr strerror(int errnum); + if (SrcPath is not null) + { + File.Delete(SrcPath); + SrcPath = null!; + } } - public static bool MakeSymbolicLink(string symbolicLinkName, string targetFileName, out string errorMessage) + private static bool MakeSymbolicLink(string symbolicLinkName, string targetFileName, out string errorMessage) { errorMessage = string.Empty; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (!Kernel32.CreateSymbolicLink(symbolicLinkName, targetFileName, Kernel32.SymbolicLinkFlag.IsFile)) + if (!CreateSymbolicLink(symbolicLinkName, targetFileName, SymbolicLinkFlag.IsFile)) { int errno = Marshal.GetLastWin32Error(); errorMessage = $"CreateSymbolicLink failed with error number {errno}"; return false; } } - else + else { - if (libc.symlink(targetFileName, symbolicLinkName) == -1) + if (symlink(targetFileName, symbolicLinkName) == -1) { int errno = Marshal.GetLastWin32Error(); - errorMessage = Marshal.PtrToStringAnsi(libc.strerror(errno)); - return false; + errorMessage = Marshal.PtrToStringAnsi(strerror(errno))!; + return false; } } return true; } + + [Flags] + private enum SymbolicLinkFlag + { + IsFile = 0x0, + IsDirectory = 0x1, + AllowUnprivilegedCreate = 0x2 + } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CreateSymbolicLink( + string symbolicLinkName, + string targetFileName, + SymbolicLinkFlag flags); + + [DllImport("libc", SetLastError = true)] + private static extern int symlink( + string targetFileName, + string linkPath); + + [DllImport("libc", CharSet = CharSet.Ansi)] + private static extern IntPtr strerror(int errnum); } }