@@ -437,4 +437,105 @@ TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
437
437
assert_ctx_ok ();
438
438
ASSERT_EQ (3 , rInt);
439
439
}
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
+
440
541
} // namespace nixC
0 commit comments