@@ -437,4 +437,105 @@ TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
437437 assert_ctx_ok ();
438438 ASSERT_EQ (3 , rInt);
439439}
440+
441+ // The following is a test case for retryable thunks.
442+ // This is a requirement for the current way in which NixOps4 evaluates its deployment expressions.
443+ // An alternative strategy could be implemented, but unwinding the stack may be a more efficient way to deal with many
444+ // suspensions/resumptions, compared to e.g. using a thread or coroutine stack for each suspended dependency. This test
445+ // models the essential bits of a deployment tool that uses such a strategy.
446+
447+ // State for the retryable primop - simulates deployment resource availability
448+ struct DeploymentResourceState
449+ {
450+ bool vm_created = false ;
451+ };
452+
453+ static void primop_load_resource_input (
454+ void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
455+ {
456+ assert (context);
457+ assert (state);
458+ auto * resource_state = static_cast <DeploymentResourceState *>(user_data);
459+
460+ // Get the resource input name argument
461+ std::string input_name;
462+ if (nix_get_string (context, args[0 ], OBSERVE_STRING (input_name)) != NIX_OK)
463+ return ;
464+
465+ // Only handle "vm_id" input - throw for anything else
466+ if (input_name != " vm_id" ) {
467+ std::string error_msg = " unknown resource input: " + input_name;
468+ nix_set_err_msg (context, NIX_ERR_NIX_ERROR, error_msg.c_str ());
469+ return ;
470+ }
471+
472+ if (resource_state->vm_created ) {
473+ // VM has been created, return the ID
474+ nix_init_string (context, ret, " vm-12345" );
475+ } else {
476+ // VM not created yet, fail with dependency error
477+ nix_set_err_msg (context, NIX_ERR_RECOVERABLE, " VM not yet created" );
478+ }
479+ }
480+
481+ TEST_F (nix_api_expr_test, nix_expr_thunk_re_evaluation_after_deployment)
482+ {
483+ // This test demonstrates NixOps4's requirement: a thunk calling a primop should be
484+ // re-evaluable when deployment resources become available that were not available initially.
485+
486+ DeploymentResourceState resource_state;
487+
488+ PrimOp * primop = nix_alloc_primop (
489+ ctx,
490+ primop_load_resource_input,
491+ 1 ,
492+ " loadResourceInput" ,
493+ nullptr ,
494+ " load a deployment resource input" ,
495+ &resource_state);
496+ assert_ctx_ok ();
497+
498+ nix_value * primopValue = nix_alloc_value (ctx, state);
499+ assert_ctx_ok ();
500+ nix_init_primop (ctx, primopValue, primop);
501+ assert_ctx_ok ();
502+
503+ nix_value * inputName = nix_alloc_value (ctx, state);
504+ assert_ctx_ok ();
505+ nix_init_string (ctx, inputName, " vm_id" );
506+ assert_ctx_ok ();
507+
508+ // Create a single thunk by using nix_init_apply instead of nix_value_call
509+ // This creates a lazy application that can be forced multiple times
510+ nix_value * thunk = nix_alloc_value (ctx, state);
511+ assert_ctx_ok ();
512+ nix_init_apply (ctx, thunk, primopValue, inputName);
513+ assert_ctx_ok ();
514+
515+ // First force: VM not created yet, should fail
516+ nix_value_force (ctx, state, thunk);
517+ ASSERT_EQ (NIX_ERR_NIX_ERROR, nix_err_code (ctx));
518+ ASSERT_THAT (nix_err_msg (nullptr , ctx, nullptr ), testing::HasSubstr (" VM not yet created" ));
519+
520+ // Clear the error context for the next attempt
521+ nix_c_context_free (ctx);
522+ ctx = nix_c_context_create ();
523+
524+ // Simulate deployment process: VM gets created
525+ resource_state.vm_created = true ;
526+
527+ // Second force of the SAME thunk: this is where the "failed" value issue appears
528+ // With failed value caching, this should fail because the thunk is marked as permanently failed
529+ // Without failed value caching (or with retryable failures), this should succeed
530+ nix_value_force (ctx, state, thunk);
531+
532+ // If we get here without error, the thunk was successfully re-evaluated
533+ assert_ctx_ok ();
534+
535+ std::string result;
536+ nix_get_string (ctx, thunk, OBSERVE_STRING (result));
537+ assert_ctx_ok ();
538+ ASSERT_STREQ (" vm-12345" , result.c_str ());
539+ }
540+
440541} // namespace nixC
0 commit comments