From e50e48579d6e71f8ef4730b6e2dcb9038eaf1336 Mon Sep 17 00:00:00 2001 From: Marcin Gemra Date: Wed, 5 Nov 2025 13:44:38 +0100 Subject: [PATCH 1/6] SNOW-2026116 Remove Mono.Unix from Windows dependencies --- CHANGELOG.md | 2 + README.md | 18 ++ .../ExternalBrowserAuthenticator.cs | 4 +- .../PlatformShims/MonoUnixWindowsShim.cs | 186 ++++++++++++++++++ Snowflake.Data/Snowflake.Data.csproj | 26 ++- 5 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a1d1d24..3a149f125 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ #### For the official .NET Release Notes please refer to https://docs.snowflake.com/en/release-notes/clients-drivers/dotnet # Changelog +- v5.2.0 + - Added multi-targeting support to eliminate prerelease Mono.Unix dependency on Windows .NET 5+. - v5.1.0 - Added `APPLICATION_PATH` to `CLIENT_ENVIRONMENT` sent during authentication to identify the application connecting to Snowflake. - Renew idle sessions in the pool if keep alive is enabled. diff --git a/README.md b/README.md index 4abef6b51..249c9fdd3 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,24 @@ The Snowflake .NET connector supports the following .NET framework and libraries Disclaimer: While the connector targets netstandard2.0 and may work with versions in its [support matrix](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0#select-net-standard-version), only the versions listed above are supported and tested by the connector +## Target Frameworks and Platform-Specific Builds + +Starting from version **5.2.0**, the Snowflake .NET connector uses multi-targeting to provide optimized builds for different platforms: + +| Target Framework | Platform | Description | +|------------------|----------|-------------------------------------------------------------| +| `net5.0-windows` | Windows (.NET 5+) | Optimized build for Windows without Mono.Unix | +| `net5.0` | Linux, macOS (.NET 5+) | Full Unix file system support with Mono.Unix | +| `netstandard2.0` | All platforms | Backward compatibility for older .NET versions | + +**What this means for you:** + +- **Windows users** on .NET 5 or higher will automatically receive a build without the prerelease `Mono.Unix` dependency, resulting in a cleaner dependency tree. +- **Linux and macOS users** on .NET 5 or higher will receive a build with full Unix file permissions support through `Mono.Unix`. +- **Older .NET versions** will continue to use the `netstandard2.0` build for maximum compatibility. + +The appropriate build is automatically selected by NuGet based on your application's target framework and operating system. + Please refer to the [Notice](#notice) section below for information about safe usage of the .NET Driver # Coding conventions for the project diff --git a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs index 139526316..104689932 100644 --- a/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/ExternalBrowserAuthenticator.cs @@ -103,7 +103,7 @@ public async Task AuthenticateAsync(CancellationToken cancellationToken) } else { - throw e; + throw; } } } @@ -141,7 +141,7 @@ public void Authenticate() } else { - throw e; + throw; } } } diff --git a/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs b/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs new file mode 100644 index 000000000..94aa93007 --- /dev/null +++ b/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs @@ -0,0 +1,186 @@ +// This file is compiled ONLY on Windows (when WINDOWS_BUILD is defined) +// It provides stub implementations of Mono.Unix types that throw PlatformNotSupportedException +// since Unix-specific operations should never be called on Windows (protected by runtime guards) + +#if WINDOWS_BUILD + +using System; +using System.IO; +using Microsoft.Win32.SafeHandles; + +namespace Mono.Unix +{ + [Flags] + internal enum FileAccessPermissions + { + None = 0, + OtherExecute = 1, + OtherWrite = 2, + OtherRead = 4, + OtherReadWriteExecute = 7, + GroupExecute = 8, + GroupWrite = 16, + GroupRead = 32, + GroupReadWriteExecute = 56, + UserExecute = 64, + UserWrite = 128, + UserRead = 256, + UserReadWrite = 384, + UserReadWriteExecute = 448, + AllPermissions = 511, + DefaultPermissions = 420 + } + + internal class UnixStream : Stream + { + private const string ErrorMessage = "Unix file operations are not supported on Windows"; + + public UnixUserInfo OwnerUser => throw new PlatformNotSupportedException(ErrorMessage); + public UnixGroupInfo OwnerGroup => throw new PlatformNotSupportedException(ErrorMessage); + public long OwnerUserId => throw new PlatformNotSupportedException(ErrorMessage); + public long OwnerGroupId => throw new PlatformNotSupportedException(ErrorMessage); + public FileAccessPermissions FileAccessPermissions => throw new PlatformNotSupportedException(ErrorMessage); + + public override bool CanRead => throw new PlatformNotSupportedException(ErrorMessage); + public override bool CanSeek => throw new PlatformNotSupportedException(ErrorMessage); + public override bool CanWrite => throw new PlatformNotSupportedException(ErrorMessage); + public override long Length => throw new PlatformNotSupportedException(ErrorMessage); + public override long Position + { + get => throw new PlatformNotSupportedException(ErrorMessage); + set => throw new PlatformNotSupportedException(ErrorMessage); + } + + public override void Flush() => throw new PlatformNotSupportedException(ErrorMessage); + public override int Read(byte[] buffer, int offset, int count) => throw new PlatformNotSupportedException(ErrorMessage); + public override long Seek(long offset, SeekOrigin origin) => throw new PlatformNotSupportedException(ErrorMessage); + public override void SetLength(long value) => throw new PlatformNotSupportedException(ErrorMessage); + public override void Write(byte[] buffer, int offset, int count) => throw new PlatformNotSupportedException(ErrorMessage); + } + + internal class UnixFileInfo + { + private const string ErrorMessage = "Unix file operations are not supported on Windows"; + + public UnixFileInfo(string path) + { + throw new PlatformNotSupportedException(ErrorMessage); + } + + public string FullName => throw new PlatformNotSupportedException(ErrorMessage); + public bool Exists => throw new PlatformNotSupportedException(ErrorMessage); + public FileAccessPermissions FileAccessPermissions => throw new PlatformNotSupportedException(ErrorMessage); + public long OwnerUserId => throw new PlatformNotSupportedException(ErrorMessage); + public UnixUserInfo OwnerUser => throw new PlatformNotSupportedException(ErrorMessage); + public long Length => throw new PlatformNotSupportedException(ErrorMessage); + + public Stream Create(FileAccessPermissions permissions) + => throw new PlatformNotSupportedException(ErrorMessage); + + public UnixStream OpenRead() + => throw new PlatformNotSupportedException(ErrorMessage); + + public UnixStream Open(FileMode mode, FileAccess access, Native.FilePermissions permissions) + => throw new PlatformNotSupportedException(ErrorMessage); + } + + internal class UnixDirectoryInfo + { + private const string ErrorMessage = "Unix directory operations are not supported on Windows"; + + public UnixDirectoryInfo(string path) + { + throw new PlatformNotSupportedException(ErrorMessage); + } + + public string FullName => throw new PlatformNotSupportedException(ErrorMessage); + public bool Exists => throw new PlatformNotSupportedException(ErrorMessage); + public FileAccessPermissions FileAccessPermissions => throw new PlatformNotSupportedException(ErrorMessage); + public long OwnerUserId => throw new PlatformNotSupportedException(ErrorMessage); + public UnixUserInfo OwnerUser => throw new PlatformNotSupportedException(ErrorMessage); + + public void Create(FileAccessPermissions permissions) + => throw new PlatformNotSupportedException(ErrorMessage); + } + + internal class UnixUserInfo + { + private const string ErrorMessage = "Unix user operations are not supported on Windows"; + + public UnixUserInfo(long userId) + { + throw new PlatformNotSupportedException(ErrorMessage); + } + + public long UserId => throw new PlatformNotSupportedException(ErrorMessage); + } + + internal class UnixGroupInfo + { + private const string ErrorMessage = "Unix group operations are not supported on Windows"; + + public UnixGroupInfo(long groupId) + { + throw new PlatformNotSupportedException(ErrorMessage); + } + + public long GroupId => throw new PlatformNotSupportedException(ErrorMessage); + } +} + +namespace Mono.Unix.Native +{ + [Flags] + internal enum FilePermissions + { + S_ISUID = 0x0800, + S_ISGID = 0x0400, + S_ISVTX = 0x0200, + S_IRUSR = 0x0100, + S_IWUSR = 0x0080, + S_IXUSR = 0x0040, + S_IRGRP = 0x0020, + S_IWGRP = 0x0010, + S_IXGRP = 0x0008, + S_IROTH = 0x0004, + S_IWOTH = 0x0002, + S_IXOTH = 0x0001, + + S_IRWXU = S_IRUSR | S_IWUSR | S_IXUSR, + S_IRWXG = S_IRGRP | S_IWGRP | S_IXGRP, + S_IRWXO = S_IROTH | S_IWOTH | S_IXOTH, + + ACCESSPERMS = S_IRWXU | S_IRWXG | S_IRWXO, + ALLPERMS = S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO, + DEFFILEMODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH + } + + internal static class Syscall + { + private const string ErrorMessage = "Unix syscalls are not supported on Windows"; + + public static int mkdir(string path, FilePermissions permissions) + => throw new PlatformNotSupportedException(ErrorMessage); + + public static long chown(string path, int userId, int groupId) + => throw new PlatformNotSupportedException(ErrorMessage); + + public static long chmod(string path, FilePermissions permissions) + => throw new PlatformNotSupportedException(ErrorMessage); + + public static long getuid() + => throw new PlatformNotSupportedException(ErrorMessage); + + public static long getgid() + => throw new PlatformNotSupportedException(ErrorMessage); + + public static long geteuid() + => throw new PlatformNotSupportedException(ErrorMessage); + + public static long getegid() + => throw new PlatformNotSupportedException(ErrorMessage); + } +} + +#endif + diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj index 883e2296f..349c086c8 100644 --- a/Snowflake.Data/Snowflake.Data.csproj +++ b/Snowflake.Data/Snowflake.Data.csproj @@ -1,6 +1,8 @@  - netstandard2.0 + + + netstandard2.0;net481;net8.0;net8.0-windows Snowflake.Data Snowflake.Data Apache-2.0 @@ -18,6 +20,11 @@ 8 + + + $(DefineConstants);WINDOWS_BUILD + + @@ -25,7 +32,6 @@ - @@ -33,6 +39,22 @@ + + + + + + + + + + + + + + + + From bf848273d9858a412a1ab75985338936985608cb Mon Sep 17 00:00:00 2001 From: Marcin Gemra Date: Thu, 13 Nov 2025 17:29:50 +0100 Subject: [PATCH 2/6] Update the docs --- CHANGELOG.md | 2 +- README.md | 12 +- .../PlatformShims/MonoUnixWindowsShim.cs | 5 +- Snowflake.Data/Snowflake.Data.csproj | 176 +++++++++--------- 4 files changed, 101 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43785a349..9414a90a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # Changelog - v5.2.0 - - Added multi-targeting support to eliminate prerelease Mono.Unix dependency on Windows .NET 5+. + - Added multi-targeting support. The appropriate build is selected by NuGet based on target framework and OS. - Fixed CRL validation to reject newly downloaded CRLs if their NextUpdate has already expired. - v5.1.0 - Added `APPLICATION_PATH` to `CLIENT_ENVIRONMENT` sent during authentication to identify the application connecting to Snowflake. diff --git a/README.md b/README.md index 249c9fdd3..4ba48bd43 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,17 @@ Starting from version **5.2.0**, the Snowflake .NET connector uses multi-targeti | Target Framework | Platform | Description | |------------------|----------|-------------------------------------------------------------| -| `net5.0-windows` | Windows (.NET 5+) | Optimized build for Windows without Mono.Unix | -| `net5.0` | Linux, macOS (.NET 5+) | Full Unix file system support with Mono.Unix | +| `net481` | Windows (.NET Framework 4.8.1) | Optimized build for Windows .NET Framework without Mono.Unix | +| `net8.0-windows` | Windows (.NET 8+) | Optimized build for Windows .NET 8+ without Mono.Unix | +| `net8.0` | Linux, macOS (.NET 8+) | Full Unix file system support with Mono.Unix | | `netstandard2.0` | All platforms | Backward compatibility for older .NET versions | **What this means for you:** -- **Windows users** on .NET 5 or higher will automatically receive a build without the prerelease `Mono.Unix` dependency, resulting in a cleaner dependency tree. -- **Linux and macOS users** on .NET 5 or higher will receive a build with full Unix file permissions support through `Mono.Unix`. -- **Older .NET versions** will continue to use the `netstandard2.0` build for maximum compatibility. +- **Windows users** on .NET Framework 4.8.1 will receive the `net481` build without the `Mono.Unix` dependency. +- **Windows users** on .NET 8 or higher will receive the `net8.0-windows` build without the `Mono.Unix` dependency. +- **Linux and macOS users** on .NET 8 or higher will receive the `net8.0`. +- **Older .NET versions** (including older .NET Framework and .NET versions) will use the `netstandard2.0` build for backward compatibility. The appropriate build is automatically selected by NuGet based on your application's target framework and operating system. diff --git a/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs b/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs index 94aa93007..32afe68d8 100644 --- a/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs +++ b/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs @@ -11,7 +11,7 @@ namespace Mono.Unix { [Flags] - internal enum FileAccessPermissions + public enum FileAccessPermissions { None = 0, OtherExecute = 1, @@ -162,6 +162,9 @@ internal static class Syscall public static int mkdir(string path, FilePermissions permissions) => throw new PlatformNotSupportedException(ErrorMessage); + public static int creat(string path, FilePermissions permissions) + => throw new PlatformNotSupportedException(ErrorMessage); + public static long chown(string path, int userId, int groupId) => throw new PlatformNotSupportedException(ErrorMessage); diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj index 349c086c8..7f497454f 100644 --- a/Snowflake.Data/Snowflake.Data.csproj +++ b/Snowflake.Data/Snowflake.Data.csproj @@ -1,87 +1,89 @@ - - - - - netstandard2.0;net481;net8.0;net8.0-windows - Snowflake.Data - Snowflake.Data - Apache-2.0 - https://github.com/snowflakedb/snowflake-connector-net - true - https://raw.githubusercontent.com/snowflakedb/snowflake-connector-net/master/Snowflake.Data/snowflake.ico - https://github.com/snowflakedb/snowflake-connector-net - git - Snowflake Connector for .NET - Snowflake Computing, Inc - Snowflake Connector for .NET - Snowflake - 5.1.0 - Full - 8 - - - - - $(DefineConstants);WINDOWS_BUILD - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - full - True - - - - full - True - - - - true - true - $(Version) - - - - $(DefineConstants);$(DefineAdditionalConstants) - - - - - - + + + netstandard2.0;net481;net8.0;net8.0-windows + Snowflake.Data + Snowflake.Data + Apache-2.0 + https://github.com/snowflakedb/snowflake-connector-net + true + https://raw.githubusercontent.com/snowflakedb/snowflake-connector-net/master/Snowflake.Data/snowflake.ico + https://github.com/snowflakedb/snowflake-connector-net + git + Snowflake Connector for .NET + Snowflake Computing, Inc + Snowflake Connector for .NET + Snowflake + 5.1.0 + Full + 8 + + + + + $(DefineConstants);WINDOWS_BUILD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + full + True + + + + full + True + + + + true + true + $(Version) + + + + $(DefineConstants);$(DefineAdditionalConstants) + + + + + + + + + + From 59d3c5fc4624968cb4d07bd86fd94c6675049417 Mon Sep 17 00:00:00 2001 From: Marcin Gemra Date: Fri, 14 Nov 2025 12:45:31 +0100 Subject: [PATCH 3/6] Exclude unix operations tests on Windows --- .../UnitTests/Tools/UnixOperationsTest.cs | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs b/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs index 1fd8817c6..2a2eefb77 100644 --- a/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs @@ -18,6 +18,7 @@ namespace Snowflake.Data.Tests.UnitTests.Tools { [TestFixture, NonParallelizable] + [Platform(Exclude = "Win")] public class UnixOperationsTest { private static UnixOperations s_unixOperations; @@ -26,8 +27,6 @@ public class UnixOperationsTest [OneTimeSetUp] public static void BeforeAll() { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return; if (!Directory.Exists(s_workingDirectory)) { Directory.CreateDirectory(s_workingDirectory); @@ -38,13 +37,10 @@ public static void BeforeAll() [OneTimeTearDown] public static void AfterAll() { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return; Directory.Delete(s_workingDirectory, true); } [Test] - [Platform(Exclude = "Win")] public void TestDetectGroupOrOthersWritablePermissions( [ValueSource(nameof(GroupOrOthersWritablePermissions))] FileAccessPermissions groupOrOthersWritablePermissions, [ValueSource(nameof(GroupNotWritablePermissions))] FileAccessPermissions groupNotWritablePermissions, @@ -65,7 +61,6 @@ public void TestDetectGroupOrOthersWritablePermissions( } [Test] - [Platform(Exclude = "Win")] public void TestDetectGroupOrOthersNotWritablePermissions( [ValueSource(nameof(UserPermissions))] FileAccessPermissions userPermissions, [ValueSource(nameof(GroupNotWritablePermissions))] FileAccessPermissions groupNotWritablePermissions, @@ -84,7 +79,6 @@ public void TestDetectGroupOrOthersNotWritablePermissions( } [Test] - [Platform(Exclude = "Win")] public void TestReadAllTextCheckingPermissionsUsingTomlConfigurationFileValidations( [ValueSource(nameof(UserAllowedPermissions))] FileAccessPermissions userAllowedPermissions) { @@ -100,7 +94,6 @@ public void TestReadAllTextCheckingPermissionsUsingTomlConfigurationFileValidati } [Test] - [Platform(Exclude = "Win")] public void TestSkipReadPermissionsWhenSkipIsEnabled() { var logsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); @@ -127,7 +120,6 @@ public void TestSkipReadPermissionsWhenSkipIsEnabled() } [Test] - [Platform(Exclude = "Win")] public void TestCheckReadPermissionsWhenSkipIsDisabled() { var logsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); @@ -154,7 +146,6 @@ public void TestCheckReadPermissionsWhenSkipIsDisabled() } [Test] - [Platform(Exclude = "Win")] public void TestWriteAllTextCheckingPermissionsUsingSFCredentialManagerFileValidations( [ValueSource(nameof(UserAllowedWritePermissions))] FileAccessPermissions userAllowedPermissions) { @@ -167,7 +158,6 @@ public void TestWriteAllTextCheckingPermissionsUsingSFCredentialManagerFileValid } [Test] - [Platform(Exclude = "Win")] public void TestFailIfGroupOrOthersHavePermissionsToFileWithTomlConfigurationValidations([ValueSource(nameof(UserReadWritePermissions))] FileAccessPermissions userPermissions, [ValueSource(nameof(GroupPermissions))] FileAccessPermissions groupPermissions, [ValueSource(nameof(OthersPermissions))] FileAccessPermissions othersPermissions) @@ -190,7 +180,6 @@ public void TestFailIfGroupOrOthersHavePermissionsToFileWithTomlConfigurationVal } [Test] - [Platform(Exclude = "Win")] public void TestFailIfGroupOrOthersHavePermissionsToFileWhileWritingWithUnixValidationsForCredentialManagerFile([ValueSource(nameof(UserReadWritePermissions))] FileAccessPermissions userPermissions, [ValueSource(nameof(GroupPermissions))] FileAccessPermissions groupPermissions, [ValueSource(nameof(OthersPermissions))] FileAccessPermissions othersPermissions) @@ -211,7 +200,6 @@ public void TestFailIfGroupOrOthersHavePermissionsToFileWhileWritingWithUnixVali } [Test] - [Platform(Exclude = "Win")] public void TestFailIfGroupOrOthersHavePermissionsToFileWhileWritingWithUnixValidationsForLogFile([ValueSource(nameof(UserReadWritePermissions))] FileAccessPermissions userPermissions, [ValueSource(nameof(GroupPermissions))] FileAccessPermissions groupPermissions, [ValueSource(nameof(OthersPermissions))] FileAccessPermissions othersPermissions) @@ -231,7 +219,6 @@ public void TestFailIfGroupOrOthersHavePermissionsToFileWhileWritingWithUnixVali } [Test] - [Platform(Exclude = "Win")] public void TestCreateFileWithUserRwPermissions() { // arrange @@ -247,7 +234,6 @@ public void TestCreateFileWithUserRwPermissions() } [Test] - [Platform(Exclude = "Win")] public void TestCreateDirectoryWithUserRwxPermissions() { // arrange @@ -263,7 +249,6 @@ public void TestCreateDirectoryWithUserRwxPermissions() } [Test] - [Platform(Exclude = "Win")] public void TestNestedDir() { // arrange @@ -279,7 +264,6 @@ public void TestNestedDir() } [Test] - [Platform(Exclude = "Win")] public void TestReadBytesFromEmptyFile() { // arrange @@ -294,7 +278,6 @@ public void TestReadBytesFromEmptyFile() } [Test] - [Platform(Exclude = "Win")] public void TestReadBytesFromSmallFile() { // arrange @@ -311,7 +294,6 @@ public void TestReadBytesFromSmallFile() } [Test] - [Platform(Exclude = "Win")] public void TestReadBytesFromLargeFile() { // arrange From 86de02e06fee6444821dc85cca05e4d401342784 Mon Sep 17 00:00:00 2001 From: Marcin Gemra Date: Fri, 14 Nov 2025 13:18:16 +0100 Subject: [PATCH 4/6] Split Windows/Unix tests --- ...tionsTest.cs => FileOperationsUnixTest.cs} | 49 +++++---------- .../Tools/FileOperationsWindowsTest.cs | 61 +++++++++++++++++++ .../UnitTests/Tools/UnixOperationsTest.cs | 10 +++ 3 files changed, 85 insertions(+), 35 deletions(-) rename Snowflake.Data.Tests/UnitTests/Tools/{FileOperationsTest.cs => FileOperationsUnixTest.cs} (87%) create mode 100644 Snowflake.Data.Tests/UnitTests/Tools/FileOperationsWindowsTest.cs diff --git a/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsTest.cs b/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsUnixTest.cs similarity index 87% rename from Snowflake.Data.Tests/UnitTests/Tools/FileOperationsTest.cs rename to Snowflake.Data.Tests/UnitTests/Tools/FileOperationsUnixTest.cs index 614e46577..aeb129b3a 100644 --- a/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsUnixTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Security; using Mono.Unix; using Mono.Unix.Native; @@ -12,7 +13,8 @@ namespace Snowflake.Data.Tests.UnitTests.Tools { [TestFixture, NonParallelizable] - public class FileOperationsTest + [Platform(Exclude = "Win")] + public class FileOperationsUnixTest { private static FileOperations s_fileOperations; private static readonly string s_relativeWorkingDirectory = $"file_operations_test_{Path.GetRandomFileName()}"; @@ -23,6 +25,11 @@ public class FileOperationsTest [SetUp] public static void Before() { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("Unix-specific tests are not run on Windows"); + } + if (!Directory.Exists(s_workingDirectory)) { Directory.CreateDirectory(s_workingDirectory); @@ -34,24 +41,15 @@ public static void Before() [TearDown] public static void After() { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + Directory.Delete(s_workingDirectory, true); } [Test] - [Platform("Win")] - public void TestReadAllTextOnWindows() - { - var filePath = CreateConfigTempFile(s_workingDirectory, s_content); - - // act - var result = s_fileOperations.ReadAllText(filePath, TomlConnectionBuilder.ValidateFilePermissions); - - // assert - Assert.AreEqual(s_content, result); - } - - [Test] - [Platform(Exclude = "Win")] public void TestReadAllTextCheckingPermissionsUsingTomlConfigurationFileValidations( [ValueSource(nameof(UserAllowedFilePermissions))] FileAccessPermissions userAllowedFilePermissions) @@ -69,7 +67,6 @@ public void TestReadAllTextCheckingPermissionsUsingTomlConfigurationFileValidati } [Test] - [Platform(Exclude = "Win")] public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadConfigurationFile( [ValueSource(nameof(UserAllowedFilePermissions))] FileAccessPermissions userAllowedFilePermissions) @@ -86,7 +83,6 @@ public void TestShouldThrowExceptionIfOtherPermissionsIsSetWhenReadConfiguration [Test] - [Platform(Exclude = "Win")] public void TestFileIsSafeOnNotWindows() { // arrange @@ -98,7 +94,6 @@ public void TestFileIsSafeOnNotWindows() } [Test] - [Platform(Exclude = "Win")] public void TestFileIsNotSafeOnNotWindowsWhenTooBroadPermissionsAreUsed( [ValueSource(nameof(InsecurePermissions))] FileAccessPermissions permissions) @@ -112,19 +107,6 @@ public void TestFileIsNotSafeOnNotWindowsWhenTooBroadPermissionsAreUsed( } [Test] - [Platform("Win")] - public void TestFileIsSafeOnWindows() - { - // arrange - var absoluteFilePath = Path.Combine(s_workingDirectory, s_fileName); - File.Create(absoluteFilePath).Close(); - - // act and assert - Assert.IsTrue(s_fileOperations.IsFileSafe(absoluteFilePath)); - } - - [Test] - [Platform(Exclude = "Win")] public void TestOwnerIsCurrentUser() { // arrange @@ -137,7 +119,6 @@ public void TestOwnerIsCurrentUser() } [Test] - [Platform(Exclude = "Win")] public void TestOwnerIsNotCurrentUser() { // arrange @@ -150,7 +131,6 @@ public void TestOwnerIsNotCurrentUser() } [Test] - [Platform(Exclude = "Win")] public void TestFileIsNotSecureWhenNotOwnedByCurrentUser() { // arrange @@ -171,7 +151,6 @@ public void TestFileIsNotSecureWhenNotOwnedByCurrentUser() } [Test] - [Platform(Exclude = "Win")] public void TestFileCopyUsesProperPermissions() { // arrange @@ -193,7 +172,6 @@ public void TestFileCopyUsesProperPermissions() } [Test] - [Platform(Exclude = "Win")] public void TestFileCopyShouldThrowExecptionIfTooBroadPermissionsAreUsed() { // arrange @@ -232,3 +210,4 @@ public static IEnumerable InsecurePermissions() } } } + diff --git a/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsWindowsTest.cs b/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsWindowsTest.cs new file mode 100644 index 000000000..a4d31019b --- /dev/null +++ b/Snowflake.Data.Tests/UnitTests/Tools/FileOperationsWindowsTest.cs @@ -0,0 +1,61 @@ +using System.IO; +using NUnit.Framework; +using Snowflake.Data.Core; +using Snowflake.Data.Core.Tools; +using Snowflake.Data.Tests.Mock; +using static Snowflake.Data.Tests.UnitTests.Configuration.EasyLoggingConfigGenerator; + +namespace Snowflake.Data.Tests.UnitTests.Tools +{ + [TestFixture, NonParallelizable] + [Platform("Win")] + public class FileOperationsWindowsTest + { + private static FileOperations s_fileOperations; + private static readonly string s_relativeWorkingDirectory = $"file_operations_test_{Path.GetRandomFileName()}"; + private static readonly string s_workingDirectory = Path.Combine(TempUtil.GetTempPath(), s_relativeWorkingDirectory); + private static readonly string s_content = "random text"; + private static readonly string s_fileName = "testfile"; + + [SetUp] + public static void Before() + { + if (!Directory.Exists(s_workingDirectory)) + { + Directory.CreateDirectory(s_workingDirectory); + } + + s_fileOperations = new FileOperations(); + } + + [TearDown] + public static void After() + { + Directory.Delete(s_workingDirectory, true); + } + + [Test] + public void TestReadAllTextOnWindows() + { + var filePath = CreateConfigTempFile(s_workingDirectory, s_content); + + // act + var result = s_fileOperations.ReadAllText(filePath, TomlConnectionBuilder.ValidateFilePermissions); + + // assert + Assert.AreEqual(s_content, result); + } + + [Test] + public void TestFileIsSafeOnWindows() + { + // arrange + var absoluteFilePath = Path.Combine(s_workingDirectory, s_fileName); + File.Create(absoluteFilePath).Close(); + + // act and assert + Assert.IsTrue(s_fileOperations.IsFileSafe(absoluteFilePath)); + } + } +} + diff --git a/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs b/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs index 2a2eefb77..218a0c887 100644 --- a/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Tools/UnixOperationsTest.cs @@ -27,6 +27,11 @@ public class UnixOperationsTest [OneTimeSetUp] public static void BeforeAll() { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("Unix-specific tests are not run on Windows"); + } + if (!Directory.Exists(s_workingDirectory)) { Directory.CreateDirectory(s_workingDirectory); @@ -37,6 +42,11 @@ public static void BeforeAll() [OneTimeTearDown] public static void AfterAll() { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + Directory.Delete(s_workingDirectory, true); } From 329812708a8254a79052df631ba310c68cd1d5a1 Mon Sep 17 00:00:00 2001 From: Marcin Gemra Date: Fri, 14 Nov 2025 14:15:07 +0100 Subject: [PATCH 5/6] Split DirectoryOperationsTest into platform-specific test classes - Created DirectoryOperationsWindowsTest for Windows-specific tests - Created DirectoryOperationsUnixTest for Unix-specific tests - Added RuntimeInformation platform check in Unix test setup - Added [Platform(Exclude="Win")] to DirectoryUnixInformationTest - Removed original mixed DirectoryOperationsTest file This completes the platform-specific test refactoring to fix OneTimeSetUp errors on Windows for net481 target framework. --- ...Test.cs => DirectoryOperationsUnixTest.cs} | 42 +++++-------- .../Tools/DirectoryOperationsWindowsTest.cs | 59 +++++++++++++++++++ .../Tools/DirectoryUnixInformationTest.cs | 1 + 3 files changed, 74 insertions(+), 28 deletions(-) rename Snowflake.Data.Tests/UnitTests/Tools/{DirectoryOperationsTest.cs => DirectoryOperationsUnixTest.cs} (81%) create mode 100644 Snowflake.Data.Tests/UnitTests/Tools/DirectoryOperationsWindowsTest.cs diff --git a/Snowflake.Data.Tests/UnitTests/Tools/DirectoryOperationsTest.cs b/Snowflake.Data.Tests/UnitTests/Tools/DirectoryOperationsUnixTest.cs similarity index 81% rename from Snowflake.Data.Tests/UnitTests/Tools/DirectoryOperationsTest.cs rename to Snowflake.Data.Tests/UnitTests/Tools/DirectoryOperationsUnixTest.cs index 5db96275c..fd7bb1d10 100644 --- a/Snowflake.Data.Tests/UnitTests/Tools/DirectoryOperationsTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Tools/DirectoryOperationsUnixTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using Mono.Unix; using Mono.Unix.Native; using NUnit.Framework; @@ -9,7 +10,8 @@ namespace Snowflake.Data.Tests.UnitTests.Tools { [TestFixture, NonParallelizable] - public class DirectoryOperationsTest + [Platform(Exclude = "Win")] + public class DirectoryOperationsUnixTest { private static DirectoryOperations s_directoryOperations; private static readonly string s_relativeWorkingDirectory = $"directory_operations_test_{Path.GetRandomFileName()}"; @@ -20,6 +22,11 @@ public class DirectoryOperationsTest [SetUp] public static void Before() { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Ignore("Unix-specific tests are not run on Windows"); + } + if (!Directory.Exists(s_workingDirectory)) { Directory.CreateDirectory(s_workingDirectory); @@ -31,23 +38,15 @@ public static void Before() [TearDown] public static void After() { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + Directory.Delete(s_workingDirectory, true); } [Test] - [Platform("Win")] - public void TestDirectoryIsSafeOnWindows() - { - // arrange - var absoluteFilePath = Path.Combine(s_workingDirectory, s_dirName); - Directory.CreateDirectory(absoluteFilePath); - - // act and assert - Assert.IsTrue(s_directoryOperations.IsDirectorySafe(absoluteFilePath)); - } - - [Test] - [Platform(Exclude = "Win")] public void TestDirectoryIsNotSafeOnNotWindowsWhenPermissionsAreTooBroad( [ValueSource(nameof(InsecurePermissions))] FileAccessPermissions permissions) @@ -60,18 +59,6 @@ public void TestDirectoryIsNotSafeOnNotWindowsWhenPermissionsAreTooBroad( } [Test] - public void TestShouldCreateDirectoryWithSafePermissions() - { - // act - s_directoryOperations.CreateDirectory(s_dirAbsolutePath); - - // assert - Assert.IsTrue(Directory.Exists(s_dirAbsolutePath)); - Assert.IsTrue(s_directoryOperations.IsDirectorySafe(s_dirAbsolutePath)); - } - - [Test] - [Platform(Exclude = "Win")] public void TestOwnerIsCurrentUser() { // arrange @@ -83,7 +70,6 @@ public void TestOwnerIsCurrentUser() } [Test] - [Platform(Exclude = "Win")] public void TestOwnerIsNotCurrentUser() { // arrange @@ -95,7 +81,6 @@ public void TestOwnerIsNotCurrentUser() } [Test] - [Platform(Exclude = "Win")] public void TestDirectoryIsNotSecureWhenNotOwnedByCurrentUser() { // arrange @@ -121,3 +106,4 @@ public static IEnumerable InsecurePermissions() } } } + diff --git a/Snowflake.Data.Tests/UnitTests/Tools/DirectoryOperationsWindowsTest.cs b/Snowflake.Data.Tests/UnitTests/Tools/DirectoryOperationsWindowsTest.cs new file mode 100644 index 000000000..395143800 --- /dev/null +++ b/Snowflake.Data.Tests/UnitTests/Tools/DirectoryOperationsWindowsTest.cs @@ -0,0 +1,59 @@ +using System.IO; +using NUnit.Framework; +using Snowflake.Data.Core.Tools; + +namespace Snowflake.Data.Tests.UnitTests.Tools +{ + [TestFixture, NonParallelizable] + [Platform("Win")] + public class DirectoryOperationsWindowsTest + { + private static DirectoryOperations s_directoryOperations; + private static readonly string s_relativeWorkingDirectory = $"directory_operations_test_{Path.GetRandomFileName()}"; + private static readonly string s_workingDirectory = Path.Combine(TempUtil.GetTempPath(), s_relativeWorkingDirectory); + private static readonly string s_dirName = "testdir"; + + [SetUp] + public static void Before() + { + if (!Directory.Exists(s_workingDirectory)) + { + Directory.CreateDirectory(s_workingDirectory); + } + + s_directoryOperations = new DirectoryOperations(); + } + + [TearDown] + public static void After() + { + Directory.Delete(s_workingDirectory, true); + } + + [Test] + public void TestDirectoryIsSafeOnWindows() + { + // arrange + var absoluteFilePath = Path.Combine(s_workingDirectory, s_dirName); + Directory.CreateDirectory(absoluteFilePath); + + // act and assert + Assert.IsTrue(s_directoryOperations.IsDirectorySafe(absoluteFilePath)); + } + + [Test] + public void TestShouldCreateDirectoryWithSafePermissions() + { + // arrange + var dirAbsolutePath = Path.Combine(s_workingDirectory, s_dirName); + + // act + s_directoryOperations.CreateDirectory(dirAbsolutePath); + + // assert + Assert.IsTrue(Directory.Exists(dirAbsolutePath)); + Assert.IsTrue(s_directoryOperations.IsDirectorySafe(dirAbsolutePath)); + } + } +} + diff --git a/Snowflake.Data.Tests/UnitTests/Tools/DirectoryUnixInformationTest.cs b/Snowflake.Data.Tests/UnitTests/Tools/DirectoryUnixInformationTest.cs index fd62172c3..6d1851ca2 100644 --- a/Snowflake.Data.Tests/UnitTests/Tools/DirectoryUnixInformationTest.cs +++ b/Snowflake.Data.Tests/UnitTests/Tools/DirectoryUnixInformationTest.cs @@ -6,6 +6,7 @@ namespace Snowflake.Data.Tests.UnitTests.Tools { [TestFixture] + [Platform(Exclude = "Win")] public class DirectoryUnixInformationTest { private const long UserId = 5; From 5faeee787a3d368e9dbc7134aa5e855bd2ff55a7 Mon Sep 17 00:00:00 2001 From: Marcin Gemra Date: Fri, 14 Nov 2025 14:51:06 +0100 Subject: [PATCH 6/6] Fix UnixFilePermissionsConverter to skip all combination enum values The converter was double-counting permissions by including both individual flags (UserRead, UserWrite) and their combinations (UserReadWrite). This caused it to return incorrect values, e.g. expecting 404 but getting 1004. The fix excludes all combination flags (ReadWrite, ReadExecute, WriteExecute, AllPermissions, DefaultPermissions, None) so only individual Read/Write/Execute flags are counted. All 512 UnixFilePermissionsConverterTest test cases now pass. --- Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs b/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs index 32afe68d8..ac20342e7 100644 --- a/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs +++ b/Snowflake.Data/PlatformShims/MonoUnixWindowsShim.cs @@ -25,7 +25,6 @@ public enum FileAccessPermissions UserExecute = 64, UserWrite = 128, UserRead = 256, - UserReadWrite = 384, UserReadWriteExecute = 448, AllPermissions = 511, DefaultPermissions = 420