| File: WindowsTrayLogTests.cs | Web Access |
| Project: src\tests\Aspire.Tray.Tests\Aspire.Tray.Tests.csproj (Aspire.Tray.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using Aspire.Tray.Tests.Helpers; namespace Aspire.Tray.Tests; public class WindowsTrayLogTests { public static bool SupportsWindows => OperatingSystem.IsWindows(); [Fact(Skip = "Uses Windows no-follow file handles.", SkipUnless = nameof(SupportsWindows))] [SupportedOSPlatform("windows")] public void CreatesMissingDirectoriesAndAppendsWithoutTruncating() { using var directory = new TestTrayStateDirectory(); var path = Path.Combine(directory.UserProfilePath, "Aspire", "Tray"); using (var first = WindowsTrayLog.OpenLog(path)) { first.Write(Encoding.UTF8.GetBytes("first\n")); } using (var second = WindowsTrayLog.OpenLog(path)) { second.Position = 0; second.Write(Encoding.UTF8.GetBytes("second\n")); } Assert.Equal("first\nsecond\n", File.ReadAllText(Path.Combine(path, "aspire-tray.log"))); } [Theory(Skip = "Uses Windows junctions.", SkipUnless = nameof(SupportsWindows))] [InlineData(0)] [InlineData(1)] [InlineData(2)] [SupportedOSPlatform("windows")] public async Task JunctionAtAnyAncestorIsRejectedWithoutWritingTheTarget(int depth) { using var directory = new TestTrayStateDirectory(); string[] components = ["profile", "Aspire", "Tray"]; var root = Directory.CreateDirectory(directory.UserProfilePath).FullName; var target = Directory.CreateDirectory(directory.LegacyDirectory).FullName; var link = root; for (var i = 0; i <= depth; i++) { link = Path.Combine(link, components[i]); } Directory.CreateDirectory(Path.GetDirectoryName(link)!); var targetLogDirectory = target; for (var i = depth + 1; i < components.Length; i++) { targetLogDirectory = Path.Combine(targetLogDirectory, components[i]); } Directory.CreateDirectory(targetLogDirectory); var sentinel = Path.Combine(targetLogDirectory, "aspire-tray.log"); File.WriteAllText(sentinel, "untouched"); var result = await TestNativeCommand.RunAsync(Path.Combine(Environment.SystemDirectory, "cmd.exe"), "/d", "/c", "mklink", "/J", link, target); Assert.True(result.ExitCode == 0, $"{result.Output}\n{result.Error}"); try { Assert.Throws<IOException>(() => WindowsTrayLog.OpenLog(Path.Combine(root, Path.Combine(components)))); Assert.Equal("untouched", File.ReadAllText(sentinel)); } finally { Directory.Delete(link); } } [Fact(Skip = "Uses Windows file symbolic links.", SkipUnless = nameof(SupportsWindows))] [SupportedOSPlatform("windows")] public void SymbolicLinkLogIsRejectedWithoutWritingTheTarget() { using var directory = new TestTrayStateDirectory(); var path = Directory.CreateDirectory(directory.UserProfilePath).FullName; var target = directory.CreateAppHost("target.txt"); File.WriteAllText(target, "untouched"); File.CreateSymbolicLink(Path.Combine(path, "aspire-tray.log"), target); Assert.Throws<IOException>(() => WindowsTrayLog.OpenLog(path)); Assert.Equal("untouched", File.ReadAllText(target)); } [Fact(Skip = "Uses Windows hard links.", SkipUnless = nameof(SupportsWindows))] [SupportedOSPlatform("windows")] public async Task HardLinkLogIsRejectedWithoutWritingTheTarget() { using var directory = new TestTrayStateDirectory(); var path = Directory.CreateDirectory(directory.UserProfilePath).FullName; var target = directory.CreateAppHost("target.txt"); File.WriteAllText(target, "untouched"); var result = await TestNativeCommand.RunAsync(Path.Combine(Environment.SystemDirectory, "cmd.exe"), "/d", "/c", "mklink", "/H", Path.Combine(path, "aspire-tray.log"), target); Assert.True(result.ExitCode == 0, $"{result.Output}\n{result.Error}"); Assert.Throws<IOException>(() => WindowsTrayLog.OpenLog(path)); Assert.Equal("untouched", File.ReadAllText(target)); } [Fact(Skip = "Uses Windows directory sharing locks.", SkipUnless = nameof(SupportsWindows))] [SupportedOSPlatform("windows")] public void AncestorCannotBeRenamedWhileItIsBeingValidated() { using var directory = new TestTrayStateDirectory(); var path = Directory.CreateDirectory(directory.UserProfilePath).FullName; using (var handle = WindowsTrayLog.OpenDirectory(path)) { Assert.Throws<IOException>(() => Directory.Move(path, directory.LegacyDirectory)); } Directory.Move(path, directory.LegacyDirectory); Assert.True(Directory.Exists(directory.LegacyDirectory)); } [Fact(Skip = "Uses Windows directory sharing locks.", SkipUnless = nameof(SupportsWindows))] [SupportedOSPlatform("windows")] public void AncestorCannotBeOpenedForReparseMutationWhileItIsBeingValidated() { using var directory = new TestTrayStateDirectory(); var path = Directory.CreateDirectory(directory.UserProfilePath).FullName; using (var handle = WindowsTrayLog.OpenDirectory(path)) { using var mutation = TestWindowsFileInterop.OpenDirectoryForReparseMutation(path); var error = Marshal.GetLastPInvokeError(); Assert.True(mutation.IsInvalid); Assert.Equal(32, error); // ERROR_SHARING_VIOLATION, not a permission failure. } using var allowed = TestWindowsFileInterop.OpenDirectoryForReparseMutation(path); Assert.False(allowed.IsInvalid); } [Fact(Skip = "Uses Windows file sharing locks.", SkipUnless = nameof(SupportsWindows))] [SupportedOSPlatform("windows")] public void OpenLogCannotBeReplacedAndContinuesWritingToItsValidatedFile() { using var directory = new TestTrayStateDirectory(); var path = directory.UserProfilePath; var log = Path.Combine(path, "aspire-tray.log"); using (var stream = WindowsTrayLog.OpenLog(path)) { Assert.Throws<IOException>(() => File.Move(log, Path.Combine(path, "old.log"))); stream.Write(Encoding.UTF8.GetBytes("original file\n")); } Assert.Equal("original file\n", File.ReadAllText(log)); } }