From ed1897ef8a9ca55e3555e070ae58946c6343d7db Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 22 Apr 2021 17:41:26 -0700 Subject: [PATCH] Fix symbolic link tests (#51712) * 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 * Test still failing on Mac, skip until I can investigate --- .../AppHostUsedWithSymbolicLinks.cs | 55 +++++++-------- .../tests/TestUtils/SymbolicLinking.cs | 70 ++++++++++++------- 2 files changed, 67 insertions(+), 58 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..991eff791deee 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() @@ -93,13 +87,13 @@ public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePa //[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 */) + 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 symlinkRelativePath = string.Empty; var fixture = sharedTestState.FrameworkDependentAppFixture_Published .Copy(); @@ -108,10 +102,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) @@ -122,7 +115,7 @@ public void Run_framework_dependent_app_behind_symlink(/* string symlinkRelative } [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).")] + "CI failing to use stat on symbolic links on Linux (permission denied).")] public void Run_framework_dependent_app_with_runtime_behind_symlink() { // Creating symbolic links requires administrative privilege on Windows, so skip test. @@ -137,9 +130,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 +155,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 +179,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 +203,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 +224,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 +257,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); } }