working sys level bindings :yippie:
This commit is contained in:
parent
4508aeab76
commit
e9022e675b
12 changed files with 3091 additions and 45 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -4,6 +4,3 @@ result-*
|
|||
|
||||
# Rust
|
||||
/target
|
||||
|
||||
# Other
|
||||
nixops4-bindings-sys
|
||||
|
|
|
|||
148
Cargo.lock
generated
148
Cargo.lock
generated
|
|
@ -93,6 +93,41 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
|
|
@ -124,6 +159,15 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
|
|
@ -150,6 +194,7 @@ dependencies = [
|
|||
"cc",
|
||||
"doxygen-bindgen",
|
||||
"pkg-config",
|
||||
"serial_test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -162,6 +207,41 @@ dependencies = [
|
|||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
|
|
@ -186,6 +266,15 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
|
|
@ -221,12 +310,71 @@ version = "2.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "scc"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
|
||||
dependencies = [
|
||||
"sdd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sdd"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f"
|
||||
dependencies = [
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"scc",
|
||||
"serial_test_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test_derive"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ description = "Unsafe direct FFI bindings to libnix C API"
|
|||
version = "0.1.0"
|
||||
readme = "../README.md"
|
||||
license = "GPL-3.0"
|
||||
license-file = "../LICENSE"
|
||||
repository = "https://codeberg.org/luminary/nixide"
|
||||
authors = [
|
||||
"_cry64 <them@dobutterfliescry.net>"
|
||||
"_cry64 <them@dobutterfliescry.net>",
|
||||
"foxxyora <foxxyora@noreply.codeberg.org>"
|
||||
]
|
||||
|
||||
|
|
@ -35,3 +34,5 @@ doxygen-bindgen = "0.1.3"
|
|||
pkg-config.workspace = true
|
||||
cc.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "3.4.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use bindgen::callbacks::ParseCallbacks;
|
||||
|
||||
|
|
@ -24,7 +23,7 @@ fn main() {
|
|||
println!("cargo:rerun-if-changed=include/wrapper.h");
|
||||
|
||||
// Tell cargo to tell rustc to link the system shared library
|
||||
println!("cargo:rustc-link-lib=bz2");
|
||||
// println!("cargo:rustc-link-lib=bz2");
|
||||
|
||||
// Use pkg-config to find nix-store include and link paths
|
||||
// This NEEDS to be included, or otherwise `nix_api_store.h` cannot
|
||||
|
|
@ -37,32 +36,23 @@ fn main() {
|
|||
"nix-flake-c",
|
||||
];
|
||||
|
||||
// Add all pkg-config include paths and GCC's include path to bindgen
|
||||
let mut args = Vec::new();
|
||||
for nix_lib in libs {
|
||||
let lib = pkg_config::probe_library(nix_lib)
|
||||
.expect(&format!("Unable to find .pc file for {}", nix_lib));
|
||||
|
||||
for include_path in lib.include_paths {
|
||||
args.push(format!("-I{}", incloude_path.display()));
|
||||
// builder = builder.clang_arg(format!("-I{}", include_path.display()));
|
||||
}
|
||||
for link_file in lib.link_files {
|
||||
println!("cargo:rustc-link-lib={}", link_file.display());
|
||||
}
|
||||
}
|
||||
let lib_args: Vec<String> = libs
|
||||
.iter()
|
||||
.map(|&name| {
|
||||
let lib = pkg_config::probe_library(name)
|
||||
.expect(&format!("Unable to find .pc file for {}", name));
|
||||
|
||||
let lib_args = libs.map(|name| {
|
||||
let lib = pkg_config::probe_library(name)
|
||||
.expect(&format!("Unable to find .pc file for {}", nix_lib));
|
||||
for p in lib.link_files {
|
||||
println!("cargo:rustc-link-lib={}", p.display());
|
||||
}
|
||||
|
||||
lib.include_paths
|
||||
.into_iter()
|
||||
.map(|p| format!("-I{}", p.display()))
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
for p in lib.link_files {
|
||||
println!("cargo:rustc-link-lib={}", p.display());
|
||||
}
|
||||
|
||||
lib.include_paths.map(|p| format!("-I{}", p.display()))
|
||||
}).flatten();
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.clang_args(lib_args)
|
||||
// The input header we would like to generate bindings for
|
||||
|
|
@ -73,7 +63,7 @@ fn main() {
|
|||
.parse_callbacks(Box::new(DoxygenCallbacks))
|
||||
// Format generated bindings with rustfmt
|
||||
.formatter(bindgen::Formatter::Rustfmt)
|
||||
.rustfmt_configuration_file(std::fs::canonicalize(".rustfmt.toml").ok());
|
||||
.rustfmt_configuration_file(std::fs::canonicalize(".rustfmt.toml").ok())
|
||||
// Finish the builder and generate the bindings
|
||||
.generate()
|
||||
// Unwrap the Result and panic on failure
|
||||
|
|
|
|||
|
|
@ -1 +1,23 @@
|
|||
#include <>
|
||||
// Pure C API for store operations
|
||||
#include <nix_api_store.h>
|
||||
|
||||
// Pure C API for error handling
|
||||
#include <nix_api_util.h>
|
||||
|
||||
// Pure C API for the Nix evaluator
|
||||
#include <nix_api_expr.h>
|
||||
|
||||
// Pure C API for external values
|
||||
#include <nix_api_external.h>
|
||||
|
||||
// Pure C API for value manipulation
|
||||
#include <nix_api_value.h>
|
||||
|
||||
// Pure C API for fetcher operations
|
||||
#include <nix_api_fetchers.h>
|
||||
|
||||
// Pure C API for flake support
|
||||
#include <nix_api_flake.h>
|
||||
|
||||
// Pure C API for main/CLI support
|
||||
#include <nix_api_main.h>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
//! # nixide-sys
|
||||
//!
|
||||
//! Unsafe direct FFI bindings to libnix C API.
|
||||
//!
|
||||
//! ## Safety
|
||||
//!
|
||||
//! These bindings are generated automatically and map directly to the C API.
|
||||
//! They are unsafe to use directly. Prefer using the high-level safe API in the
|
||||
//! parent crate unless you know what you're doing.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(rustdoc::bare_urls)]
|
||||
#![allow(rustdoc::invalid_html_tags)]
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
|
|
|||
1235
nixide-sys/tests/eval.rs
Normal file
1235
nixide-sys/tests/eval.rs
Normal file
File diff suppressed because it is too large
Load diff
78
nixide-sys/tests/flake.rs
Normal file
78
nixide-sys/tests/flake.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use nixide_sys::*;
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn flake_settings_new_and_free() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
// Create new flake settings
|
||||
let settings = nix_flake_settings_new(ctx);
|
||||
assert!(!settings.is_null(), "nix_flake_settings_new returned null");
|
||||
|
||||
// Free flake settings (should not crash)
|
||||
nix_flake_settings_free(settings);
|
||||
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn flake_settings_add_to_eval_state_builder() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, ptr::null(), ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let settings = nix_flake_settings_new(ctx);
|
||||
assert!(!settings.is_null(), "nix_flake_settings_new returned null");
|
||||
|
||||
// Add flake settings to eval state builder
|
||||
let err = nix_flake_settings_add_to_eval_state_builder(ctx, settings, builder);
|
||||
// Accept OK or ERR_UNKNOWN (depends on Nix build/config)
|
||||
assert!(
|
||||
err == nix_err_NIX_OK || err == nix_err_NIX_ERR_UNKNOWN,
|
||||
"nix_flake_settings_add_to_eval_state_builder returned unexpected error \
|
||||
code: {err}"
|
||||
);
|
||||
|
||||
nix_flake_settings_free(settings);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn flake_settings_null_context() {
|
||||
// Passing NULL context should not crash, but may error
|
||||
unsafe {
|
||||
let settings = nix_flake_settings_new(ptr::null_mut());
|
||||
// May return null if context is required
|
||||
if !settings.is_null() {
|
||||
nix_flake_settings_free(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
503
nixide-sys/tests/memory.rs
Normal file
503
nixide-sys/tests/memory.rs
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use std::ffi::CString;
|
||||
|
||||
use nixide_sys::*;
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn value_reference_counting() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
// Create a value
|
||||
let value = nix_alloc_value(ctx, state);
|
||||
assert!(!value.is_null());
|
||||
|
||||
// Initialize with an integer
|
||||
let init_err = nix_init_int(ctx, value, 42);
|
||||
assert_eq!(init_err, nix_err_NIX_OK);
|
||||
|
||||
// Test value-specific reference counting
|
||||
let incref_err = nix_value_incref(ctx, value);
|
||||
assert_eq!(incref_err, nix_err_NIX_OK);
|
||||
|
||||
// Value should still be valid after increment
|
||||
let int_val = nix_get_int(ctx, value);
|
||||
assert_eq!(int_val, 42);
|
||||
|
||||
// Test decrement
|
||||
let decref_err = nix_value_decref(ctx, value);
|
||||
assert_eq!(decref_err, nix_err_NIX_OK);
|
||||
|
||||
// Value should still be valid (original reference still exists)
|
||||
let int_val2 = nix_get_int(ctx, value);
|
||||
assert_eq!(int_val2, 42);
|
||||
|
||||
// Final decrement (should not crash)
|
||||
let final_decref_err = nix_value_decref(ctx, value);
|
||||
assert_eq!(final_decref_err, nix_err_NIX_OK);
|
||||
|
||||
// Clean up
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn general_gc_reference_counting() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
// Create a value for general GC testing
|
||||
let value = nix_alloc_value(ctx, state);
|
||||
assert!(!value.is_null());
|
||||
|
||||
let init_err = nix_init_string(
|
||||
ctx,
|
||||
value,
|
||||
CString::new("test string for GC").unwrap().as_ptr(),
|
||||
);
|
||||
assert_eq!(init_err, nix_err_NIX_OK);
|
||||
|
||||
// Test general GC reference counting
|
||||
let gc_incref_err = nix_gc_incref(ctx, value as *const ::std::os::raw::c_void);
|
||||
assert_eq!(gc_incref_err, nix_err_NIX_OK);
|
||||
|
||||
// Value should still be accessible
|
||||
let value_type = nix_get_type(ctx, value);
|
||||
assert_eq!(value_type, ValueType_NIX_TYPE_STRING);
|
||||
|
||||
// Test GC decrement
|
||||
let gc_decref_err = nix_gc_decref(ctx, value as *const ::std::os::raw::c_void);
|
||||
assert_eq!(gc_decref_err, nix_err_NIX_OK);
|
||||
|
||||
// Final cleanup
|
||||
nix_value_decref(ctx, value);
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn manual_garbage_collection() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
// Create a few values to test basic GC functionality
|
||||
let mut values = Vec::new();
|
||||
for i in 0..3 {
|
||||
let value = nix_alloc_value(ctx, state);
|
||||
if !value.is_null() {
|
||||
let init_err = nix_init_int(ctx, value, i);
|
||||
if init_err == nix_err_NIX_OK {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify values are accessible before GC
|
||||
for (i, &value) in values.iter().enumerate() {
|
||||
let int_val = nix_get_int(ctx, value);
|
||||
assert_eq!(int_val, i as i64);
|
||||
}
|
||||
|
||||
// Clean up values before attempting GC to avoid signal issues
|
||||
for value in values {
|
||||
nix_value_decref(ctx, value);
|
||||
}
|
||||
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn value_copying_and_memory_management() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
// Create original value
|
||||
let original = nix_alloc_value(ctx, state);
|
||||
assert!(!original.is_null());
|
||||
|
||||
let test_string = CString::new("test string for copying").unwrap();
|
||||
let init_err = nix_init_string(ctx, original, test_string.as_ptr());
|
||||
assert_eq!(init_err, nix_err_NIX_OK);
|
||||
|
||||
// Create copy
|
||||
let copy = nix_alloc_value(ctx, state);
|
||||
assert!(!copy.is_null());
|
||||
|
||||
let copy_err = nix_copy_value(ctx, copy, original);
|
||||
assert_eq!(copy_err, nix_err_NIX_OK);
|
||||
|
||||
// Verify copy has same type and can be accessed
|
||||
let original_type = nix_get_type(ctx, original);
|
||||
let copy_type = nix_get_type(ctx, copy);
|
||||
assert_eq!(original_type, copy_type);
|
||||
assert_eq!(copy_type, ValueType_NIX_TYPE_STRING);
|
||||
|
||||
// Test string contents using callback
|
||||
unsafe extern "C" fn string_callback(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: ::std::os::raw::c_uint,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
||||
let s = std::str::from_utf8(s).unwrap_or("");
|
||||
let result = unsafe { &mut *(user_data as *mut Option<String>) };
|
||||
*result = Some(s.to_string());
|
||||
}
|
||||
|
||||
let mut original_string: Option<String> = None;
|
||||
let mut copy_string: Option<String> = None;
|
||||
|
||||
let _ = nix_get_string(
|
||||
ctx,
|
||||
original,
|
||||
Some(string_callback),
|
||||
&mut original_string as *mut Option<String> as *mut ::std::os::raw::c_void,
|
||||
);
|
||||
|
||||
let _ = nix_get_string(
|
||||
ctx,
|
||||
copy,
|
||||
Some(string_callback),
|
||||
&mut copy_string as *mut Option<String> as *mut ::std::os::raw::c_void,
|
||||
);
|
||||
|
||||
// Both should have the same string content
|
||||
assert_eq!(original_string, copy_string);
|
||||
assert!(original_string
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("test string"));
|
||||
|
||||
// Test reference counting on both values
|
||||
let incref_orig = nix_value_incref(ctx, original);
|
||||
let incref_copy = nix_value_incref(ctx, copy);
|
||||
assert_eq!(incref_orig, nix_err_NIX_OK);
|
||||
assert_eq!(incref_copy, nix_err_NIX_OK);
|
||||
|
||||
// Values should still be accessible after increment
|
||||
assert_eq!(nix_get_type(ctx, original), ValueType_NIX_TYPE_STRING);
|
||||
assert_eq!(nix_get_type(ctx, copy), ValueType_NIX_TYPE_STRING);
|
||||
|
||||
// Clean up with decrements
|
||||
nix_value_decref(ctx, original);
|
||||
nix_value_decref(ctx, original); // extra decref from incref
|
||||
nix_value_decref(ctx, copy);
|
||||
nix_value_decref(ctx, copy); // extra decref from incref
|
||||
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn complex_value_memory_management() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
// Create a complex structure: list containing attribute sets
|
||||
let list_builder = nix_make_list_builder(ctx, state, 2);
|
||||
assert!(!list_builder.is_null());
|
||||
|
||||
// Create first element: attribute set
|
||||
let attrs1 = nix_alloc_value(ctx, state);
|
||||
assert!(!attrs1.is_null());
|
||||
|
||||
let bindings_builder1 = nix_make_bindings_builder(ctx, state, 2);
|
||||
assert!(!bindings_builder1.is_null());
|
||||
|
||||
// Add attributes to first set
|
||||
let key1 = CString::new("name").unwrap();
|
||||
let val1 = nix_alloc_value(ctx, state);
|
||||
assert!(!val1.is_null());
|
||||
let name_str = CString::new("first").unwrap();
|
||||
let _ = nix_init_string(ctx, val1, name_str.as_ptr());
|
||||
|
||||
let insert_err1 = nix_bindings_builder_insert(ctx, bindings_builder1, key1.as_ptr(), val1);
|
||||
assert_eq!(insert_err1, nix_err_NIX_OK);
|
||||
|
||||
let key2 = CString::new("value").unwrap();
|
||||
let val2 = nix_alloc_value(ctx, state);
|
||||
assert!(!val2.is_null());
|
||||
let _ = nix_init_int(ctx, val2, 42);
|
||||
|
||||
let insert_err2 = nix_bindings_builder_insert(ctx, bindings_builder1, key2.as_ptr(), val2);
|
||||
assert_eq!(insert_err2, nix_err_NIX_OK);
|
||||
|
||||
let make_attrs_err1 = nix_make_attrs(ctx, attrs1, bindings_builder1);
|
||||
assert_eq!(make_attrs_err1, nix_err_NIX_OK);
|
||||
|
||||
// Insert first attrs into list
|
||||
let list_insert_err1 = nix_list_builder_insert(ctx, list_builder, 0, attrs1);
|
||||
assert_eq!(list_insert_err1, nix_err_NIX_OK);
|
||||
|
||||
// Create second element
|
||||
let attrs2 = nix_alloc_value(ctx, state);
|
||||
assert!(!attrs2.is_null());
|
||||
|
||||
let bindings_builder2 = nix_make_bindings_builder(ctx, state, 1);
|
||||
assert!(!bindings_builder2.is_null());
|
||||
|
||||
let key3 = CString::new("data").unwrap();
|
||||
let val3 = nix_alloc_value(ctx, state);
|
||||
assert!(!val3.is_null());
|
||||
let data_str = CString::new("second").unwrap();
|
||||
let _ = nix_init_string(ctx, val3, data_str.as_ptr());
|
||||
|
||||
let insert_err3 = nix_bindings_builder_insert(ctx, bindings_builder2, key3.as_ptr(), val3);
|
||||
assert_eq!(insert_err3, nix_err_NIX_OK);
|
||||
|
||||
let make_attrs_err2 = nix_make_attrs(ctx, attrs2, bindings_builder2);
|
||||
assert_eq!(make_attrs_err2, nix_err_NIX_OK);
|
||||
|
||||
let list_insert_err2 = nix_list_builder_insert(ctx, list_builder, 1, attrs2);
|
||||
assert_eq!(list_insert_err2, nix_err_NIX_OK);
|
||||
|
||||
// Create final list
|
||||
let final_list = nix_alloc_value(ctx, state);
|
||||
assert!(!final_list.is_null());
|
||||
|
||||
let make_list_err = nix_make_list(ctx, list_builder, final_list);
|
||||
assert_eq!(make_list_err, nix_err_NIX_OK);
|
||||
|
||||
// Test the complex structure
|
||||
assert_eq!(nix_get_type(ctx, final_list), ValueType_NIX_TYPE_LIST);
|
||||
assert_eq!(nix_get_list_size(ctx, final_list), 2);
|
||||
|
||||
// Access nested elements
|
||||
let elem0 = nix_get_list_byidx(ctx, final_list, state, 0);
|
||||
let elem1 = nix_get_list_byidx(ctx, final_list, state, 1);
|
||||
assert!(!elem0.is_null() && !elem1.is_null());
|
||||
|
||||
assert_eq!(nix_get_type(ctx, elem0), ValueType_NIX_TYPE_ATTRS);
|
||||
assert_eq!(nix_get_type(ctx, elem1), ValueType_NIX_TYPE_ATTRS);
|
||||
|
||||
// Test memory management with deep copying
|
||||
let copied_list = nix_alloc_value(ctx, state);
|
||||
assert!(!copied_list.is_null());
|
||||
|
||||
let copy_err = nix_copy_value(ctx, copied_list, final_list);
|
||||
assert_eq!(copy_err, nix_err_NIX_OK);
|
||||
|
||||
// Force deep evaluation on copy
|
||||
let deep_force_err = nix_value_force_deep(ctx, state, copied_list);
|
||||
assert_eq!(deep_force_err, nix_err_NIX_OK);
|
||||
|
||||
// Both should still be accessible
|
||||
assert_eq!(nix_get_list_size(ctx, final_list), 2);
|
||||
assert_eq!(nix_get_list_size(ctx, copied_list), 2);
|
||||
|
||||
// Clean up all the values
|
||||
nix_value_decref(ctx, copied_list);
|
||||
nix_value_decref(ctx, final_list);
|
||||
nix_value_decref(ctx, attrs2);
|
||||
nix_value_decref(ctx, attrs1);
|
||||
nix_value_decref(ctx, val3);
|
||||
nix_value_decref(ctx, val2);
|
||||
nix_value_decref(ctx, val1);
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn memory_management_error_conditions() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
// Test reference counting with NULL pointers (should handle gracefully)
|
||||
let null_incref_err = nix_gc_incref(ctx, std::ptr::null() as *const ::std::os::raw::c_void);
|
||||
|
||||
// XXX: May succeed or fail depending on implementation. We can't really
|
||||
// know, so assert both.
|
||||
assert!(null_incref_err == nix_err_NIX_OK || null_incref_err == nix_err_NIX_ERR_UNKNOWN);
|
||||
|
||||
let null_decref_err = nix_gc_decref(ctx, std::ptr::null() as *const ::std::os::raw::c_void);
|
||||
assert!(null_decref_err == nix_err_NIX_OK || null_decref_err == nix_err_NIX_ERR_UNKNOWN);
|
||||
|
||||
let null_value_incref_err = nix_value_incref(ctx, std::ptr::null_mut());
|
||||
// Some Nix APIs gracefully handle null pointers and return OK
|
||||
assert!(
|
||||
null_value_incref_err == nix_err_NIX_OK
|
||||
|| null_value_incref_err == nix_err_NIX_ERR_UNKNOWN
|
||||
);
|
||||
|
||||
let null_value_decref_err = nix_value_decref(ctx, std::ptr::null_mut());
|
||||
// Some Nix APIs gracefully handle null pointers and return OK
|
||||
assert!(
|
||||
null_value_decref_err == nix_err_NIX_OK
|
||||
|| null_value_decref_err == nix_err_NIX_ERR_UNKNOWN
|
||||
);
|
||||
|
||||
// Test copy with NULL values
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
let valid_value = nix_alloc_value(ctx, state);
|
||||
assert!(!valid_value.is_null());
|
||||
|
||||
// Test copying to/from NULL
|
||||
let copy_from_null_err = nix_copy_value(ctx, valid_value, std::ptr::null_mut());
|
||||
assert_ne!(copy_from_null_err, nix_err_NIX_OK);
|
||||
|
||||
let copy_to_null_err = nix_copy_value(ctx, std::ptr::null_mut(), valid_value);
|
||||
assert_ne!(copy_to_null_err, nix_err_NIX_OK);
|
||||
|
||||
// Clean up
|
||||
nix_value_decref(ctx, valid_value);
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
639
nixide-sys/tests/primop.rs
Normal file
639
nixide-sys/tests/primop.rs
Normal file
|
|
@ -0,0 +1,639 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use std::{
|
||||
ffi::CString,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
use nixide_sys::*;
|
||||
use serial_test::serial;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestPrimOpData {
|
||||
call_count: AtomicU32,
|
||||
last_arg_value: AtomicU32,
|
||||
}
|
||||
|
||||
// Simple PrimOp that adds 1 to an integer argument
|
||||
unsafe extern "C" fn add_one_primop(
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
context: *mut nix_c_context,
|
||||
state: *mut EvalState,
|
||||
args: *mut *mut nix_value,
|
||||
ret: *mut nix_value,
|
||||
) {
|
||||
if user_data.is_null()
|
||||
|| context.is_null()
|
||||
|| state.is_null()
|
||||
|| args.is_null()
|
||||
|| ret.is_null()
|
||||
{
|
||||
let _ = unsafe {
|
||||
nix_set_err_msg(
|
||||
context,
|
||||
nix_err_NIX_ERR_UNKNOWN,
|
||||
b"Null pointer in add_one_primop\0".as_ptr() as *const _,
|
||||
)
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
let data = unsafe { &*(user_data as *const TestPrimOpData) };
|
||||
data.call_count.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
// Get first argument
|
||||
let arg = unsafe { *args.offset(0) };
|
||||
if arg.is_null() {
|
||||
let _ = unsafe {
|
||||
nix_set_err_msg(
|
||||
context,
|
||||
nix_err_NIX_ERR_UNKNOWN,
|
||||
b"Missing argument in add_one_primop\0".as_ptr() as *const _,
|
||||
)
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// Force evaluation of argument
|
||||
if unsafe { nix_value_force(context, state, arg) } != nix_err_NIX_OK {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if argument is integer
|
||||
if unsafe { nix_get_type(context, arg) } != ValueType_NIX_TYPE_INT {
|
||||
let _ = unsafe {
|
||||
nix_set_err_msg(
|
||||
context,
|
||||
nix_err_NIX_ERR_UNKNOWN,
|
||||
b"Expected integer argument in add_one_primop\0".as_ptr() as *const _,
|
||||
)
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// Get integer value and add 1
|
||||
let value = unsafe { nix_get_int(context, arg) };
|
||||
data.last_arg_value.store(value as u32, Ordering::SeqCst);
|
||||
|
||||
// Set return value
|
||||
let _ = unsafe { nix_init_int(context, ret, value + 1) };
|
||||
}
|
||||
|
||||
// PrimOp that returns a constant string
|
||||
unsafe extern "C" fn hello_world_primop(
|
||||
_user_data: *mut ::std::os::raw::c_void,
|
||||
context: *mut nix_c_context,
|
||||
_state: *mut EvalState,
|
||||
_args: *mut *mut nix_value,
|
||||
ret: *mut nix_value,
|
||||
) {
|
||||
let hello = CString::new("Hello from Rust PrimOp!").unwrap();
|
||||
let _ = unsafe { nix_init_string(context, ret, hello.as_ptr()) };
|
||||
}
|
||||
|
||||
// PrimOp that takes multiple arguments and concatenates them
|
||||
unsafe extern "C" fn concat_strings_primop(
|
||||
_user_data: *mut ::std::os::raw::c_void,
|
||||
context: *mut nix_c_context,
|
||||
state: *mut EvalState,
|
||||
args: *mut *mut nix_value,
|
||||
ret: *mut nix_value,
|
||||
) {
|
||||
if context.is_null() || state.is_null() || args.is_null() || ret.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
// This PrimOp expects exactly 2 string arguments
|
||||
let mut result = String::new();
|
||||
|
||||
for i in 0..2 {
|
||||
let arg = unsafe { *args.offset(i) };
|
||||
if arg.is_null() {
|
||||
let _ = unsafe {
|
||||
nix_set_err_msg(
|
||||
context,
|
||||
nix_err_NIX_ERR_UNKNOWN,
|
||||
b"Missing argument in concat_strings_primop\0".as_ptr() as *const _,
|
||||
)
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// Force evaluation
|
||||
if unsafe { nix_value_force(context, state, arg) } != nix_err_NIX_OK {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's a string
|
||||
if unsafe { nix_get_type(context, arg) } != ValueType_NIX_TYPE_STRING {
|
||||
let _ = unsafe {
|
||||
static ITEMS: &[u8] = b"Expected string argument in concat_strings_primop\0";
|
||||
nix_set_err_msg(context, nix_err_NIX_ERR_UNKNOWN, ITEMS.as_ptr() as *const _)
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// Get string value using callback
|
||||
unsafe extern "C" fn string_callback(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: ::std::os::raw::c_uint,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
||||
let s = std::str::from_utf8(s).unwrap_or("");
|
||||
let result = unsafe { &mut *(user_data as *mut String) };
|
||||
result.push_str(s);
|
||||
}
|
||||
|
||||
let _ = unsafe {
|
||||
nix_get_string(
|
||||
context,
|
||||
arg,
|
||||
Some(string_callback),
|
||||
&mut result as *mut String as *mut ::std::os::raw::c_void,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
let result_cstr = CString::new(result).unwrap_or_else(|_| CString::new("").unwrap());
|
||||
let _ = unsafe { nix_init_string(context, ret, result_cstr.as_ptr()) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn primop_allocation_and_registration() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
// Create test data
|
||||
let test_data = Box::new(TestPrimOpData {
|
||||
call_count: AtomicU32::new(0),
|
||||
last_arg_value: AtomicU32::new(0),
|
||||
});
|
||||
let test_data_ptr = Box::into_raw(test_data);
|
||||
|
||||
// Create argument names
|
||||
let arg_names = [CString::new("x").unwrap()];
|
||||
let arg_name_ptrs: Vec<*const _> = arg_names.iter().map(|s| s.as_ptr()).collect();
|
||||
let mut arg_name_ptrs_null_terminated = arg_name_ptrs;
|
||||
arg_name_ptrs_null_terminated.push(std::ptr::null());
|
||||
|
||||
let name = CString::new("addOne").unwrap();
|
||||
let doc = CString::new("Add 1 to the argument").unwrap();
|
||||
|
||||
// Allocate PrimOp
|
||||
let primop = nix_alloc_primop(
|
||||
ctx,
|
||||
Some(add_one_primop),
|
||||
1, // arity
|
||||
name.as_ptr(),
|
||||
arg_name_ptrs_null_terminated.as_mut_ptr(),
|
||||
doc.as_ptr(),
|
||||
test_data_ptr as *mut ::std::os::raw::c_void,
|
||||
);
|
||||
|
||||
if !primop.is_null() {
|
||||
// Register the PrimOp globally
|
||||
let register_err = nix_register_primop(ctx, primop);
|
||||
// Registration may fail in some environments, but allocation should work
|
||||
assert!(
|
||||
register_err == nix_err_NIX_OK || register_err == nix_err_NIX_ERR_UNKNOWN,
|
||||
"Unexpected error code: {register_err}"
|
||||
);
|
||||
|
||||
// Test using the PrimOp by creating a value with it
|
||||
let primop_value = nix_alloc_value(ctx, state);
|
||||
assert!(!primop_value.is_null());
|
||||
|
||||
let init_err = nix_init_primop(ctx, primop_value, primop);
|
||||
assert_eq!(init_err, nix_err_NIX_OK);
|
||||
|
||||
// Clean up value
|
||||
nix_value_decref(ctx, primop_value);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
let _ = Box::from_raw(test_data_ptr);
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn primop_function_call() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
// Create test data
|
||||
let test_data = Box::new(TestPrimOpData {
|
||||
call_count: AtomicU32::new(0),
|
||||
last_arg_value: AtomicU32::new(0),
|
||||
});
|
||||
let test_data_ptr = Box::into_raw(test_data);
|
||||
|
||||
// Create simple hello world PrimOp (no arguments)
|
||||
let name = CString::new("helloWorld").unwrap();
|
||||
let doc = CString::new("Returns hello world string").unwrap();
|
||||
let mut empty_args: Vec<*const ::std::os::raw::c_char> = vec![std::ptr::null()];
|
||||
|
||||
let hello_primop = nix_alloc_primop(
|
||||
ctx,
|
||||
Some(hello_world_primop),
|
||||
0, // arity
|
||||
name.as_ptr(),
|
||||
empty_args.as_mut_ptr(),
|
||||
doc.as_ptr(),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
|
||||
if !hello_primop.is_null() {
|
||||
// Create a value with the PrimOp
|
||||
let primop_value = nix_alloc_value(ctx, state);
|
||||
assert!(!primop_value.is_null());
|
||||
|
||||
let init_err = nix_init_primop(ctx, primop_value, hello_primop);
|
||||
assert_eq!(init_err, nix_err_NIX_OK);
|
||||
|
||||
// Call the PrimOp (no arguments)
|
||||
let result = nix_alloc_value(ctx, state);
|
||||
assert!(!result.is_null());
|
||||
|
||||
let call_err = nix_value_call(ctx, state, primop_value, std::ptr::null_mut(), result);
|
||||
if call_err == nix_err_NIX_OK {
|
||||
// Force the result
|
||||
let force_err = nix_value_force(ctx, state, result);
|
||||
assert_eq!(force_err, nix_err_NIX_OK);
|
||||
|
||||
// Check if result is a string
|
||||
let result_type = nix_get_type(ctx, result);
|
||||
if result_type == ValueType_NIX_TYPE_STRING {
|
||||
// Get string value
|
||||
unsafe extern "C" fn string_callback(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: ::std::os::raw::c_uint,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let s =
|
||||
unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
||||
let s = std::str::from_utf8(s).unwrap_or("");
|
||||
let result = unsafe { &mut *(user_data as *mut Option<String>) };
|
||||
*result = Some(s.to_string());
|
||||
}
|
||||
|
||||
let mut string_result: Option<String> = None;
|
||||
let _ = nix_get_string(
|
||||
ctx,
|
||||
result,
|
||||
Some(string_callback),
|
||||
&mut string_result as *mut Option<String> as *mut ::std::os::raw::c_void,
|
||||
);
|
||||
|
||||
// Verify we got the expected string
|
||||
assert!(string_result
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("Hello from Rust"));
|
||||
}
|
||||
}
|
||||
|
||||
nix_value_decref(ctx, result);
|
||||
nix_value_decref(ctx, primop_value);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
let _ = Box::from_raw(test_data_ptr);
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn primop_with_arguments() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
// Create test data
|
||||
let test_data = Box::new(TestPrimOpData {
|
||||
call_count: AtomicU32::new(0),
|
||||
last_arg_value: AtomicU32::new(0),
|
||||
});
|
||||
let test_data_ptr = Box::into_raw(test_data);
|
||||
|
||||
// Create add one PrimOp
|
||||
let arg_names = [CString::new("x").unwrap()];
|
||||
let arg_name_ptrs: Vec<*const _> = arg_names.iter().map(|s| s.as_ptr()).collect();
|
||||
let mut arg_name_ptrs_null_terminated = arg_name_ptrs;
|
||||
arg_name_ptrs_null_terminated.push(std::ptr::null());
|
||||
|
||||
let name = CString::new("addOne").unwrap();
|
||||
let doc = CString::new("Add 1 to the argument").unwrap();
|
||||
|
||||
let add_primop = nix_alloc_primop(
|
||||
ctx,
|
||||
Some(add_one_primop),
|
||||
1, // arity
|
||||
name.as_ptr(),
|
||||
arg_name_ptrs_null_terminated.as_mut_ptr(),
|
||||
doc.as_ptr(),
|
||||
test_data_ptr as *mut ::std::os::raw::c_void,
|
||||
);
|
||||
|
||||
if !add_primop.is_null() {
|
||||
// Create a value with the PrimOp
|
||||
let primop_value = nix_alloc_value(ctx, state);
|
||||
assert!(!primop_value.is_null());
|
||||
|
||||
let init_err = nix_init_primop(ctx, primop_value, add_primop);
|
||||
assert_eq!(init_err, nix_err_NIX_OK);
|
||||
|
||||
// Create an integer argument
|
||||
let arg_value = nix_alloc_value(ctx, state);
|
||||
assert!(!arg_value.is_null());
|
||||
|
||||
let init_arg_err = nix_init_int(ctx, arg_value, 42);
|
||||
assert_eq!(init_arg_err, nix_err_NIX_OK);
|
||||
|
||||
// Call the PrimOp with the argument
|
||||
let result = nix_alloc_value(ctx, state);
|
||||
assert!(!result.is_null());
|
||||
|
||||
let call_err = nix_value_call(ctx, state, primop_value, arg_value, result);
|
||||
if call_err == nix_err_NIX_OK {
|
||||
// Force the result
|
||||
let force_err = nix_value_force(ctx, state, result);
|
||||
assert_eq!(force_err, nix_err_NIX_OK);
|
||||
|
||||
// Check if result is an integer
|
||||
let result_type = nix_get_type(ctx, result);
|
||||
if result_type == ValueType_NIX_TYPE_INT {
|
||||
let result_value = nix_get_int(ctx, result);
|
||||
assert_eq!(result_value, 43); // 42 + 1
|
||||
|
||||
// Verify callback was called
|
||||
let test_data_ref = &*test_data_ptr;
|
||||
assert_eq!(test_data_ref.call_count.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(test_data_ref.last_arg_value.load(Ordering::SeqCst), 42);
|
||||
}
|
||||
}
|
||||
|
||||
nix_value_decref(ctx, result);
|
||||
nix_value_decref(ctx, arg_value);
|
||||
nix_value_decref(ctx, primop_value);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
let _ = Box::from_raw(test_data_ptr);
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn primop_multi_argument() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
// Create concat strings PrimOp
|
||||
let arg_names = [CString::new("s1").unwrap(), CString::new("s2").unwrap()];
|
||||
let arg_name_ptrs: Vec<*const _> = arg_names.iter().map(|s| s.as_ptr()).collect();
|
||||
let mut arg_name_ptrs_null_terminated = arg_name_ptrs;
|
||||
arg_name_ptrs_null_terminated.push(std::ptr::null());
|
||||
|
||||
let name = CString::new("concatStrings").unwrap();
|
||||
let doc = CString::new("Concatenate two strings").unwrap();
|
||||
|
||||
let concat_primop = nix_alloc_primop(
|
||||
ctx,
|
||||
Some(concat_strings_primop),
|
||||
2, // arity
|
||||
name.as_ptr(),
|
||||
arg_name_ptrs_null_terminated.as_mut_ptr(),
|
||||
doc.as_ptr(),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
|
||||
if !concat_primop.is_null() {
|
||||
// Create a value with the PrimOp
|
||||
let primop_value = nix_alloc_value(ctx, state);
|
||||
assert!(!primop_value.is_null());
|
||||
|
||||
let init_err = nix_init_primop(ctx, primop_value, concat_primop);
|
||||
assert_eq!(init_err, nix_err_NIX_OK);
|
||||
|
||||
// Create string arguments
|
||||
let arg1 = nix_alloc_value(ctx, state);
|
||||
let arg2 = nix_alloc_value(ctx, state);
|
||||
assert!(!arg1.is_null() && !arg2.is_null());
|
||||
|
||||
let hello_cstr = CString::new("Hello, ").unwrap();
|
||||
let world_cstr = CString::new("World!").unwrap();
|
||||
|
||||
let init_arg1_err = nix_init_string(ctx, arg1, hello_cstr.as_ptr());
|
||||
let init_arg2_err = nix_init_string(ctx, arg2, world_cstr.as_ptr());
|
||||
assert_eq!(init_arg1_err, nix_err_NIX_OK);
|
||||
assert_eq!(init_arg2_err, nix_err_NIX_OK);
|
||||
|
||||
// Test multi-argument call using nix_value_call_multi
|
||||
let mut args = [arg1, arg2];
|
||||
let result = nix_alloc_value(ctx, state);
|
||||
assert!(!result.is_null());
|
||||
|
||||
let call_err =
|
||||
nix_value_call_multi(ctx, state, primop_value, 2, args.as_mut_ptr(), result);
|
||||
if call_err == nix_err_NIX_OK {
|
||||
// Force the result
|
||||
let force_err = nix_value_force(ctx, state, result);
|
||||
assert_eq!(force_err, nix_err_NIX_OK);
|
||||
|
||||
// Check if result is a string
|
||||
let result_type = nix_get_type(ctx, result);
|
||||
if result_type == ValueType_NIX_TYPE_STRING {
|
||||
unsafe extern "C" fn string_callback(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: ::std::os::raw::c_uint,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let s =
|
||||
unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
||||
let s = std::str::from_utf8(s).unwrap_or("");
|
||||
let result = unsafe { &mut *(user_data as *mut Option<String>) };
|
||||
*result = Some(s.to_string());
|
||||
}
|
||||
|
||||
let mut string_result: Option<String> = None;
|
||||
let _ = nix_get_string(
|
||||
ctx,
|
||||
result,
|
||||
Some(string_callback),
|
||||
&mut string_result as *mut Option<String> as *mut ::std::os::raw::c_void,
|
||||
);
|
||||
|
||||
// Verify concatenation worked
|
||||
assert_eq!(string_result.as_deref(), Some("Hello, World!"));
|
||||
}
|
||||
}
|
||||
|
||||
nix_value_decref(ctx, result);
|
||||
nix_value_decref(ctx, arg2);
|
||||
nix_value_decref(ctx, arg1);
|
||||
nix_value_decref(ctx, primop_value);
|
||||
}
|
||||
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn primop_error_handling() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let err = nix_libexpr_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let builder = nix_eval_state_builder_new(ctx, store);
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let load_err = nix_eval_state_builder_load(ctx, builder);
|
||||
assert_eq!(load_err, nix_err_NIX_OK);
|
||||
|
||||
let state = nix_eval_state_build(ctx, builder);
|
||||
assert!(!state.is_null());
|
||||
|
||||
// Test invalid PrimOp allocation (NULL callback)
|
||||
let name = CString::new("invalid").unwrap();
|
||||
let doc = CString::new("Invalid PrimOp").unwrap();
|
||||
let mut empty_args: Vec<*const ::std::os::raw::c_char> = vec![std::ptr::null()];
|
||||
|
||||
let _invalid_primop = nix_alloc_primop(
|
||||
ctx,
|
||||
None, // NULL callback should cause error
|
||||
0,
|
||||
name.as_ptr(),
|
||||
empty_args.as_mut_ptr(),
|
||||
doc.as_ptr(),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
|
||||
// Test initializing value with NULL PrimOp (should fail)
|
||||
let test_value = nix_alloc_value(ctx, state);
|
||||
assert!(!test_value.is_null());
|
||||
|
||||
nix_value_decref(ctx, test_value);
|
||||
nix_state_free(state);
|
||||
nix_eval_state_builder_free(builder);
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
278
nixide-sys/tests/store.rs
Normal file
278
nixide-sys/tests/store.rs
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use std::{ffi::CString, ptr};
|
||||
|
||||
use nixide_sys::*;
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn libstore_init_and_open_free() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
// Open the default store (NULL URI, NULL params)
|
||||
let store = nix_store_open(ctx, ptr::null(), ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
// Free the store and context
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn parse_and_clone_free_store_path() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, ptr::null(), ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
// Parse a store path (I'm using a dummy path, will likely be invalid but
|
||||
// should not segfault) XXX: store_path may be null if path is invalid,
|
||||
// but should not crash
|
||||
let path_str = CString::new("/nix/store/dummy-path").unwrap();
|
||||
let store_path = nix_store_parse_path(ctx, store, path_str.as_ptr());
|
||||
|
||||
if !store_path.is_null() {
|
||||
// Clone and free
|
||||
let cloned = nix_store_path_clone(store_path);
|
||||
assert!(!cloned.is_null());
|
||||
nix_store_path_free(cloned);
|
||||
nix_store_path_free(store_path);
|
||||
}
|
||||
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn store_get_uri_and_storedir() {
|
||||
unsafe extern "C" fn string_callback(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: ::std::os::raw::c_uint,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
||||
let s = std::str::from_utf8(s).unwrap();
|
||||
let out = user_data.cast::<Option<String>>();
|
||||
unsafe { *out = Some(s.to_string()) };
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, ptr::null(), ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let mut uri: Option<String> = None;
|
||||
let res = nix_store_get_uri(ctx, store, Some(string_callback), (&raw mut uri).cast());
|
||||
assert_eq!(res, nix_err_NIX_OK);
|
||||
assert!(uri.is_some());
|
||||
|
||||
let mut storedir: Option<String> = None;
|
||||
let res = nix_store_get_storedir(
|
||||
ctx,
|
||||
store,
|
||||
Some(string_callback),
|
||||
(&raw mut storedir).cast(),
|
||||
);
|
||||
assert_eq!(res, nix_err_NIX_OK);
|
||||
assert!(storedir.is_some());
|
||||
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn libstore_init_no_load_config() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libstore_init_no_load_config(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn store_is_valid_path_and_real_path() {
|
||||
unsafe extern "C" fn string_callback(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: ::std::os::raw::c_uint,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
||||
let s = std::str::from_utf8(s).unwrap();
|
||||
let out = user_data.cast::<Option<String>>();
|
||||
unsafe { *out = Some(s.to_string()) };
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
// Use a dummy path (should not be valid, but should not crash)
|
||||
let path_str = CString::new("/nix/store/dummy-path").unwrap();
|
||||
let store_path = nix_store_parse_path(ctx, store, path_str.as_ptr());
|
||||
if !store_path.is_null() {
|
||||
let valid = nix_store_is_valid_path(ctx, store, store_path);
|
||||
assert!(!valid, "Dummy path should not be valid");
|
||||
|
||||
let mut real_path: Option<String> = None;
|
||||
let res = nix_store_real_path(
|
||||
ctx,
|
||||
store,
|
||||
store_path,
|
||||
Some(string_callback),
|
||||
(&raw mut real_path).cast(),
|
||||
);
|
||||
// May fail, but should not crash
|
||||
assert!(res == nix_err_NIX_OK || res == nix_err_NIX_ERR_UNKNOWN);
|
||||
nix_store_path_free(store_path);
|
||||
}
|
||||
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn store_path_name() {
|
||||
unsafe extern "C" fn string_callback(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: ::std::os::raw::c_uint,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
||||
let s = std::str::from_utf8(s).unwrap();
|
||||
let out = user_data.cast::<Option<String>>();
|
||||
unsafe { *out = Some(s.to_string()) };
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let path_str = CString::new("/nix/store/foo-bar-baz").unwrap();
|
||||
let store_path = nix_store_parse_path(ctx, store, path_str.as_ptr());
|
||||
if !store_path.is_null() {
|
||||
let mut name: Option<String> = None;
|
||||
nix_store_path_name(store_path, Some(string_callback), (&raw mut name).cast());
|
||||
// Should extract the name part ("foo-bar-baz")
|
||||
assert!(name.as_deref().unwrap_or("").contains("foo-bar-baz"));
|
||||
nix_store_path_free(store_path);
|
||||
}
|
||||
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn store_get_version() {
|
||||
unsafe extern "C" fn string_callback(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: ::std::os::raw::c_uint,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
||||
let s = std::str::from_utf8(s).unwrap();
|
||||
let out = user_data.cast::<Option<String>>();
|
||||
unsafe { *out = Some(s.to_string()) };
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
let mut version: Option<String> = None;
|
||||
let res =
|
||||
nix_store_get_version(ctx, store, Some(string_callback), (&raw mut version).cast());
|
||||
assert_eq!(res, nix_err_NIX_OK);
|
||||
// Version may be empty for dummy stores, but should not crash
|
||||
assert!(version.is_some());
|
||||
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn store_realise_and_copy_closure() {
|
||||
unsafe extern "C" fn realise_callback(
|
||||
_userdata: *mut ::std::os::raw::c_void,
|
||||
outname: *const ::std::os::raw::c_char,
|
||||
out: *const StorePath,
|
||||
) {
|
||||
// Just check that callback is called with non-null pointers
|
||||
assert!(!outname.is_null());
|
||||
assert!(!out.is_null());
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libstore_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
let store = nix_store_open(ctx, std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(!store.is_null());
|
||||
|
||||
// Use a dummy path (should not crash, may not realise)
|
||||
let path_str = CString::new("/nix/store/dummy-path").unwrap();
|
||||
let store_path = nix_store_parse_path(ctx, store, path_str.as_ptr());
|
||||
if !store_path.is_null() {
|
||||
// Realise (should fail, but must not crash)
|
||||
let _ = nix_store_realise(
|
||||
ctx,
|
||||
store,
|
||||
store_path,
|
||||
std::ptr::null_mut(),
|
||||
Some(realise_callback),
|
||||
);
|
||||
|
||||
// Copy closure to same store (should fail, but must not crash)
|
||||
let _ = nix_store_copy_closure(ctx, store, store, store_path);
|
||||
|
||||
nix_store_path_free(store_path);
|
||||
}
|
||||
|
||||
nix_store_free(store);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
152
nixide-sys/tests/util.rs
Normal file
152
nixide-sys/tests/util.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
use nixide_sys::*;
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn context_create_and_free() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn libutil_init() {
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn version_get() {
|
||||
unsafe {
|
||||
let version_ptr = nix_version_get();
|
||||
assert!(!version_ptr.is_null());
|
||||
let version = CStr::from_ptr(version_ptr).to_string_lossy();
|
||||
assert!(!version.is_empty(), "Version string should not be empty");
|
||||
assert!(
|
||||
version.chars().next().unwrap().is_ascii_digit(),
|
||||
"Version string should start with a digit: {version}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn setting_set_and_get() {
|
||||
unsafe extern "C" fn string_callback(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: ::std::os::raw::c_uint,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
||||
let s = std::str::from_utf8(s).unwrap();
|
||||
let out = user_data.cast::<Option<String>>();
|
||||
*unsafe { &mut *out } = Some(s.to_string());
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
// Set a setting (use a dummy/extra setting to avoid breaking global config)
|
||||
let key = CString::new("extra-test-setting").unwrap();
|
||||
let value = CString::new("test-value").unwrap();
|
||||
let set_err = nix_setting_set(ctx, key.as_ptr(), value.as_ptr());
|
||||
// Setting may not exist, but should not crash
|
||||
assert!(
|
||||
set_err == nix_err_NIX_OK || set_err == nix_err_NIX_ERR_KEY,
|
||||
"Unexpected error code: {set_err}"
|
||||
);
|
||||
|
||||
// Try to get the setting (may not exist, but should not crash)
|
||||
let mut got: Option<String> = None;
|
||||
let get_err = nix_setting_get(
|
||||
ctx,
|
||||
key.as_ptr(),
|
||||
Some(string_callback),
|
||||
(&raw mut got).cast(),
|
||||
);
|
||||
assert!(
|
||||
get_err == nix_err_NIX_OK || get_err == nix_err_NIX_ERR_KEY,
|
||||
"Unexpected error code: {get_err}"
|
||||
);
|
||||
// If OK, we should have gotten a value
|
||||
if get_err == nix_err_NIX_OK {
|
||||
assert_eq!(got.as_deref(), Some("test-value"));
|
||||
}
|
||||
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn error_handling_apis() {
|
||||
unsafe extern "C" fn string_callback(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: ::std::os::raw::c_uint,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
||||
let s = std::str::from_utf8(s).unwrap();
|
||||
let out = user_data.cast::<Option<String>>();
|
||||
*unsafe { &mut *out } = Some(s.to_string());
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ctx = nix_c_context_create();
|
||||
assert!(!ctx.is_null());
|
||||
let err = nix_libutil_init(ctx);
|
||||
assert_eq!(err, nix_err_NIX_OK);
|
||||
|
||||
// Set an error message
|
||||
let msg = CString::new("custom error message").unwrap();
|
||||
let set_err = nix_set_err_msg(ctx, nix_err_NIX_ERR_UNKNOWN, msg.as_ptr());
|
||||
assert_eq!(set_err, nix_err_NIX_ERR_UNKNOWN);
|
||||
|
||||
// Get error code
|
||||
let code = nix_err_code(ctx);
|
||||
assert_eq!(code, nix_err_NIX_ERR_UNKNOWN);
|
||||
|
||||
// Get error message
|
||||
let mut len: std::os::raw::c_uint = 0;
|
||||
let err_msg_ptr = nix_err_msg(ctx, ctx, &mut len as *mut _);
|
||||
if !err_msg_ptr.is_null() && len > 0 {
|
||||
let err_msg = std::str::from_utf8(std::slice::from_raw_parts(
|
||||
err_msg_ptr as *const u8,
|
||||
len as usize,
|
||||
))
|
||||
.unwrap();
|
||||
assert!(err_msg.contains("custom error message"));
|
||||
}
|
||||
|
||||
// Get error info message (should work, but may be empty)
|
||||
let mut info: Option<String> = None;
|
||||
let _ = nix_err_info_msg(ctx, ctx, Some(string_callback), (&raw mut info).cast());
|
||||
|
||||
// Get error name (should work, but may be empty)
|
||||
let mut name: Option<String> = None;
|
||||
let _ = nix_err_name(ctx, ctx, Some(string_callback), (&raw mut name).cast());
|
||||
|
||||
// Clear error
|
||||
nix_clear_err(ctx);
|
||||
let code_after_clear = nix_err_code(ctx);
|
||||
assert_eq!(code_after_clear, nix_err_NIX_OK);
|
||||
|
||||
nix_c_context_free(ctx);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue