2024-07-19 17:54:31 +02:00
use crate ::primop ;
2024-06-14 18:42:23 +02:00
use crate ::value ::{ Int , Value , ValueType } ;
2024-03-19 14:43:01 +01:00
use anyhow ::Context as _ ;
use anyhow ::{ bail , Result } ;
2024-07-19 18:29:56 +02:00
use cstr ::cstr ;
2024-03-19 14:43:01 +01:00
use lazy_static ::lazy_static ;
use nix_c_raw as raw ;
2024-04-08 16:54:36 +02:00
use nix_store ::path ::StorePath ;
2024-06-27 16:01:40 +02:00
use nix_store ::store ::{ Store , StoreWeak } ;
2024-03-19 14:43:01 +01:00
use nix_util ::context ::Context ;
2024-05-22 14:45:12 +02:00
use nix_util ::string_return ::{ callback_get_result_string , callback_get_result_string_data } ;
2024-06-15 12:40:45 +02:00
use nix_util ::{ check_call , check_call_opt_key , result_string_init } ;
2024-07-19 18:04:34 +02:00
use std ::ffi ::{ c_char , CString } ;
2024-04-09 16:20:18 +02:00
use std ::os ::raw ::c_uint ;
2024-05-13 19:26:58 +02:00
use std ::ptr ::{ null , null_mut , NonNull } ;
2024-06-27 16:01:40 +02:00
use std ::sync ::{ Arc , Weak } ;
2024-03-19 14:43:01 +01:00
lazy_static! {
static ref INIT : Result < ( ) > = {
unsafe {
raw ::GC_allow_register_threads ( ) ;
2024-07-02 16:35:32 -04:00
check_call! ( raw ::libexpr_init ( & mut Context ::new ( ) ) ) ? ;
2024-06-15 14:51:26 +02:00
Ok ( ( ) )
2024-03-19 14:43:01 +01:00
}
} ;
}
pub fn init ( ) -> Result < ( ) > {
let x = INIT . as_ref ( ) ;
match x {
Ok ( _ ) = > Ok ( ( ) ) ,
Err ( e ) = > {
// Couldn't just clone the error, so we have to print it here.
Err ( anyhow ::format_err! ( " nix_libstore_init error: {} " , e ) )
}
}
}
2024-04-08 16:54:36 +02:00
pub struct RealisedString {
pub s : String ,
pub paths : Vec < StorePath > ,
}
2024-06-27 16:01:40 +02:00
/// A [Weak] reference to an [EvalState]
pub struct EvalStateWeak {
inner : Weak < EvalStateRef > ,
store : StoreWeak ,
}
impl EvalStateWeak {
/// Upgrade the weak reference to a proper [EvalState].
///
/// If no normal reference to the [EvalState] is around anymore elsewhere, this fails by returning `None`.
pub fn upgrade ( & self ) -> Option < EvalState > {
self . inner . upgrade ( ) . and_then ( | eval_state | {
self . store . upgrade ( ) . map ( | store | EvalState {
2024-11-27 10:26:39 +01:00
eval_state ,
store ,
2024-06-27 16:01:40 +02:00
context : Context ::new ( ) ,
} )
} )
}
}
2024-06-26 21:00:54 +02:00
struct EvalStateRef {
2024-03-19 14:43:01 +01:00
eval_state : NonNull < raw ::EvalState > ,
2024-06-26 21:00:54 +02:00
}
impl EvalStateRef {
2024-12-17 10:40:03 +01:00
/// # Safety
///
/// This function is unsafe because it returns a raw pointer. The caller must ensure that the pointer is not used beyond the lifetime of the underlying [raw::EvalState].
unsafe fn as_ptr ( & self ) -> * mut raw ::EvalState {
2024-06-26 21:00:54 +02:00
self . eval_state . as_ptr ( )
}
}
impl Drop for EvalStateRef {
fn drop ( & mut self ) {
unsafe {
raw ::state_free ( self . eval_state . as_ptr ( ) ) ;
}
}
}
pub struct EvalState {
eval_state : Arc < EvalStateRef > ,
2024-03-19 14:43:01 +01:00
store : Store ,
2024-07-19 17:54:31 +02:00
pub ( crate ) context : Context ,
2024-03-19 14:43:01 +01:00
}
impl EvalState {
2024-05-13 19:26:58 +02:00
pub fn new < ' a > ( store : Store , lookup_path : impl IntoIterator < Item = & ' a str > ) -> Result < Self > {
2024-06-15 12:40:45 +02:00
let mut context = Context ::new ( ) ;
2024-03-19 14:43:01 +01:00
2024-05-13 19:26:58 +02:00
// this intermediate value must be here and must not be moved
// because it owns the data the `*const c_char` pointers point to.
let lookup_path : Vec < CString > = lookup_path
. into_iter ( )
. map ( | path | {
CString ::new ( path ) . with_context ( | | {
format! ( " EvalState::new: lookup_path ` {path} ` contains null byte " )
} )
} )
. collect ::< Result < _ > > ( ) ? ;
// this intermediate value owns the data the `*mut *const c_char` pointer points to.
let mut lookup_path : Vec < * const c_char > = lookup_path
. iter ( )
. map ( | s | s . as_ptr ( ) )
. chain ( std ::iter ::once ( null ( ) ) ) // signal the end of the array
. collect ( ) ;
2024-03-19 14:43:01 +01:00
init ( ) ? ;
2024-06-15 14:51:26 +02:00
let eval_state = unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::state_create (
& mut context ,
lookup_path . as_mut_ptr ( ) ,
store . raw_ptr ( )
) )
2024-06-15 14:51:26 +02:00
} ? ;
2024-03-19 14:43:01 +01:00
Ok ( EvalState {
2024-06-26 21:00:54 +02:00
eval_state : Arc ::new ( EvalStateRef {
eval_state : NonNull ::new ( eval_state ) . unwrap_or_else ( | | {
panic! ( " nix_state_create returned a null pointer without an error " )
} ) ,
2024-06-14 17:28:50 +02:00
} ) ,
2024-03-19 14:43:01 +01:00
store ,
context ,
} )
}
2024-12-17 10:40:03 +01:00
/// # Safety
///
/// This function is unsafe because it returns a raw pointer. The caller must ensure that the pointer is not used beyond the lifetime of this `EvalState`.
pub unsafe fn raw_ptr ( & self ) -> * mut raw ::EvalState {
2024-03-19 14:43:01 +01:00
self . eval_state . as_ptr ( )
}
pub fn store ( & self ) -> & Store {
& self . store
}
2024-06-27 16:01:40 +02:00
pub fn weak_ref ( & self ) -> EvalStateWeak {
EvalStateWeak {
inner : Arc ::downgrade ( & self . eval_state ) ,
store : self . store . weak_ref ( ) ,
}
}
2024-05-29 02:52:43 +02:00
/// Parses and evaluates a Nix expression `expr`.
///
/// Expressions can contain relative paths such as `./.` that are resolved relative to the given `path`.
///
/// # Examples
///
/// ```
/// # use nix_expr::eval_state::EvalState;
/// use nix_store::store::Store;
/// use nix_expr::value::Value;
///
/// # fn main() -> anyhow::Result<()> {
2024-12-13 00:54:35 +01:00
/// # let mut es = EvalState::new(Store::open(None, [])?, [])?;
2024-05-29 02:52:43 +02:00
/// let v: Value = es.eval_from_string("42", ".")?;
/// assert_eq!(es.require_int(&v)?, 42);
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " nix_expr_eval_from_string " ) ]
2024-06-15 12:40:45 +02:00
pub fn eval_from_string ( & mut self , expr : & str , path : & str ) -> Result < Value > {
2024-03-19 14:43:01 +01:00
let expr_ptr =
CString ::new ( expr ) . with_context ( | | " eval_from_string: expr contains null byte " ) ? ;
let path_ptr =
CString ::new ( path ) . with_context ( | | " eval_from_string: path contains null byte " ) ? ;
unsafe {
2024-06-14 17:28:50 +02:00
let value = self . new_value_uninitialized ( ) ? ;
2024-07-02 16:35:32 -04:00
check_call! ( raw ::expr_eval_from_string (
2024-06-15 14:34:30 +02:00
& mut self . context ,
self . eval_state . as_ptr ( ) ,
2024-06-15 12:40:45 +02:00
expr_ptr . as_ptr ( ) ,
path_ptr . as_ptr ( ) ,
value . raw_ptr ( )
2024-07-02 16:35:32 -04:00
) ) ? ;
2024-06-14 17:28:50 +02:00
Ok ( value )
}
2024-03-19 14:43:01 +01:00
}
2024-05-24 11:15:43 +02:00
/// Try turn any Value into a Value that isn't a Thunk.
2024-06-15 12:40:45 +02:00
pub fn force ( & mut self , v : & Value ) -> Result < ( ) > {
2024-06-15 14:34:30 +02:00
unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::value_force (
& mut self . context ,
self . eval_state . as_ptr ( ) ,
v . raw_ptr ( )
) )
2024-06-15 14:34:30 +02:00
} ? ;
2024-06-14 17:28:50 +02:00
Ok ( ( ) )
2024-03-19 14:43:01 +01:00
}
2024-06-15 12:40:45 +02:00
pub fn value_type_unforced ( & mut self , value : & Value ) -> Option < ValueType > {
2024-07-02 16:35:32 -04:00
let r = unsafe { check_call! ( raw ::get_type ( & mut self . context , value . raw_ptr ( ) ) ) } ;
2024-06-14 15:50:07 +02:00
// .unwrap(): no reason for this to fail, as it does not evaluate
2024-06-14 17:28:50 +02:00
ValueType ::from_raw ( r . unwrap ( ) )
2024-03-19 14:43:01 +01:00
}
2024-06-15 12:40:45 +02:00
pub fn value_type ( & mut self , value : & Value ) -> Result < ValueType > {
2024-06-03 04:18:58 +02:00
match self . value_type_unforced ( value ) {
2024-06-14 18:42:23 +02:00
Some ( a ) = > Ok ( a ) ,
None = > {
2024-06-03 04:18:58 +02:00
self . force ( value ) ? ;
match self . value_type_unforced ( value ) {
2024-06-14 18:42:23 +02:00
Some ( a ) = > Ok ( a ) ,
None = > {
panic! ( " Nix value must not be thunk after being forced. " )
2024-06-03 04:18:58 +02:00
}
}
}
2024-03-19 14:43:01 +01:00
}
}
2024-06-15 12:40:45 +02:00
pub fn require_int ( & mut self , v : & Value ) -> Result < Int > {
2024-06-14 15:50:07 +02:00
let t = self . value_type ( v ) ? ;
2024-04-09 13:13:52 +02:00
if t ! = ValueType ::Int {
bail! ( " expected an int, but got a {:?} " , t ) ;
}
2024-07-02 16:35:32 -04:00
unsafe { check_call! ( raw ::get_int ( & mut self . context , v . raw_ptr ( ) ) ) }
2024-04-09 13:13:52 +02:00
}
2024-12-02 14:02:51 +01:00
2024-04-09 16:20:18 +02:00
/// Evaluate, and require that the value is an attrset.
/// Returns a list of the keys in the attrset.
2024-12-02 14:02:51 +01:00
///
/// NOTE: this currently implements its own sorting, which probably matches Nix's implementation, but is not guaranteed.
pub fn require_attrs_names ( & mut self , v : & Value ) -> Result < Vec < String > > {
self . require_attrs_names_unsorted ( v ) . map ( | mut v | {
v . sort ( ) ;
v
} )
}
/// For when [require_attrs_names] isn't fast enough.
/// Only use when it's ok that the keys are returned in an arbitrary order.
2024-12-02 13:52:17 +01:00
pub fn require_attrs_names_unsorted ( & mut self , v : & Value ) -> Result < Vec < String > > {
2024-06-14 15:40:11 +02:00
let t = self . value_type ( v ) ? ;
2024-04-09 16:20:18 +02:00
if t ! = ValueType ::AttrSet {
bail! ( " expected an attrset, but got a {:?} " , t ) ;
}
2024-07-02 16:35:32 -04:00
let n = unsafe { check_call! ( raw ::get_attrs_size ( & mut self . context , v . raw_ptr ( ) ) ) } ? ;
2024-06-15 14:51:26 +02:00
let mut attrs = Vec ::with_capacity ( n as usize ) ;
2024-06-14 17:28:50 +02:00
for i in 0 .. n {
2024-06-15 12:40:45 +02:00
let cstr_ptr : * const i8 = unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::get_attr_name_byidx (
2024-06-15 14:34:30 +02:00
& mut self . context ,
2024-06-15 12:40:45 +02:00
v . raw_ptr ( ) ,
2024-06-15 14:34:30 +02:00
self . eval_state . as_ptr ( ) ,
2024-06-15 12:40:45 +02:00
i as c_uint
2024-07-02 16:35:32 -04:00
) )
2024-06-15 12:40:45 +02:00
} ? ;
2024-06-14 17:28:50 +02:00
let cstr = unsafe { std ::ffi ::CStr ::from_ptr ( cstr_ptr ) } ;
let s = cstr
. to_str ( )
. map_err ( | e | anyhow ::format_err! ( " Nix attrset key is not valid UTF-8: {} " , e ) ) ? ;
2024-06-15 14:51:26 +02:00
attrs . insert ( i as usize , s . to_owned ( ) ) ;
2024-04-09 16:20:18 +02:00
}
Ok ( attrs )
}
2024-04-09 13:13:52 +02:00
2024-06-14 18:21:13 +02:00
/// Evaluate, require that the value is an attrset, and select an attribute by name.
2024-06-15 12:40:45 +02:00
pub fn require_attrs_select ( & mut self , v : & Value , attr_name : & str ) -> Result < Value > {
2024-06-14 18:21:13 +02:00
let t = self . value_type ( v ) ? ;
if t ! = ValueType ::AttrSet {
bail! ( " expected an attrset, but got a {:?} " , t ) ;
2024-04-10 14:32:42 +02:00
}
2024-06-14 18:21:13 +02:00
let attr_name = CString ::new ( attr_name )
. with_context ( | | " require_attrs_select: attrName contains null byte " ) ? ;
2024-12-17 10:40:03 +01:00
unsafe {
let v2 = check_call! ( raw ::get_attr_byname (
2024-06-15 14:34:30 +02:00
& mut self . context ,
2024-06-15 12:40:45 +02:00
v . raw_ptr ( ) ,
2024-06-15 14:34:30 +02:00
self . eval_state . as_ptr ( ) ,
2024-06-15 12:40:45 +02:00
attr_name . as_ptr ( )
2025-01-31 07:38:11 +01:00
) ) ;
match v2 {
Ok ( v2 ) = > Ok ( Value ::new ( v2 ) ) ,
Err ( e ) = > {
// As of Nix 2.26, the error message is not helpful when it
// is simply missing, so we provide a better one. (Note that
// missing attributes requested by Nix expressions OTOH is a
// different error message which works fine.)
if e . to_string ( ) = = " missing attribute " {
bail! ( " attribute `{}` not found " , attr_name . to_string_lossy ( ) ) ;
} else {
Err ( e )
}
}
}
2024-12-17 10:40:03 +01:00
}
2024-04-10 14:32:42 +02:00
}
/// Evaluate, require that the value is an attrset, and select an attribute by name.
2024-06-14 18:21:13 +02:00
///
/// Return `Err(...)` if `v` is not an attrset, or if some other error occurred.
///
/// Return `Ok(None)` if the attribute is not present.
///
/// Return `Ok(Some(value))` if the attribute is present.
2024-06-15 12:40:45 +02:00
pub fn require_attrs_select_opt (
& mut self ,
v : & Value ,
attr_name : & str ,
) -> Result < Option < Value > > {
2024-06-14 15:40:11 +02:00
let t = self . value_type ( v ) ? ;
2024-04-10 14:32:42 +02:00
if t ! = ValueType ::AttrSet {
bail! ( " expected an attrset, but got a {:?} " , t ) ;
}
let attr_name = CString ::new ( attr_name )
. with_context ( | | " require_attrs_select_opt: attrName contains null byte " ) ? ;
2024-06-15 12:40:45 +02:00
let v2 = unsafe {
2024-07-02 16:44:04 -04:00
check_call_opt_key! ( raw ::get_attr_byname (
2024-06-15 14:55:23 +02:00
& mut self . context ,
2024-06-15 12:40:45 +02:00
v . raw_ptr ( ) ,
2024-06-15 14:55:23 +02:00
self . eval_state . as_ptr ( ) ,
2024-06-15 12:40:45 +02:00
attr_name . as_ptr ( )
2024-07-02 16:44:04 -04:00
) )
2024-06-15 12:40:45 +02:00
} ? ;
2024-12-17 10:40:03 +01:00
Ok ( v2 . map ( | x | unsafe { Value ::new ( x ) } ) )
2024-04-10 14:32:42 +02:00
}
2024-04-09 13:27:27 +02:00
/// Create a new value containing the passed string.
/// Returns a string value without any string context.
2024-06-15 12:40:45 +02:00
pub fn new_value_str ( & mut self , s : & str ) -> Result < Value > {
2024-04-09 13:27:27 +02:00
let s = CString ::new ( s ) . with_context ( | | " new_value_str: contains null byte " ) ? ;
let v = unsafe {
2024-06-14 17:28:50 +02:00
let value = self . new_value_uninitialized ( ) ? ;
2024-07-02 16:35:32 -04:00
check_call! ( raw ::init_string (
& mut self . context ,
value . raw_ptr ( ) ,
s . as_ptr ( )
) ) ? ;
2024-04-09 13:27:27 +02:00
value
} ;
Ok ( v )
}
2024-06-15 12:40:45 +02:00
pub fn new_value_int ( & mut self , i : Int ) -> Result < Value > {
2024-04-09 13:29:14 +02:00
let v = unsafe {
2024-06-14 17:28:50 +02:00
let value = self . new_value_uninitialized ( ) ? ;
2024-07-02 16:35:32 -04:00
check_call! ( raw ::init_int ( & mut self . context , value . raw_ptr ( ) , i ) ) ? ;
2024-04-09 13:29:14 +02:00
value
} ;
Ok ( v )
}
2024-07-19 18:29:56 +02:00
/// Create a new thunk that will evaluate to the result of the given function.
/// The function will be called with the current EvalState.
/// The function must not return a thunk.
///
/// The name is shown in stack traces.
pub fn new_value_thunk (
& mut self ,
name : & str ,
f : Box < dyn Fn ( & mut EvalState ) -> Result < Value > > ,
) -> Result < Value > {
// Nix doesn't have a function for creating a thunk, so we have to
// create a function and pass it a dummy argument.
let name = CString ::new ( name ) . with_context ( | | " new_thunk: name contains null byte " ) ? ;
let primop = primop ::PrimOp ::new (
self ,
primop ::PrimOpMeta {
// name is observable in stack traces, ie if the thunk returns Err
name : name . as_c_str ( ) ,
// doc is unlikely to be observable, so we provide a constant one for simplicity.
doc : cstr ! ( " Performs an on demand computation, implemented outside the Nix language in native code. " ) ,
// like doc, unlikely to be observed
args : [ CString ::new ( " internal_unused " ) . unwrap ( ) . as_c_str ( ) ] ,
} ,
Box ::new ( move | eval_state , _dummy : & [ Value ; 1 ] | f ( eval_state ) ) ,
) ? ;
let p = self . new_value_primop ( primop ) ? ;
self . new_value_apply ( & p , & p )
}
2024-04-04 16:38:12 +02:00
/// Not exposed, because the caller must always explicitly handle the context or not accept one at all.
2024-06-15 12:40:45 +02:00
fn get_string ( & mut self , value : & Value ) -> Result < String > {
2024-05-22 14:45:12 +02:00
let mut r = result_string_init! ( ) ;
2024-04-23 13:23:42 +02:00
unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::get_string (
2024-06-15 14:51:26 +02:00
& mut self . context ,
value . raw_ptr ( ) ,
Some ( callback_get_result_string ) ,
callback_get_result_string_data ( & mut r )
2024-07-02 16:35:32 -04:00
) ) ? ;
2024-04-23 13:23:42 +02:00
} ;
2024-05-22 14:45:12 +02:00
r
2024-04-04 16:38:12 +02:00
}
/// NOTE: this will be replaced by two methods, one that also returns the context, and one that checks that the context is empty
2024-06-15 12:40:45 +02:00
pub fn require_string ( & mut self , value : & Value ) -> Result < String > {
2024-06-14 15:40:11 +02:00
let t = self . value_type ( value ) ? ;
2024-04-04 16:38:12 +02:00
if t ! = ValueType ::String {
bail! ( " expected a string, but got a {:?} " , t ) ;
}
self . get_string ( value )
}
2024-04-08 16:54:36 +02:00
pub fn realise_string (
2024-06-15 12:40:45 +02:00
& mut self ,
2024-04-08 16:54:36 +02:00
value : & Value ,
is_import_from_derivation : bool ,
) -> Result < RealisedString > {
2024-06-14 15:40:11 +02:00
let t = self . value_type ( value ) ? ;
2024-04-08 16:54:36 +02:00
if t ! = ValueType ::String {
bail! ( " expected a string, but got a {:?} " , t ) ;
}
2024-06-15 12:40:45 +02:00
let rs = unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::string_realise (
2024-06-15 14:34:30 +02:00
& mut self . context ,
self . eval_state . as_ptr ( ) ,
2024-04-08 16:54:36 +02:00
value . raw_ptr ( ) ,
2024-06-15 12:40:45 +02:00
is_import_from_derivation
2024-07-02 16:35:32 -04:00
) )
2024-06-15 12:40:45 +02:00
} ? ;
2024-04-08 16:54:36 +02:00
let s = unsafe {
let start = raw ::realised_string_get_buffer_start ( rs ) as * const u8 ;
let size = raw ::realised_string_get_buffer_size ( rs ) ;
let slice = std ::slice ::from_raw_parts ( start , size ) ;
String ::from_utf8 ( slice . to_vec ( ) )
. map_err ( | e | anyhow ::format_err! ( " Nix string is not valid UTF-8: {} " , e ) ) ?
} ;
let paths = unsafe {
let n = raw ::realised_string_get_store_path_count ( rs ) ;
let mut paths = Vec ::with_capacity ( n as usize ) ;
for i in 0 .. n {
let path = raw ::realised_string_get_store_path ( rs , i ) ;
2024-12-16 14:11:17 +01:00
let path = NonNull ::new ( path as * mut raw ::StorePath ) . ok_or_else ( | | {
anyhow ::format_err! (
" nix_realised_string_get_store_path returned a null pointer "
)
} ) ? ;
2024-04-08 16:54:36 +02:00
paths . push ( StorePath ::new_raw_clone ( path ) ) ;
}
paths
} ;
2024-05-22 13:38:55 +02:00
// We've converted the nix_realised_string to a native struct containing copies, so we can free it now.
unsafe {
raw ::realised_string_free ( rs ) ;
}
2024-04-08 16:54:36 +02:00
Ok ( RealisedString { s , paths } )
}
2024-03-19 14:43:01 +01:00
2024-04-09 13:14:53 +02:00
/// Eagerly apply a function to an argument.
2024-05-24 09:34:20 +02:00
///
/// For a lazy version, see [`new_value_apply`][`EvalState::new_value_apply`].
2024-06-15 12:40:45 +02:00
pub fn call ( & mut self , f : Value , a : Value ) -> Result < Value > {
let value = self . new_value_uninitialized ( ) ? ;
2024-06-14 17:28:50 +02:00
unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::value_call (
2024-06-15 14:34:30 +02:00
& mut self . context ,
self . eval_state . as_ptr ( ) ,
2024-06-15 12:40:45 +02:00
f . raw_ptr ( ) ,
a . raw_ptr ( ) ,
value . raw_ptr ( )
2024-07-02 16:35:32 -04:00
) )
2024-06-15 12:40:45 +02:00
} ? ;
Ok ( value )
2024-04-09 13:14:53 +02:00
}
2024-12-01 18:56:41 +01:00
/// Eagerly apply a function with multiple curried arguments.
#[ doc(alias = " nix_value_call_multi " ) ]
pub fn call_multi ( & mut self , f : & Value , args : & [ Value ] ) -> Result < Value > {
let value = self . new_value_uninitialized ( ) ? ;
unsafe {
2024-12-17 10:40:03 +01:00
let mut args_ptrs = args . iter ( ) . map ( | a | a . raw_ptr ( ) ) . collect ::< Vec < _ > > ( ) ;
2024-12-01 18:56:41 +01:00
check_call! ( raw ::value_call_multi (
& mut self . context ,
self . eval_state . as_ptr ( ) ,
f . raw_ptr ( ) ,
args_ptrs . len ( ) ,
args_ptrs . as_mut_ptr ( ) ,
value . raw_ptr ( )
) )
} ? ;
Ok ( value )
}
2024-05-24 09:34:20 +02:00
/// Apply a function to an argument, but don't evaluate the result just yet.
///
/// For an eager version, see [`call`][`EvalState::call`].
2024-06-15 12:40:45 +02:00
pub fn new_value_apply ( & mut self , f : & Value , a : & Value ) -> Result < Value > {
2024-06-14 17:28:50 +02:00
let value = self . new_value_uninitialized ( ) ? ;
2024-06-15 12:40:45 +02:00
unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::init_apply (
2024-06-15 14:34:30 +02:00
& mut self . context ,
2024-06-15 12:40:45 +02:00
value . raw_ptr ( ) ,
f . raw_ptr ( ) ,
a . raw_ptr ( )
2024-07-02 16:35:32 -04:00
) )
2024-06-15 12:40:45 +02:00
} ? ;
Ok ( value )
2024-05-24 09:34:20 +02:00
}
2024-06-15 12:40:45 +02:00
fn new_value_uninitialized ( & mut self ) -> Result < Value > {
2024-12-17 10:40:03 +01:00
unsafe {
let value = check_call! ( raw ::alloc_value (
2024-07-02 16:35:32 -04:00
& mut self . context ,
self . eval_state . as_ptr ( )
2024-12-17 10:40:03 +01:00
) ) ? ;
Ok ( Value ::new ( value ) )
}
2024-03-19 14:43:01 +01:00
}
2024-06-27 18:46:54 +02:00
2024-07-19 17:54:31 +02:00
/// Create a new Nix function that is implemented by a Rust function.
2024-06-27 18:46:54 +02:00
/// This is also known as a "primop" in Nix, short for primitive operation.
2024-07-19 17:54:31 +02:00
/// Most of the `builtins.*` values are examples of primops, but
/// `new_value_primop` does not affect `builtins`.
pub fn new_value_primop ( & mut self , primop : primop ::PrimOp ) -> Result < Value > {
let value = self . new_value_uninitialized ( ) ? ;
unsafe {
check_call! ( raw ::init_primop (
& mut self . context ,
value . raw_ptr ( ) ,
primop . ptr
) ) ? ;
} ;
Ok ( value )
}
2024-12-01 21:50:07 +01:00
pub fn new_value_attrs < I > ( & mut self , attrs : I ) -> Result < Value >
where
I : IntoIterator < Item = ( String , Value ) > ,
I ::IntoIter : ExactSizeIterator ,
{
let iter = attrs . into_iter ( ) ;
let size = iter . len ( ) ;
let bindings_builder = BindingsBuilder ::new ( self , size ) ? ;
for ( name , value ) in iter {
let name =
CString ::new ( name ) . with_context ( | | " new_value_attrs: name contains null byte " ) ? ;
unsafe {
check_call! ( raw ::bindings_builder_insert (
& mut self . context ,
bindings_builder . ptr ,
name . as_ptr ( ) ,
value . raw_ptr ( )
) ) ? ;
}
}
let value = self . new_value_uninitialized ( ) ? ;
unsafe {
check_call! ( raw ::make_attrs (
& mut self . context ,
value . raw_ptr ( ) ,
bindings_builder . ptr
) ) ? ;
}
Ok ( value )
}
}
struct BindingsBuilder {
ptr : * mut raw ::BindingsBuilder ,
}
impl Drop for BindingsBuilder {
fn drop ( & mut self ) {
unsafe {
raw ::bindings_builder_free ( self . ptr ) ;
}
}
}
impl BindingsBuilder {
fn new ( eval_state : & mut EvalState , capacity : usize ) -> Result < Self > {
let ptr = unsafe {
check_call! ( raw ::make_bindings_builder (
& mut eval_state . context ,
eval_state . eval_state . as_ptr ( ) ,
capacity
) )
} ? ;
Ok ( BindingsBuilder { ptr } )
}
2024-03-19 14:43:01 +01:00
}
pub fn gc_now ( ) {
unsafe {
2024-05-09 12:29:32 -04:00
raw ::gc_now ( ) ;
2024-03-19 14:43:01 +01:00
}
}
2024-10-09 00:53:11 +02:00
pub struct ThreadRegistrationGuard {
must_unregister : bool ,
}
impl Drop for ThreadRegistrationGuard {
fn drop ( & mut self ) {
if self . must_unregister {
unsafe {
raw ::GC_unregister_my_thread ( ) ;
}
}
}
}
fn gc_register_my_thread_do_it ( ) -> Result < ( ) > {
2024-03-19 14:43:01 +01:00
unsafe {
let mut sb : raw ::GC_stack_base = raw ::GC_stack_base {
2024-05-09 12:41:54 -04:00
mem_base : null_mut ( ) ,
2024-03-19 14:43:01 +01:00
} ;
let r = raw ::GC_get_stack_base ( & mut sb ) ;
if r as u32 ! = raw ::GC_SUCCESS {
Err ( anyhow ::format_err! ( " GC_get_stack_base failed: {} " , r ) ) ? ;
}
raw ::GC_register_my_thread ( & sb ) ;
Ok ( ( ) )
}
}
2024-10-09 00:53:11 +02:00
pub fn gc_register_my_thread ( ) -> Result < ThreadRegistrationGuard > {
init ( ) ? ;
unsafe {
let already_done = raw ::GC_thread_is_registered ( ) ;
if already_done ! = 0 {
return Ok ( ThreadRegistrationGuard {
must_unregister : false ,
} ) ;
}
gc_register_my_thread_do_it ( ) ? ;
Ok ( ThreadRegistrationGuard {
must_unregister : true ,
} )
}
}
2024-06-26 21:00:54 +02:00
impl Clone for EvalState {
fn clone ( & self ) -> Self {
EvalState {
eval_state : self . eval_state . clone ( ) ,
store : self . store . clone ( ) ,
context : Context ::new ( ) ,
2024-03-19 14:43:01 +01:00
}
}
}
2024-04-10 14:34:51 +02:00
/// Initialize the Nix library for testing. This includes some modifications to the Nix settings, that must not be used in production.
/// Use at your own peril, in rust test suites.
pub fn test_init ( ) {
init ( ) . unwrap ( ) ;
// During development, we encountered a problem where the build hook
// would cause the test suite to reinvokes itself, causing an infinite loop.
// While _NIX_TEST_NO_SANDBOX=1 should prevent this, we may also set the
// build hook to "" to prevent this.
2024-11-26 14:52:25 +01:00
nix_util ::settings ::set ( " build-hook " , " " ) . unwrap ( ) ;
2024-04-10 14:34:51 +02:00
// When testing in the sandbox, the default build dir would be a parent of the storeDir,
// which causes an error. So we set a custom build dir here.
2025-01-02 07:53:55 +01:00
// Only available on linux
if cfg! ( target_os = " linux " ) {
nix_util ::settings ::set ( " sandbox-build-dir " , " /custom-build-dir-for-test " ) . unwrap ( ) ;
}
2024-04-10 14:34:51 +02:00
std ::env ::set_var ( " _NIX_TEST_NO_SANDBOX " , " 1 " ) ;
2024-11-26 14:52:49 +01:00
// The tests run offline
nix_util ::settings ::set ( " substituters " , " " ) . unwrap ( ) ;
2024-04-10 14:34:51 +02:00
}
2024-03-19 14:43:01 +01:00
#[ cfg(test) ]
mod tests {
2024-05-17 01:56:16 +02:00
use super ::* ;
2024-07-19 17:54:31 +02:00
use cstr ::cstr ;
2024-03-19 14:43:01 +01:00
use ctor ::ctor ;
2024-05-17 01:56:16 +02:00
use std ::collections ::HashMap ;
use std ::fs ::read_dir ;
2024-05-13 19:29:48 +02:00
use std ::io ::Write as _ ;
2024-06-27 18:46:54 +02:00
use std ::sync ::{ Arc , Mutex } ;
2024-03-19 14:43:01 +01:00
#[ ctor ]
fn setup ( ) {
2024-04-10 14:34:51 +02:00
test_init ( ) ;
2024-03-19 14:43:01 +01:00
}
2024-10-16 12:14:37 +02:00
/// Run a function while making sure that the current thread is registered with the GC.
pub fn gc_registering_current_thread < F , R > ( f : F ) -> Result < R >
where
F : FnOnce ( ) -> R ,
{
let guard = gc_register_my_thread ( ) ? ;
let r = f ( ) ;
drop ( guard ) ;
Ok ( r )
}
2024-03-19 14:43:01 +01:00
#[ test ]
fn eval_state_new_and_drop ( ) {
gc_registering_current_thread ( | | {
// very basic test: make sure initialization doesn't crash
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-05-13 19:26:58 +02:00
let _e = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
} )
. unwrap ( ) ;
}
2024-06-27 16:01:40 +02:00
#[ test ]
fn weak_ref ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-27 16:01:40 +02:00
let es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let weak = es . weak_ref ( ) ;
let _es = weak . upgrade ( ) . unwrap ( ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn weak_ref_gone ( ) {
gc_registering_current_thread ( | | {
let weak = {
2024-11-27 14:13:54 +01:00
// Use a slightly different URL which is unique in the test suite, to bypass the global store cache
2024-12-13 00:54:35 +01:00
let store = Store ::open ( Some ( " auto?foo=bar " ) , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-27 16:01:40 +02:00
let es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
es . weak_ref ( )
} ;
assert! ( weak . upgrade ( ) . is_none ( ) ) ;
assert! ( weak . store . upgrade ( ) . is_none ( ) ) ;
assert! ( weak . inner . upgrade ( ) . is_none ( ) ) ;
} )
. unwrap ( ) ;
}
2024-05-13 19:29:48 +02:00
#[ test ]
fn eval_state_lookup_path ( ) {
let import_expression = " import <test_file0> + import <test_file1> " ;
let integer0 = 83 ;
let integer1 = 103 ;
let mut test_file0 = tempfile ::NamedTempFile ::new ( ) . unwrap ( ) ;
let mut test_file1 = tempfile ::NamedTempFile ::new ( ) . unwrap ( ) ;
writeln! ( test_file0 , " {integer0} " ) . unwrap ( ) ;
writeln! ( test_file1 , " {integer1} " ) . unwrap ( ) ;
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let mut es = EvalState ::new ( Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) , [ ] ) . unwrap ( ) ;
2024-05-13 19:29:48 +02:00
assert! ( es . eval_from_string ( import_expression , " <test> " ) . is_err ( ) ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new (
2024-12-13 00:54:35 +01:00
Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ,
2024-05-13 19:29:48 +02:00
[
format! ( " test_file0= {} " , test_file0 . path ( ) . to_str ( ) . unwrap ( ) ) . as_str ( ) ,
format! ( " test_file1= {} " , test_file1 . path ( ) . to_str ( ) . unwrap ( ) ) . as_str ( ) ,
] ,
)
. unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let ie = & es . eval_from_string ( import_expression , " <test> " ) . unwrap ( ) ;
let v = es . require_int ( ie ) . unwrap ( ) ;
2024-05-13 19:29:48 +02:00
assert_eq! ( v , integer0 + integer1 ) ;
} )
. unwrap ( ) ;
test_file0 . close ( ) . unwrap ( ) ;
test_file1 . close ( ) . unwrap ( ) ;
}
2024-03-19 14:43:01 +01:00
#[ test ]
fn eval_state_eval_from_string ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " 1 " , " <test> " ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
let v2 = v . clone ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::Int ) ) ;
2024-06-03 04:20:17 +02:00
let t2 = es . value_type_unforced ( & v2 ) ;
2024-06-14 18:42:23 +02:00
assert! ( t2 = = Some ( ValueType ::Int ) ) ;
2024-03-19 14:43:01 +01:00
gc_now ( ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_value_bool ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::Bool ) ) ;
2024-03-19 14:43:01 +01:00
} )
. unwrap ( ) ;
}
2024-04-09 13:13:52 +02:00
#[ test ]
fn eval_state_value_int ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:13:52 +02:00
let v = es . eval_from_string ( " 1 " , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
let t = es . value_type ( & v ) . unwrap ( ) ;
assert! ( t = = ValueType ::Int ) ;
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 1 ) ;
} )
. unwrap ( ) ;
}
2024-06-14 19:17:35 +02:00
#[ test ]
fn eval_state_require_int_forces_thunk ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-06-14 19:17:35 +02:00
let f = es . eval_from_string ( " x: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " 2 " , " <test> " ) . unwrap ( ) ;
let v = es . new_value_apply ( & f , & a ) . unwrap ( ) ;
let t = es . value_type_unforced ( & v ) ;
assert! ( t = = None ) ;
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 3 ) ;
} )
. unwrap ( ) ;
}
2024-06-14 19:29:33 +02:00
/// A helper that turns an expression into a thunk.
2024-06-15 12:40:45 +02:00
fn make_thunk ( es : & mut EvalState , expr : & str ) -> Value {
2024-06-14 19:29:33 +02:00
// This would be silly in real code, but it works for the current Nix implementation.
// A Nix implementation that applies the identity function eagerly would be a valid
// Nix implementation, but annoying because we'll have to change this helper to do
// something more complicated that isn't optimized away.
let f = es . eval_from_string ( " x: x " , " <test> " ) . unwrap ( ) ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
es . new_value_apply ( & f , & v ) . unwrap ( )
}
#[ test ]
fn make_thunk_helper_works ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = make_thunk ( & mut es , " 1 " ) ;
2024-06-14 19:29:33 +02:00
let t = es . value_type_unforced ( & v ) ;
assert! ( t = = None ) ;
} )
. unwrap ( ) ;
}
2024-04-09 16:20:18 +02:00
#[ test ]
fn eval_state_value_attrs_names_empty ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 16:20:18 +02:00
let v = es . eval_from_string ( " { } " , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::AttrSet ) ) ;
2024-12-02 13:52:17 +01:00
let attrs = es . require_attrs_names_unsorted ( & v ) . unwrap ( ) ;
2024-04-09 16:20:18 +02:00
assert_eq! ( attrs . len ( ) , 0 ) ;
} )
. unwrap ( )
}
2024-06-14 19:29:33 +02:00
#[ test ]
2024-12-02 13:52:17 +01:00
fn eval_state_require_attrs_names_unsorted_forces_thunk ( ) {
2024-06-14 19:29:33 +02:00
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = make_thunk ( & mut es , " { a = 1; b = 2; } " ) ;
2024-06-14 19:29:33 +02:00
let t = es . value_type_unforced ( & v ) ;
assert! ( t = = None ) ;
2024-12-02 13:52:17 +01:00
let attrs = es . require_attrs_names_unsorted ( & v ) . unwrap ( ) ;
2024-06-14 19:29:33 +02:00
assert_eq! ( attrs . len ( ) , 2 ) ;
} )
. unwrap ( )
}
2024-04-09 16:20:18 +02:00
#[ test ]
2024-12-02 13:52:17 +01:00
fn eval_state_require_attrs_names_unsorted_bad_type ( ) {
2024-04-09 16:20:18 +02:00
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 16:20:18 +02:00
let v = es . eval_from_string ( " 1 " , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-12-02 13:52:17 +01:00
let r = es . require_attrs_names_unsorted ( & v ) ;
2024-04-09 16:20:18 +02:00
assert! ( r . is_err ( ) ) ;
assert_eq! (
r . unwrap_err ( ) . to_string ( ) ,
" expected an attrset, but got a Int "
) ;
} )
. unwrap ( )
}
#[ test ]
fn eval_state_value_attrs_names_example ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 16:20:18 +02:00
let expr = r # "{ a = throw "nope a"; b = throw "nope b"; }"# ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
2024-12-02 14:08:03 +01:00
let attrs = es . require_attrs_names ( & v ) . unwrap ( ) ;
2024-04-09 16:20:18 +02:00
assert_eq! ( attrs . len ( ) , 2 ) ;
assert_eq! ( attrs [ 0 ] , " a " ) ;
assert_eq! ( attrs [ 1 ] , " b " ) ;
} )
. unwrap ( ) ;
}
2024-04-10 14:32:42 +02:00
#[ test ]
fn eval_state_require_attrs_select ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-10 14:32:42 +02:00
let expr = r # "{ a = "aye"; b = "bee"; }"# ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
let a = es . require_attrs_select ( & v , " a " ) . unwrap ( ) ;
let b = es . require_attrs_select ( & v , " b " ) . unwrap ( ) ;
assert_eq! ( es . require_string ( & a ) . unwrap ( ) , " aye " ) ;
assert_eq! ( es . require_string ( & b ) . unwrap ( ) , " bee " ) ;
2024-06-14 18:21:13 +02:00
let missing = es . require_attrs_select ( & v , " c " ) ;
match missing {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
let s = format! ( " {e:#} " ) ;
2025-01-31 07:38:11 +01:00
if ! s . contains ( " attribute `c` not found " ) {
2024-06-14 18:21:13 +02:00
eprintln! ( " unexpected error message: {} " , s ) ;
assert! ( false ) ;
}
}
}
2024-04-10 14:32:42 +02:00
} )
. unwrap ( )
}
2024-06-14 19:29:33 +02:00
#[ test ]
fn eval_state_require_attrs_select_forces_thunk ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-06-14 19:29:33 +02:00
let expr = r # "{ a = "aye"; b = "bee"; }"# ;
2024-06-15 12:40:45 +02:00
let v = make_thunk ( & mut es , expr ) ;
2024-06-14 19:29:33 +02:00
assert! ( es . value_type_unforced ( & v ) . is_none ( ) ) ;
let r = es . require_attrs_select ( & v , " a " ) ;
assert! ( r . is_ok ( ) ) ;
} )
. unwrap ( )
}
2024-04-10 14:32:42 +02:00
#[ test ]
fn eval_state_require_attrs_select_error ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-10 14:32:42 +02:00
let expr = r # "{ a = throw "oh no the error"; }"# ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
let r = es . require_attrs_select ( & v , " a " ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " oh no the error " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
assert! ( false ) ;
}
}
}
} )
. unwrap ( )
}
#[ test ]
fn eval_state_require_attrs_select_opt ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-10 14:32:42 +02:00
let expr = r # "{ a = "aye"; b = "bee"; }"# ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
let a = es . require_attrs_select_opt ( & v , " a " ) . unwrap ( ) . unwrap ( ) ;
let b = es . require_attrs_select_opt ( & v , " b " ) . unwrap ( ) . unwrap ( ) ;
assert_eq! ( es . require_string ( & a ) . unwrap ( ) , " aye " ) ;
assert_eq! ( es . require_string ( & b ) . unwrap ( ) , " bee " ) ;
let c = es . require_attrs_select_opt ( & v , " c " ) . unwrap ( ) ;
assert! ( c . is_none ( ) ) ;
} )
. unwrap ( )
}
2024-06-14 19:29:33 +02:00
#[ test ]
fn eval_state_require_attrs_select_opt_forces_thunk ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-06-14 19:29:33 +02:00
let expr = r # "{ a = "aye"; b = "bee"; }"# ;
2024-06-15 12:40:45 +02:00
let v = make_thunk ( & mut es , expr ) ;
2024-06-14 19:29:33 +02:00
assert! ( es . value_type_unforced ( & v ) . is_none ( ) ) ;
let r = es . require_attrs_select_opt ( & v , " a " ) ;
assert! ( r . is_ok ( ) ) ;
} )
. unwrap ( )
}
2024-04-10 14:32:42 +02:00
#[ test ]
fn eval_state_require_attrs_select_opt_error ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-10 14:32:42 +02:00
let expr = r # "{ a = throw "oh no the error"; }"# ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
let r = es . require_attrs_select_opt ( & v , " a " ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " oh no the error " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
assert! ( false ) ;
}
}
}
} )
. unwrap ( )
}
2024-03-19 14:43:01 +01:00
#[ test ]
fn eval_state_value_string ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " \" hello \" " , " <test> " ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::String ) ) ;
2024-04-04 16:38:12 +02:00
let s = es . require_string ( & v ) . unwrap ( ) ;
assert! ( s = = " hello " ) ;
} )
. unwrap ( ) ;
}
2024-06-14 19:29:33 +02:00
#[ test ]
fn eval_state_value_string_forces_thunk ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = make_thunk ( & mut es , " \" hello \" " ) ;
2024-06-14 19:29:33 +02:00
assert! ( es . value_type_unforced ( & v ) . is_none ( ) ) ;
let s = es . require_string ( & v ) . unwrap ( ) ;
assert! ( s = = " hello " ) ;
} )
. unwrap ( ) ;
}
2024-04-04 16:38:12 +02:00
#[ test ]
fn eval_state_value_string_unexpected_bool ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
2024-04-04 16:38:12 +02:00
es . force ( & v ) . unwrap ( ) ;
let r = es . require_string ( & v ) ;
assert! ( r . is_err ( ) ) ;
// TODO: safe print value (like Nix would)
assert_eq! (
r . unwrap_err ( ) . to_string ( ) ,
" expected a string, but got a Bool "
) ;
} )
. unwrap ( )
}
#[ test ]
fn eval_state_value_string_unexpected_path_value ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " /foo " , " <test> " ) . unwrap ( ) ;
2024-04-04 16:38:12 +02:00
es . force ( & v ) . unwrap ( ) ;
let r = es . require_string ( & v ) ;
assert! ( r . is_err ( ) ) ;
assert_eq! (
r . unwrap_err ( ) . to_string ( ) ,
" expected a string, but got a Path "
) ;
} )
. unwrap ( )
}
#[ test ]
fn eval_state_value_string_bad_utf ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-04 16:38:12 +02:00
let v = es
2024-04-08 16:23:59 +02:00
. eval_from_string ( " builtins.substring 0 1 \" ü \" " , " <test> " )
2024-04-04 16:38:12 +02:00
. unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::String ) ) ;
2024-04-04 16:38:12 +02:00
let r = es . require_string ( & v ) ;
assert! ( r . is_err ( ) ) ;
assert! ( r
. unwrap_err ( )
. to_string ( )
. contains ( " Nix string is not valid UTF-8 " ) ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_value_string_unexpected_context ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-04 16:38:12 +02:00
let v = es
2024-04-08 16:23:59 +02:00
. eval_from_string ( " (derivation { name = \" hello \" ; system = \" dummy \" ; builder = \" cmd.exe \" ; }).outPath " , " <test> " )
2024-04-04 16:38:12 +02:00
. unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::String ) ) ;
2024-04-04 16:38:12 +02:00
// TODO
// let r = es.require_string_without_context(&v);
// assert!(r.is_err());
// assert!(r.unwrap_err().to_string().contains("unexpected context"));
2024-03-19 14:43:01 +01:00
} )
. unwrap ( ) ;
}
2024-04-09 13:27:27 +02:00
#[ test ]
fn eval_state_new_string ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:27:27 +02:00
let v = es . new_value_str ( " hello " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::String ) ) ;
2024-04-09 13:27:27 +02:00
let s = es . require_string ( & v ) . unwrap ( ) ;
assert! ( s = = " hello " ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_new_string_empty ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:27:27 +02:00
let v = es . new_value_str ( " " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::String ) ) ;
2024-04-09 13:27:27 +02:00
let s = es . require_string ( & v ) . unwrap ( ) ;
assert! ( s = = " " ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_new_string_invalid ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:27:27 +02:00
let r = es . new_value_str ( " hell \0 no " ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " contains null byte " ) {
eprintln! ( " {} " , e ) ;
assert! ( false ) ;
}
}
}
} )
. unwrap ( ) ;
}
2024-04-09 13:29:14 +02:00
#[ test ]
fn eval_state_new_int ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:29:14 +02:00
let v = es . new_value_int ( 42 ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::Int ) ) ;
2024-04-09 13:29:14 +02:00
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 42 ) ;
} )
. unwrap ( ) ;
}
2024-03-19 14:43:01 +01:00
#[ test ]
fn eval_state_value_attrset ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " { } " , " <test> " ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::AttrSet ) ) ;
2024-03-19 14:43:01 +01:00
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_value_list ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
let v = es
2025-09-30 20:18:59 +02:00
. eval_from_string ( " [ ] " , " <test> " )
2024-03-19 14:43:01 +01:00
. unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::List ) ) ;
2024-03-19 14:43:01 +01:00
} )
. unwrap ( ) ;
}
2024-04-08 16:54:36 +02:00
#[ test ]
fn eval_state_realise_string ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:54:36 +02:00
let expr = r #"
' '
a derivation output : $ {
derivation { name = " letsbuild " ;
system = builtins . currentSystem ;
builder = " /bin/sh " ;
args = [ " -c " " echo foo > $out " ] ;
} }
a path : $ { builtins . toFile " just-a-file " " ooh file good " }
a derivation path by itself : $ {
builtins . unsafeDiscardOutputDependency
( derivation {
name = " not-actually-built-yet " ;
system = builtins . currentSystem ;
builder = " /bin/sh " ;
args = [ " -c " " echo foo > $out " ] ;
} ) . drvPath }
' '
" #;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
let rs = es . realise_string ( & v , false ) . unwrap ( ) ;
assert! ( rs . s . starts_with ( " a derivation output: " ) ) ;
assert! ( rs . s . contains ( " -letsbuild \n " ) ) ;
assert! ( ! rs . s . contains ( " -letsbuild.drv " ) ) ;
assert! ( rs . s . contains ( " a path: " ) ) ;
assert! ( rs . s . contains ( " -just-a-file " ) ) ;
assert! ( ! rs . s . contains ( " -just-a-file.drv " ) ) ;
assert! ( ! rs . s . contains ( " ooh file good " ) ) ;
assert! ( rs . s . ends_with ( " -not-actually-built-yet.drv \n " ) ) ;
assert_eq! ( rs . paths . len ( ) , 3 ) ;
let mut names : Vec < String > = rs . paths . iter ( ) . map ( | p | p . name ( ) . unwrap ( ) ) . collect ( ) ;
names . sort ( ) ;
assert_eq! ( names [ 0 ] , " just-a-file " ) ;
assert_eq! ( names [ 1 ] , " letsbuild " ) ;
assert_eq! ( names [ 2 ] , " not-actually-built-yet.drv " ) ;
} )
. unwrap ( ) ;
}
2024-04-09 13:14:53 +02:00
#[ test ]
fn eval_state_call ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:14:53 +02:00
let f = es . eval_from_string ( " x: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " 2 " , " <test> " ) . unwrap ( ) ;
let v = es . call ( f , a ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::Int ) ) ;
2024-04-09 13:14:53 +02:00
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 3 ) ;
} )
. unwrap ( ) ;
}
2024-12-01 18:56:41 +01:00
#[ test ]
fn eval_state_call_multi ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-12-01 18:56:41 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
// This is a function that takes two arguments.
let f = es . eval_from_string ( " x: y: x - y " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " 2 " , " <test> " ) . unwrap ( ) ;
let b = es . eval_from_string ( " 3 " , " <test> " ) . unwrap ( ) ;
let v = es . call_multi ( & f , & [ a , b ] ) . unwrap ( ) ;
let t = es . value_type_unforced ( & v ) ;
assert! ( t = = Some ( ValueType ::Int ) ) ;
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = - 1 ) ;
} )
. unwrap ( ) ;
}
2024-05-24 09:34:20 +02:00
#[ test ]
fn eval_state_apply ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-05-24 09:34:20 +02:00
// This is a function that takes two arguments.
let f = es . eval_from_string ( " x: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " 2 " , " <test> " ) . unwrap ( ) ;
let v = es . new_value_apply ( & f , & a ) . unwrap ( ) ;
2024-06-14 18:42:23 +02:00
assert! ( es . value_type_unforced ( & v ) = = None ) ;
2024-05-24 09:34:20 +02:00
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::Int ) ) ;
2024-05-24 09:34:20 +02:00
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 3 ) ;
} )
. unwrap ( ) ;
}
2024-04-09 13:14:53 +02:00
#[ test ]
fn eval_state_call_fail_body ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:14:53 +02:00
let f = es . eval_from_string ( " x: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
let r = es . call ( f , a ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " cannot coerce " ) {
eprintln! ( " {} " , e ) ;
assert! ( false ) ;
}
}
}
} )
. unwrap ( ) ;
}
2024-12-01 18:56:41 +01:00
#[ test ]
fn eval_state_call_multi_fail_body ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-12-01 18:56:41 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
// This is a function that takes two arguments.
let f = es . eval_from_string ( " x: y: x - y " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " 2 " , " <test> " ) . unwrap ( ) ;
let b = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
let r = es . call_multi ( & f , & [ a , b ] ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " expected an integer but found " ) {
eprintln! ( " {} " , e ) ;
assert! ( false ) ;
}
}
}
} )
. unwrap ( ) ;
}
2024-05-24 09:34:20 +02:00
#[ test ]
fn eval_state_apply_fail_body ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-05-24 09:34:20 +02:00
let f = es . eval_from_string ( " x: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
// Lazy => no error
let r = es . new_value_apply ( & f , & a ) . unwrap ( ) ;
// Force it => error
let res = es . force ( & r ) ;
match res {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " cannot coerce " ) {
eprintln! ( " {} " , e ) ;
assert! ( false ) ;
}
}
}
} )
. unwrap ( ) ;
}
2024-06-14 19:32:56 +02:00
/// This tests the behavior of `call`, which is strict, unlike `new_value_apply`.
2024-04-09 13:14:53 +02:00
#[ test ]
fn eval_state_call_fail_args ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:14:53 +02:00
let f = es . eval_from_string ( " {x}: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " {} " , " <test> " ) . unwrap ( ) ;
let r = es . call ( f , a ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " called without required argument " ) {
eprintln! ( " {} " , e ) ;
assert! ( false ) ;
}
}
2024-12-01 18:56:41 +01:00
}
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_call_multi_fail_args ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-12-01 18:56:41 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
// This is a function that takes two arguments.
let f = es . eval_from_string ( " {x}: {y}: x - y " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " {x = 2;} " , " <test> " ) . unwrap ( ) ;
let b = es . eval_from_string ( " {} " , " <test> " ) . unwrap ( ) ;
let r = es . call_multi ( & f , & [ a , b ] ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " called without required argument " ) {
eprintln! ( " {} " , e ) ;
assert! ( false ) ;
}
}
2024-04-09 13:14:53 +02:00
}
} )
. unwrap ( ) ;
}
2024-05-24 09:34:20 +02:00
2024-06-14 19:32:56 +02:00
/// This tests the behavior of `new_value_apply`, which is lazy, unlike `call`.
2024-05-24 09:34:20 +02:00
#[ test ]
2024-06-14 19:32:56 +02:00
fn eval_state_apply_fail_args_lazy ( ) {
2024-05-24 09:34:20 +02:00
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-05-24 09:34:20 +02:00
let f = es . eval_from_string ( " {x}: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " {} " , " <test> " ) . unwrap ( ) ;
// Lazy => no error
let r = es . new_value_apply ( & f , & a ) . unwrap ( ) ;
// Force it => error
let res = es . force ( & r ) ;
match res {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " called without required argument " ) {
eprintln! ( " {} " , e ) ;
assert! ( false ) ;
}
}
}
} )
. unwrap ( ) ;
}
2024-05-17 01:56:16 +02:00
#[ test ]
fn store_open_params ( ) {
gc_registering_current_thread ( | | {
let store = tempfile ::tempdir ( ) . unwrap ( ) ;
let store_path = store . path ( ) . to_str ( ) . unwrap ( ) ;
let state = tempfile ::tempdir ( ) . unwrap ( ) ;
let state_path = state . path ( ) . to_str ( ) . unwrap ( ) ;
let log = tempfile ::tempdir ( ) . unwrap ( ) ;
let log_path = log . path ( ) . to_str ( ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new (
2024-05-17 01:56:16 +02:00
Store ::open (
2024-12-13 00:54:35 +01:00
Some ( " local " ) ,
2024-05-17 01:56:16 +02:00
HashMap ::from ( [
( " store " , store_path ) ,
( " state " , state_path ) ,
( " log " , log_path ) ,
] )
. iter ( )
. map ( | ( a , b ) | ( * a , * b ) ) ,
)
. unwrap ( ) ,
[ ] ,
)
. unwrap ( ) ;
let expr = r #"
' '
a derivation output : $ {
derivation { name = " letsbuild " ;
system = builtins . currentSystem ;
builder = " /bin/sh " ;
args = [ " -c " " echo foo > $out " ] ;
} }
a path : $ { builtins . toFile " just-a-file " " ooh file good " }
a derivation path by itself : $ {
builtins . unsafeDiscardOutputDependency
( derivation {
name = " not-actually-built-yet " ;
system = builtins . currentSystem ;
builder = " /bin/sh " ;
args = [ " -c " " echo foo > $out " ] ;
} ) . drvPath }
' '
" #;
let derivations : [ & [ u8 ] ; 3 ] = [
b " letsbuild.drv " ,
b " just-a-file " ,
b " not-actually-built-yet.drv " ,
] ;
let _ = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
// assert that all three `derivations` are inside the store and the `state` directory is not empty either.
let store_contents : Vec < _ > = read_dir ( store . path ( ) )
. unwrap ( )
. map ( | dir_entry | dir_entry . unwrap ( ) . file_name ( ) )
. collect ( ) ;
for derivation in derivations {
assert! ( store_contents
. iter ( )
. find ( | f | f . as_encoded_bytes ( ) . ends_with ( derivation ) )
. is_some ( ) ) ;
}
assert! ( ! empty ( read_dir ( state . path ( ) ) . unwrap ( ) ) ) ;
store . close ( ) . unwrap ( ) ;
state . close ( ) . unwrap ( ) ;
log . close ( ) . unwrap ( ) ;
} )
. unwrap ( ) ;
}
2024-06-27 18:46:54 +02:00
2024-05-17 01:56:16 +02:00
fn empty ( foldable : impl IntoIterator ) -> bool {
foldable . into_iter ( ) . all ( | _ | false )
}
2024-06-27 18:46:54 +02:00
#[ test ]
fn eval_state_primop_anon_call ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let bias : Arc < Mutex < Int > > = Arc ::new ( Mutex ::new ( 0 ) ) ;
let bias_control = bias . clone ( ) ;
2024-07-19 18:50:10 +02:00
let primop = primop ::PrimOp ::new (
2024-07-19 18:04:34 +02:00
& mut es ,
2024-07-19 18:50:10 +02:00
primop ::PrimOpMeta {
name : cstr ! ( " testFunction " ) ,
args : [ cstr! ( " a " ) , cstr! ( " b " ) ] ,
doc : cstr ! ( " anonymous test function " ) ,
} ,
2024-07-19 18:04:34 +02:00
Box ::new ( move | es , [ a , b ] | {
let a = es . require_int ( a ) ? ;
let b = es . require_int ( b ) ? ;
let c = * bias . lock ( ) . unwrap ( ) ;
Ok ( es . new_value_int ( a + b + c ) ? )
} ) ,
)
. unwrap ( ) ;
2024-07-19 18:50:10 +02:00
let f = es . new_value_primop ( primop ) . unwrap ( ) ;
2024-06-27 18:46:54 +02:00
{
* bias_control . lock ( ) . unwrap ( ) = 10 ;
}
let a = es . new_value_int ( 2 ) . unwrap ( ) ;
let b = es . new_value_int ( 3 ) . unwrap ( ) ;
let fa = es . call ( f , a ) . unwrap ( ) ;
let v = es . call ( fa , b ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
let t = es . value_type ( & v ) . unwrap ( ) ;
assert! ( t = = ValueType ::Int ) ;
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 15 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_primop_anon_call_throw ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-07-19 18:50:10 +02:00
let f = {
let es : & mut EvalState = & mut es ;
let prim = primop ::PrimOp ::new (
es ,
primop ::PrimOpMeta {
name : cstr ! ( " throwingTestFunction " ) ,
args : [ cstr! ( " arg " ) ] ,
doc : cstr ! ( " anonymous test function " ) ,
} ,
Box ::new ( move | es , [ a ] | {
let a = es . require_int ( a ) ? ;
bail! ( " error with arg [{}] " , a ) ;
} ) ,
)
. unwrap ( ) ;
es . new_value_primop ( prim )
}
2024-07-19 18:04:34 +02:00
. unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let a = es . new_value_int ( 2 ) . unwrap ( ) ;
let r = es . call ( f , a ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " error with arg [2] " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
assert! ( false ) ;
}
}
}
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_primop_anon_call_no_args ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-07-19 18:50:10 +02:00
let v = es
. new_value_thunk (
" test_thunk " ,
Box ::new ( move | es : & mut EvalState | Ok ( es . new_value_int ( 42 ) ? ) ) ,
)
. unwrap ( ) ;
2024-06-27 18:46:54 +02:00
es . force ( & v ) . unwrap ( ) ;
let t = es . value_type ( & v ) . unwrap ( ) ;
eprintln! ( " {:?} " , t ) ;
assert! ( t = = ValueType ::Int ) ;
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 42 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_primop_anon_call_no_args_lazy ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-07-19 18:50:10 +02:00
let v = es
. new_value_thunk (
" test_thunk " ,
Box ::new ( move | _ | {
bail! ( " error message in test case eval_state_primop_anon_call_no_args_lazy " )
} ) ,
)
. unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let r = es . force ( & v ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains (
" error message in test case eval_state_primop_anon_call_no_args_lazy " ,
) {
eprintln! ( " unexpected error message: {} " , e ) ;
assert! ( false ) ;
}
2024-07-19 18:29:56 +02:00
if ! e . to_string ( ) . contains ( " test_thunk " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
assert! ( false ) ;
}
2024-06-27 18:46:54 +02:00
}
}
} )
. unwrap ( ) ;
}
2024-07-19 17:54:31 +02:00
#[ test ]
pub fn eval_state_primop_custom ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-07-19 17:54:31 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let primop = primop ::PrimOp ::new (
& mut es ,
primop ::PrimOpMeta {
name : cstr ! ( " frobnicate " ) ,
doc : cstr ! ( " Frobnicates widgets " ) ,
args : [ cstr! ( " x " ) , cstr! ( " y " ) ] ,
} ,
Box ::new ( | es , args | {
let a = es . require_int ( & args [ 0 ] ) ? ;
let b = es . require_int ( & args [ 1 ] ) ? ;
Ok ( es . new_value_int ( a + b ) ? )
} ) ,
)
. unwrap ( ) ;
let f = es . new_value_primop ( primop ) . unwrap ( ) ;
let a = es . new_value_int ( 2 ) . unwrap ( ) ;
let b = es . new_value_int ( 3 ) . unwrap ( ) ;
let fa = es . call ( f , a ) . unwrap ( ) ;
let fb = es . call ( fa , b ) . unwrap ( ) ;
es . force ( & fb ) . unwrap ( ) ;
let t = es . value_type ( & fb ) . unwrap ( ) ;
assert! ( t = = ValueType ::Int ) ;
let i = es . require_int ( & fb ) . unwrap ( ) ;
assert! ( i = = 5 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
pub fn eval_state_primop_custom_throw ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-07-19 17:54:31 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let primop = primop ::PrimOp ::new (
& mut es ,
primop ::PrimOpMeta {
name : cstr ! ( " frobnicate " ) ,
doc : cstr ! ( " Frobnicates widgets " ) ,
args : [ cstr! ( " x " ) ] ,
} ,
Box ::new ( | _es , _args | bail! ( " The frob unexpectedly fizzled " ) ) ,
)
. unwrap ( ) ;
let f = es . new_value_primop ( primop ) . unwrap ( ) ;
let a = es . new_value_int ( 0 ) . unwrap ( ) ;
match es . call ( f , a ) {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " The frob unexpectedly fizzled " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
assert! ( false ) ;
}
if ! e . to_string ( ) . contains ( " frobnicate " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
assert! ( false ) ;
}
}
}
} )
. unwrap ( ) ;
}
2024-12-01 21:50:07 +01:00
#[ test ]
pub fn eval_state_new_value_attrs_from_slice_empty ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-12-01 21:50:07 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let attrs = es . new_value_attrs ( [ ] ) . unwrap ( ) ;
let t = es . value_type ( & attrs ) . unwrap ( ) ;
assert! ( t = = ValueType ::AttrSet ) ;
let names = es . require_attrs_names ( & attrs ) . unwrap ( ) ;
assert! ( names . is_empty ( ) ) ;
} )
. unwrap ( ) ;
}
#[ test ]
pub fn eval_state_new_value_attrs_from_vec ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-12-01 21:50:07 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let attrs = {
let a = es . new_value_int ( 1 ) . unwrap ( ) ;
let b = es . new_value_int ( 2 ) . unwrap ( ) ;
es . new_value_attrs ( vec! [ ( " a " . to_string ( ) , a ) , ( " b " . to_string ( ) , b ) ] )
. unwrap ( )
} ;
let t = es . value_type ( & attrs ) . unwrap ( ) ;
assert! ( t = = ValueType ::AttrSet ) ;
let names = es . require_attrs_names ( & attrs ) . unwrap ( ) ;
assert_eq! ( names . len ( ) , 2 ) ;
assert_eq! ( names [ 0 ] , " a " ) ;
assert_eq! ( names [ 1 ] , " b " ) ;
let a = es . require_attrs_select ( & attrs , " a " ) . unwrap ( ) ;
let b = es . require_attrs_select ( & attrs , " b " ) . unwrap ( ) ;
let i = es . require_int ( & a ) . unwrap ( ) ;
assert_eq! ( i , 1 ) ;
let i = es . require_int ( & b ) . unwrap ( ) ;
assert_eq! ( i , 2 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
pub fn eval_state_new_value_attrs_from_hashmap ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-12-01 21:50:07 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let attrs = {
let a = es . new_value_int ( 1 ) . unwrap ( ) ;
let b = es . new_value_int ( 2 ) . unwrap ( ) ;
es . new_value_attrs ( HashMap ::from ( [ ( " a " . to_string ( ) , a ) , ( " b " . to_string ( ) , b ) ] ) )
. unwrap ( )
} ;
let t = es . value_type ( & attrs ) . unwrap ( ) ;
assert! ( t = = ValueType ::AttrSet ) ;
let names = es . require_attrs_names ( & attrs ) . unwrap ( ) ;
assert_eq! ( names . len ( ) , 2 ) ;
assert_eq! ( names [ 0 ] , " a " ) ;
assert_eq! ( names [ 1 ] , " b " ) ;
let a = es . require_attrs_select ( & attrs , " a " ) . unwrap ( ) ;
let b = es . require_attrs_select ( & attrs , " b " ) . unwrap ( ) ;
let i = es . require_int ( & a ) . unwrap ( ) ;
assert_eq! ( i , 1 ) ;
let i = es . require_int ( & b ) . unwrap ( ) ;
assert_eq! ( i , 2 ) ;
} )
. unwrap ( ) ;
}
2024-03-19 14:43:01 +01:00
}