From a57ca8d108b3ff18c310108152344bbe4eae5b5f Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Tue, 27 Oct 2026 15:47:36 -0700 Subject: [PATCH 1/2] wip, add test suite for loading --- .../DriverManager/DriverNotFoundTests.cs | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/DriverNotFoundTests.cs diff --git a/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/DriverNotFoundTests.cs b/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/DriverNotFoundTests.cs new file mode 100644 index 0000000000..180c06e12a --- /dev/null +++ b/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/DriverNotFoundTests.cs @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using Apache.Arrow.Adbc.DriverManager; +using Xunit; + +namespace Apache.Arrow.Adbc.Tests.DriverManager +{ + /// + /// A driver that does not exist should be reported as an + /// with + /// no matter how it was specified or which search locations are enabled. + /// + [Collection(DriverManagerSecurityCollection.Name)] + public class DriverNotFoundTests + { + private const string MissingDriverName = "adbc_test_driver_manager_missing_driver_7c8f5482"; + + public static IEnumerable AllLoadFlags() + { + AdbcLoadFlags all = AdbcLoadFlags.SearchEnv | AdbcLoadFlags.SearchUser | + AdbcLoadFlags.SearchSystem | AdbcLoadFlags.AllowRelativePaths; + for (int flags = 0; flags <= (int)all; flags++) + { + yield return new object[] { (AdbcLoadFlags)flags }; + } + } + + [Theory] + [MemberData(nameof(AllLoadFlags))] + public void BareName_AllLoadFlags_ThrowsNotFound(AdbcLoadFlags flags) + { + AssertNotFound(MissingDriverName, flags); + } + + [Theory] + [InlineData(AdbcLoadFlags.Default)] + [InlineData(AdbcLoadFlags.None)] + public void BareName_AdditionalSearchPath_ThrowsNotFound(AdbcLoadFlags flags) + { + AssertNotFound(MissingDriverName, flags, MissingDriverDir()); + } + + [Theory] + [InlineData(AdbcLoadFlags.Default)] + [InlineData(AdbcLoadFlags.None)] + public void AbsolutePathWithoutExtension_ThrowsNotFound(AdbcLoadFlags flags) + { + AssertNotFound(Path.Combine(MissingDriverDir(), MissingDriverName), flags); + } + + [Theory] + [InlineData(AdbcLoadFlags.Default)] + [InlineData(AdbcLoadFlags.None)] + public void AbsolutePathWithLibraryExtension_ThrowsNotFound(AdbcLoadFlags flags) + { + AssertNotFound(Path.Combine(MissingDriverDir(), PlatformLibraryName(MissingDriverName)), flags); + } + + [Fact] + public void RelativePathWithLibraryExtension_ThrowsNotFound() + { + AssertNotFound(PlatformLibraryName(MissingDriverName), AdbcLoadFlags.Compatible); + } + + [Theory] + [InlineData(AdbcLoadFlags.Default)] + [InlineData(AdbcLoadFlags.None)] + public void AbsoluteManifestPath_ThrowsNotFound(AdbcLoadFlags flags) + { + AssertNotFound(Path.Combine(MissingDriverDir(), MissingDriverName + ".toml"), flags); + } + + [Fact] + public void RelativeManifestPath_ThrowsNotFound() + { + AssertNotFound(MissingDriverName + ".toml", AdbcLoadFlags.Compatible); + } + + private static void AssertNotFound(string driverName, AdbcLoadFlags flags, string? additionalSearchPathList = null) + { + Exception? ex = Record.Exception( + () => AdbcDriverManager.FindLoadDriver( + driverName, + loadOptions: flags, + additionalSearchPathList: additionalSearchPathList)); + + Assert.True( + ex is AdbcException, + $"Expected AdbcException(NotFound), got {ex?.GetType().FullName ?? "no exception"}: {ex?.Message}"); + Assert.Equal(AdbcStatusCode.NotFound, ((AdbcException)ex!).Status); + } + + private static string MissingDriverDir() => + Path.Combine(Path.GetTempPath(), "adbc_missing_driver_dir_7c8f5482"); + + private static string PlatformLibraryName(string name) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return name + ".dll"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return "lib" + name + ".dylib"; + return "lib" + name + ".so"; + } + } +} From d36b26576c5c1d1a624865b6c968fde20c8b8d95 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 28 Oct 2026 15:07:37 -0700 Subject: [PATCH 2/2] Update code and tests to match --- .../C/CAdbcDriverImporter.cs | 4 +- .../DriverManager/AdbcDriverManager.cs | 67 +++++++--- .../DriverManager/DriverNotFoundTests.cs | 121 ++++++++++++++++-- .../TomlConnectionProfileTests.cs | 2 +- 4 files changed, 168 insertions(+), 26 deletions(-) diff --git a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs index 19b85e123a..57e96ef721 100644 --- a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs +++ b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs @@ -79,7 +79,9 @@ private static AdbcDriver Load( IntPtr library = NativeLibrary.Load(file); if (library == IntPtr.Zero) { - throw new ArgumentException("unable to load library", nameof(file)); + // Only reachable with the netstandard2.0 NativeLibrary shim; the + // runtime's NativeLibrary.Load throws DllNotFoundException itself. + throw new DllNotFoundException($"Unable to load shared library '{file}'."); } try diff --git a/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs b/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs index 3ebfc3bfc8..8eb2df3be8 100644 --- a/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs +++ b/csharp/src/Apache.Arrow.Adbc/DriverManager/AdbcDriverManager.cs @@ -134,12 +134,42 @@ private static AdbcDriver LoadNativeDriver(string driverPath, string? entrypoint typeName: null, manifestPath: null, loadMethod: loadMethod, - () => entrypoint == null - ? CAdbcDriverImporter.LoadWithFallback( - driverPath, - resolvedEntrypoint, - DefaultNativeEntrypoint) - : CAdbcDriverImporter.Load(driverPath, resolvedEntrypoint)); + () => ImportNativeDriver( + driverPath, + () => entrypoint == null + ? CAdbcDriverImporter.LoadWithFallback( + driverPath, + resolvedEntrypoint, + DefaultNativeEntrypoint) + : CAdbcDriverImporter.Load(driverPath, resolvedEntrypoint))); + } + + /// + /// Runs a native driver import, reporting a library that is missing or + /// cannot be loaded as to match the + /// C/C++ and Rust driver managers. The underlying load error is kept as + /// the inner exception. + /// + private static AdbcDriver ImportNativeDriver(string driverPath, Func import) + { + if (!File.Exists(driverPath)) + { + throw new AdbcException( + $"Driver library file not found: '{driverPath}'.", + AdbcStatusCode.NotFound); + } + + try + { + return import(); + } + catch (Exception ex) when (ex is DllNotFoundException || ex is BadImageFormatException) + { + throw new AdbcException( + $"Could not load driver library '{driverPath}': {ex.Message}", + AdbcStatusCode.NotFound, + ex); + } } // ----------------------------------------------------------------------- @@ -853,11 +883,13 @@ private static AdbcDriver LoadFromAbsolutePath(string path, string? entrypoint) private static AdbcDriver LoadFromManifest(string manifestPath, string? entrypoint) { + // A manifest that can't be read is an invalid argument, as in the + // C/C++ and Rust driver managers if (!File.Exists(manifestPath)) { throw new AdbcException( - $"Driver manifest file not found.", - AdbcStatusCode.NotFound); + $"Driver manifest file not found: '{manifestPath}'.", + AdbcStatusCode.InvalidArgument); } DriverManifest manifest = DriverManifest.LoadFromFile(manifestPath); @@ -948,12 +980,14 @@ private static AdbcDriver LoadByEntrypointScheme( typeName: null, manifestPath: manifestPath, loadMethod: loadMethod, - () => allowNativeFallback - ? CAdbcDriverImporter.LoadWithFallback( - driverPath, - entrypoint, - DefaultNativeEntrypoint) - : CAdbcDriverImporter.Load(driverPath, entrypoint)); + () => ImportNativeDriver( + driverPath, + () => allowNativeFallback + ? CAdbcDriverImporter.LoadWithFallback( + driverPath, + entrypoint, + DefaultNativeEntrypoint) + : CAdbcDriverImporter.Load(driverPath, entrypoint))); } /// @@ -997,8 +1031,9 @@ private static void EnsureRuntime(bool isNetFramework, string scheme) // 2. Try native library extensions. If a matching file exists but fails // to load (e.g., corrupt binary, wrong architecture, missing entrypoint), - // surface that concrete error rather than silently degrading to a generic - // NotFound -- the file is clearly the user's intended driver. + // stop here rather than trying other directories -- the file is clearly + // the user's intended driver. A library that cannot be loaded is reported + // as NotFound with the concrete error attached (see ImportNativeDriver). foreach (string nativeExt in GetPlatformExtensions()) { string libPath = Path.Combine(dir, driverName + nativeExt); diff --git a/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/DriverNotFoundTests.cs b/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/DriverNotFoundTests.cs index 180c06e12a..f57a8a047a 100644 --- a/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/DriverNotFoundTests.cs +++ b/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/DriverNotFoundTests.cs @@ -28,12 +28,26 @@ namespace Apache.Arrow.Adbc.Tests.DriverManager /// A driver that does not exist should be reported as an /// with /// no matter how it was specified or which search locations are enabled. + /// A library file that exists but cannot be loaded is also reported as + /// , with the load error attached, + /// matching the C/C++ driver manager. A manifest file that doesn't exist + /// is reported as . /// [Collection(DriverManagerSecurityCollection.Name)] - public class DriverNotFoundTests + public class DriverNotFoundTests : IDisposable { private const string MissingDriverName = "adbc_test_driver_manager_missing_driver_7c8f5482"; + private readonly List _tempDirs = new List(); + + public void Dispose() + { + foreach (string d in _tempDirs) + { + try { if (Directory.Exists(d)) Directory.Delete(d, true); } catch { } + } + } + public static IEnumerable AllLoadFlags() { AdbcLoadFlags all = AdbcLoadFlags.SearchEnv | AdbcLoadFlags.SearchUser | @@ -72,7 +86,9 @@ public void AbsolutePathWithoutExtension_ThrowsNotFound(AdbcLoadFlags flags) [InlineData(AdbcLoadFlags.None)] public void AbsolutePathWithLibraryExtension_ThrowsNotFound(AdbcLoadFlags flags) { - AssertNotFound(Path.Combine(MissingDriverDir(), PlatformLibraryName(MissingDriverName)), flags); + string path = Path.Combine(MissingDriverDir(), PlatformLibraryName(MissingDriverName)); + AdbcException ex = AssertNotFound(path, flags); + Assert.Contains(path, ex.Message); } [Fact] @@ -81,21 +97,82 @@ public void RelativePathWithLibraryExtension_ThrowsNotFound() AssertNotFound(PlatformLibraryName(MissingDriverName), AdbcLoadFlags.Compatible); } + // A manifest file that doesn't exist is InvalidArgument rather than + // NotFound, as in the C/C++ and Rust driver managers [Theory] [InlineData(AdbcLoadFlags.Default)] [InlineData(AdbcLoadFlags.None)] - public void AbsoluteManifestPath_ThrowsNotFound(AdbcLoadFlags flags) + public void AbsoluteManifestPath_ThrowsInvalidArgument(AdbcLoadFlags flags) + { + string path = Path.Combine(MissingDriverDir(), MissingDriverName + ".toml"); + AdbcException ex = Assert.Throws( + () => AdbcDriverManager.FindLoadDriver(path, loadOptions: flags)); + Assert.Equal(AdbcStatusCode.InvalidArgument, ex.Status); + Assert.Contains(path, ex.Message); + } + + [Fact] + public void RelativeManifestPath_ThrowsInvalidArgument() + { + string name = MissingDriverName + ".toml"; + AdbcException ex = Assert.Throws( + () => AdbcDriverManager.FindLoadDriver(name, loadOptions: AdbcLoadFlags.Compatible)); + Assert.Equal(AdbcStatusCode.InvalidArgument, ex.Status); + Assert.Contains(name, ex.Message); + } + + [Fact] + public void ManifestWithMissingLibrary_ThrowsNotFound() + { + string dir = CreateTempDir(); + string libraryPath = Path.Combine(dir, PlatformLibraryName(MissingDriverName)); + string manifestPath = WriteManifest(dir, libraryPath); + + AdbcException ex = AssertNotFound(manifestPath, AdbcLoadFlags.Default); + Assert.Contains(libraryPath, ex.Message); + } + + // ----------------------------------------------------------------------- + // A library file that exists but is not a loadable shared library + // ----------------------------------------------------------------------- + + [Fact] + public void InvalidLibrary_AbsolutePath_ThrowsNotFound() { - AssertNotFound(Path.Combine(MissingDriverDir(), MissingDriverName + ".toml"), flags); + string libraryPath = WriteInvalidLibrary(CreateTempDir(), PlatformLibraryName(MissingDriverName)); + + AdbcException ex = AssertNotFound(libraryPath, AdbcLoadFlags.Default); + Assert.Contains(libraryPath, ex.Message); + Assert.NotNull(ex.InnerException); } [Fact] - public void RelativeManifestPath_ThrowsNotFound() + public void InvalidLibrary_BareNameInSearchPath_ThrowsNotFound() { - AssertNotFound(MissingDriverName + ".toml", AdbcLoadFlags.Compatible); + string dir = CreateTempDir(); + string extension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".dll" + : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? ".dylib" + : ".so"; + string libraryPath = WriteInvalidLibrary(dir, MissingDriverName + extension); + + AdbcException ex = AssertNotFound(MissingDriverName, AdbcLoadFlags.None, dir); + Assert.Contains(libraryPath, ex.Message); + Assert.NotNull(ex.InnerException); + } + + [Fact] + public void ManifestWithInvalidLibrary_ThrowsNotFound() + { + string dir = CreateTempDir(); + string libraryPath = WriteInvalidLibrary(dir, PlatformLibraryName(MissingDriverName)); + string manifestPath = WriteManifest(dir, libraryPath); + + AdbcException ex = AssertNotFound(manifestPath, AdbcLoadFlags.Default); + Assert.Contains(libraryPath, ex.Message); + Assert.NotNull(ex.InnerException); } - private static void AssertNotFound(string driverName, AdbcLoadFlags flags, string? additionalSearchPathList = null) + private static AdbcException AssertNotFound(string driverName, AdbcLoadFlags flags, string? additionalSearchPathList = null) { Exception? ex = Record.Exception( () => AdbcDriverManager.FindLoadDriver( @@ -106,7 +183,35 @@ private static void AssertNotFound(string driverName, AdbcLoadFlags flags, strin Assert.True( ex is AdbcException, $"Expected AdbcException(NotFound), got {ex?.GetType().FullName ?? "no exception"}: {ex?.Message}"); - Assert.Equal(AdbcStatusCode.NotFound, ((AdbcException)ex!).Status); + AdbcException adbcEx = (AdbcException)ex!; + Assert.Equal(AdbcStatusCode.NotFound, adbcEx.Status); + return adbcEx; + } + + private string CreateTempDir() + { + string dir = Path.Combine(Path.GetTempPath(), "adbc_driver_not_found_" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(dir); + _tempDirs.Add(dir); + return dir; + } + + private static string WriteInvalidLibrary(string dir, string fileName) + { + string path = Path.Combine(dir, fileName); + File.WriteAllText(path, "this is not a shared library"); + return path; + } + + private static string WriteManifest(string dir, string libraryPath) + { + string path = Path.Combine(dir, MissingDriverName + ".toml"); + File.WriteAllText(path, + "manifest_version = 1\n" + + "name = \"Missing\"\n" + + "\n[Driver]\n" + + "shared = '" + libraryPath + "'\n"); + return path; } private static string MissingDriverDir() => diff --git a/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/TomlConnectionProfileTests.cs b/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/TomlConnectionProfileTests.cs index 540582d8c6..1538cde73b 100644 --- a/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/TomlConnectionProfileTests.cs +++ b/csharp/test/Apache.Arrow.Adbc.Tests/DriverManager/TomlConnectionProfileTests.cs @@ -667,7 +667,7 @@ public void FindLoadDriver_AbsoluteTomlPathNotFound_ThrowsAdbcException() string fakePath = Path.Combine(Path.GetTempPath(), "fake_manifest_notexist.toml"); AdbcException ex = Assert.Throws( () => AdbcDriverManager.FindLoadDriver(fakePath)); - Assert.Equal(AdbcStatusCode.NotFound, ex.Status); + Assert.Equal(AdbcStatusCode.InvalidArgument, ex.Status); } [Fact]