|
3 | 3 | *--------------------------------------------------------------------------------------------*/ |
4 | 4 |
|
5 | 5 | using System.Runtime.CompilerServices; |
6 | | -using System.Runtime.InteropServices; |
7 | 6 | using System.Text.RegularExpressions; |
8 | 7 | using Microsoft.Extensions.Logging; |
9 | 8 |
|
@@ -51,42 +50,68 @@ public static async Task<E2ETestContext> CreateAsync() |
51 | 50 | return new E2ETestContext(homeDir, workDir, proxyUrl, proxy, repoRoot); |
52 | 51 | } |
53 | 52 |
|
| 53 | + /// <summary> |
| 54 | + /// Returns a canonical path with symlinks resolved in every directory |
| 55 | + /// component. .NET has no built-in equivalent of POSIX <c>realpath</c> |
| 56 | + /// that walks all parents, so we walk the components ourselves and use |
| 57 | + /// <see cref="DirectoryInfo.ResolveLinkTarget(bool)"/> on each one. |
| 58 | + /// On Windows, where the test temp paths don't traverse symlinks, |
| 59 | + /// <see cref="Path.GetFullPath(string)"/> is sufficient. |
| 60 | + /// </summary> |
54 | 61 | private static string ResolveSymlinks(string path) |
55 | 62 | { |
56 | 63 | if (OperatingSystem.IsWindows()) |
57 | 64 | { |
58 | 65 | return Path.GetFullPath(path); |
59 | 66 | } |
60 | 67 |
|
61 | | - IntPtr resolved = IntPtr.Zero; |
62 | 68 | try |
63 | 69 | { |
64 | | - resolved = NativeRealpath(path, IntPtr.Zero); |
65 | | - if (resolved == IntPtr.Zero) |
| 70 | + var fullPath = Path.GetFullPath(path); |
| 71 | + var root = Path.GetPathRoot(fullPath); |
| 72 | + if (string.IsNullOrEmpty(root)) |
66 | 73 | { |
67 | | - return Path.GetFullPath(path); |
| 74 | + return fullPath; |
68 | 75 | } |
69 | | - return Marshal.PtrToStringAnsi(resolved) ?? Path.GetFullPath(path); |
| 76 | + |
| 77 | + var components = fullPath |
| 78 | + .Substring(root.Length) |
| 79 | + .Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); |
| 80 | + |
| 81 | + var resolved = root; |
| 82 | + foreach (var component in components) |
| 83 | + { |
| 84 | + resolved = Path.Combine(resolved, component); |
| 85 | + try |
| 86 | + { |
| 87 | + var info = new DirectoryInfo(resolved); |
| 88 | + if (info.Exists && info.LinkTarget != null) |
| 89 | + { |
| 90 | + var target = info.ResolveLinkTarget(returnFinalTarget: true); |
| 91 | + if (target != null && !string.IsNullOrEmpty(target.FullName)) |
| 92 | + { |
| 93 | + resolved = target.FullName; |
| 94 | + } |
| 95 | + } |
| 96 | + } |
| 97 | + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) |
| 98 | + { |
| 99 | + // Component we can't inspect; keep what we have and continue. |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + return resolved; |
70 | 104 | } |
71 | | - catch |
| 105 | + catch (Exception ex) when (ex is IOException |
| 106 | + or UnauthorizedAccessException |
| 107 | + or ArgumentException |
| 108 | + or NotSupportedException |
| 109 | + or PathTooLongException) |
72 | 110 | { |
73 | 111 | return Path.GetFullPath(path); |
74 | 112 | } |
75 | | - finally |
76 | | - { |
77 | | - if (resolved != IntPtr.Zero) |
78 | | - { |
79 | | - NativeFree(resolved); |
80 | | - } |
81 | | - } |
82 | 113 | } |
83 | 114 |
|
84 | | - [DllImport("libc", EntryPoint = "realpath", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] |
85 | | - private static extern IntPtr NativeRealpath([MarshalAs(UnmanagedType.LPStr)] string path, IntPtr resolved); |
86 | | - |
87 | | - [DllImport("libc", EntryPoint = "free")] |
88 | | - private static extern void NativeFree(IntPtr ptr); |
89 | | - |
90 | 115 | private static string FindRepoRoot() |
91 | 116 | { |
92 | 117 | var dir = new DirectoryInfo(AppContext.BaseDirectory); |
|
0 commit comments