working sys level bindings :yippie:

This commit is contained in:
do butterflies cry? 2026-03-13 23:20:49 +10:00
parent 4508aeab76
commit e9022e675b
Signed by: cry
GPG key ID: F68745A836CA0412
12 changed files with 3091 additions and 45 deletions

3
.gitignore vendored
View file

@ -4,6 +4,3 @@ result-*
# Rust
/target
# Other
nixops4-bindings-sys

148
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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>

View file

@ -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

File diff suppressed because it is too large Load diff

78
nixide-sys/tests/flake.rs Normal file
View 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
View 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
View 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
View 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
View 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);
}
}