nixide/nixide-sys/tests/memory.rs

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);
}
}