Skip to main content

datafusion_ffi/tests/
utils.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::path::{Path, PathBuf};
19
20use datafusion_common::{DataFusionError, Result};
21
22use crate::tests::ForeignLibraryModule;
23
24/// Compute the path to the built cdylib. Checks debug, release, and ci profile dirs.
25fn compute_library_dir(target_path: &Path) -> PathBuf {
26    let debug_dir = target_path.join("debug");
27    let release_dir = target_path.join("release");
28    let ci_dir = target_path.join("ci");
29
30    let all_dirs = vec![debug_dir.clone(), release_dir, ci_dir];
31
32    all_dirs
33        .into_iter()
34        .filter(|dir| dir.join("deps").exists())
35        .filter_map(|dir| {
36            dir.join("deps")
37                .metadata()
38                .and_then(|m| m.modified())
39                .ok()
40                .map(|date| (dir, date))
41        })
42        .max_by_key(|(_, date)| *date)
43        .map(|(dir, _)| dir)
44        .unwrap_or(debug_dir)
45}
46
47/// Find the cdylib file for datafusion_ffi in the given directory.
48fn find_cdylib(deps_dir: &Path) -> Result<PathBuf> {
49    let lib_prefix = if cfg!(target_os = "windows") {
50        ""
51    } else {
52        "lib"
53    };
54    let lib_ext = if cfg!(target_os = "macos") {
55        "dylib"
56    } else if cfg!(target_os = "windows") {
57        "dll"
58    } else {
59        "so"
60    };
61
62    let pattern = format!("{lib_prefix}datafusion_ffi.{lib_ext}");
63    let lib_path = deps_dir.join(&pattern);
64
65    if lib_path.exists() {
66        return Ok(lib_path);
67    }
68
69    Err(DataFusionError::External(
70        format!("Could not find library at {}", lib_path.display()).into(),
71    ))
72}
73
74pub fn get_module() -> Result<ForeignLibraryModule> {
75    let expected_version = crate::version();
76
77    let crate_root = Path::new(env!("CARGO_MANIFEST_DIR"));
78    let target_dir = crate_root
79        .parent()
80        .expect("Failed to find crate parent")
81        .parent()
82        .expect("Failed to find workspace root")
83        .join("target");
84
85    let library_dir = compute_library_dir(target_dir.as_path());
86    let lib_path = find_cdylib(&library_dir.join("deps"))?;
87
88    // Load the library using libloading
89    let lib = unsafe {
90        libloading::Library::new(&lib_path)
91            .map_err(|e| DataFusionError::External(Box::new(e)))?
92    };
93
94    let get_module: libloading::Symbol<extern "C" fn() -> ForeignLibraryModule> = unsafe {
95        lib.get(b"datafusion_ffi_get_module")
96            .map_err(|e| DataFusionError::External(Box::new(e)))?
97    };
98
99    let module = get_module();
100
101    assert_eq!((module.version)(), expected_version);
102
103    // Leak the library to keep it loaded for the duration of the test
104    std::mem::forget(lib);
105
106    Ok(module)
107}