503 lines
16 KiB
Rust
503 lines
16 KiB
Rust
#![cfg(feature = "nix-expr-c")]
|
|
#![cfg(test)]
|
|
|
|
use core::ffi::{c_char, c_uint, c_void};
|
|
use std::ffi::CString;
|
|
use std::{ptr, slice, str};
|
|
|
|
use serial_test::serial;
|
|
|
|
use nixide_sys::*;
|
|
|
|
#[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, NixErr::Ok);
|
|
|
|
let err = nix_libstore_init(ctx);
|
|
assert_eq!(err, NixErr::Ok);
|
|
|
|
let err = nix_libexpr_init(ctx);
|
|
assert_eq!(err, NixErr::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 load_err = nix_eval_state_builder_load(ctx, builder);
|
|
assert_eq!(load_err, NixErr::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, NixErr::Ok);
|
|
|
|
// Test value-specific reference counting
|
|
let incref_err = nix_value_incref(ctx, value);
|
|
assert_eq!(incref_err, NixErr::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, NixErr::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, NixErr::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, NixErr::Ok);
|
|
|
|
let err = nix_libstore_init(ctx);
|
|
assert_eq!(err, NixErr::Ok);
|
|
|
|
let err = nix_libexpr_init(ctx);
|
|
assert_eq!(err, NixErr::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 load_err = nix_eval_state_builder_load(ctx, builder);
|
|
assert_eq!(load_err, NixErr::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, NixErr::Ok);
|
|
|
|
// Test general GC reference counting
|
|
let gc_incref_err = nix_gc_incref(ctx, value as *const c_void);
|
|
assert_eq!(gc_incref_err, NixErr::Ok);
|
|
|
|
// Value should still be accessible
|
|
let value_type = nix_get_type(ctx, value);
|
|
assert_eq!(value_type, ValueType::String);
|
|
|
|
// Test GC decrement
|
|
let gc_decref_err = nix_gc_decref(ctx, value as *const c_void);
|
|
assert_eq!(gc_decref_err, NixErr::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, NixErr::Ok);
|
|
|
|
let err = nix_libstore_init(ctx);
|
|
assert_eq!(err, NixErr::Ok);
|
|
|
|
let err = nix_libexpr_init(ctx);
|
|
assert_eq!(err, NixErr::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 load_err = nix_eval_state_builder_load(ctx, builder);
|
|
assert_eq!(load_err, NixErr::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 == NixErr::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, NixErr::Ok);
|
|
|
|
let err = nix_libstore_init(ctx);
|
|
assert_eq!(err, NixErr::Ok);
|
|
|
|
let err = nix_libexpr_init(ctx);
|
|
assert_eq!(err, NixErr::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 load_err = nix_eval_state_builder_load(ctx, builder);
|
|
assert_eq!(load_err, NixErr::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, NixErr::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, NixErr::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::String);
|
|
|
|
// Test string contents using callback
|
|
unsafe extern "C" fn string_callback(
|
|
start: *const c_char,
|
|
n: c_uint,
|
|
user_data: *mut c_void,
|
|
) {
|
|
let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };
|
|
let s = 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 c_void,
|
|
);
|
|
|
|
let _ = nix_get_string(
|
|
ctx,
|
|
copy,
|
|
Some(string_callback),
|
|
&mut copy_string as *mut Option<String> as *mut 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, NixErr::Ok);
|
|
assert_eq!(incref_copy, NixErr::Ok);
|
|
|
|
// Values should still be accessible after increment
|
|
assert_eq!(nix_get_type(ctx, original), ValueType::String);
|
|
assert_eq!(nix_get_type(ctx, copy), ValueType::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, NixErr::Ok);
|
|
|
|
let err = nix_libstore_init(ctx);
|
|
assert_eq!(err, NixErr::Ok);
|
|
|
|
let err = nix_libexpr_init(ctx);
|
|
assert_eq!(err, NixErr::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 load_err = nix_eval_state_builder_load(ctx, builder);
|
|
assert_eq!(load_err, NixErr::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, NixErr::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, NixErr::Ok);
|
|
|
|
let make_attrs_err1 = nix_make_attrs(ctx, attrs1, bindings_builder1);
|
|
assert_eq!(make_attrs_err1, NixErr::Ok);
|
|
|
|
// Insert first attrs into list
|
|
let list_insert_err1 = nix_list_builder_insert(ctx, list_builder, 0, attrs1);
|
|
assert_eq!(list_insert_err1, NixErr::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, NixErr::Ok);
|
|
|
|
let make_attrs_err2 = nix_make_attrs(ctx, attrs2, bindings_builder2);
|
|
assert_eq!(make_attrs_err2, NixErr::Ok);
|
|
|
|
let list_insert_err2 = nix_list_builder_insert(ctx, list_builder, 1, attrs2);
|
|
assert_eq!(list_insert_err2, NixErr::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, NixErr::Ok);
|
|
|
|
// Test the complex structure
|
|
assert_eq!(nix_get_type(ctx, final_list), ValueType::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::Attrs);
|
|
assert_eq!(nix_get_type(ctx, elem1), ValueType::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, NixErr::Ok);
|
|
|
|
// Force deep evaluation on copy
|
|
let deep_force_err = nix_value_force_deep(ctx, state, copied_list);
|
|
assert_eq!(deep_force_err, NixErr::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, ptr::null() as *const c_void);
|
|
|
|
// XXX: May succeed or fail depending on implementation. We can't really
|
|
// know, so assert both.
|
|
assert!(null_incref_err == NixErr::Ok || null_incref_err == NixErr::Unknown);
|
|
|
|
let null_decref_err = nix_gc_decref(ctx, ptr::null() as *const c_void);
|
|
assert!(null_decref_err == NixErr::Ok || null_decref_err == NixErr::Unknown);
|
|
|
|
let null_value_incref_err = nix_value_incref(ctx, ptr::null_mut());
|
|
// Some Nix APIs gracefully handle null pointers and return OK
|
|
assert!(null_value_incref_err == NixErr::Ok || null_value_incref_err == NixErr::Unknown);
|
|
|
|
let null_value_decref_err = nix_value_decref(ctx, ptr::null_mut());
|
|
// Some Nix APIs gracefully handle null pointers and return OK
|
|
assert!(null_value_decref_err == NixErr::Ok || null_value_decref_err == NixErr::Unknown);
|
|
|
|
// Test copy with NULL values
|
|
let err = nix_libutil_init(ctx);
|
|
assert_eq!(err, NixErr::Ok);
|
|
|
|
let err = nix_libstore_init(ctx);
|
|
assert_eq!(err, NixErr::Ok);
|
|
|
|
let err = nix_libexpr_init(ctx);
|
|
assert_eq!(err, NixErr::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 load_err = nix_eval_state_builder_load(ctx, builder);
|
|
assert_eq!(load_err, NixErr::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, ptr::null_mut());
|
|
assert_ne!(copy_from_null_err, NixErr::Ok);
|
|
|
|
let copy_to_null_err = nix_copy_value(ctx, ptr::null_mut(), valid_value);
|
|
assert_ne!(copy_to_null_err, NixErr::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);
|
|
}
|
|
}
|