@@ -218,6 +218,66 @@ struct LambdaAdapter
218218 }
219219};
220220
221+ class NixApiStoreTestWithRealisedPath : public nix_api_store_test_base
222+ {
223+ public:
224+ StorePath * drvPath = nullptr ;
225+ nix_derivation * drv = nullptr ;
226+ Store * store = nullptr ;
227+ StorePath * outPath = nullptr ;
228+
229+ void SetUp () override
230+ {
231+ nix_api_store_test_base::SetUp ();
232+
233+ nix::experimentalFeatureSettings.set (" extra-experimental-features" , " ca-derivations" );
234+ nix::settings.substituters = {};
235+
236+ store = open_local_store ();
237+
238+ std::filesystem::path unitTestData{getenv (" _NIX_TEST_UNIT_DATA" )};
239+ std::ifstream t{unitTestData / " derivation/ca/self-contained.json" };
240+ std::stringstream buffer;
241+ buffer << t.rdbuf ();
242+
243+ drv = nix_derivation_from_json (ctx, store, buffer.str ().c_str ());
244+ assert_ctx_ok ();
245+ ASSERT_NE (drv, nullptr );
246+
247+ drvPath = nix_add_derivation (ctx, store, drv);
248+ assert_ctx_ok ();
249+ ASSERT_NE (drvPath, nullptr );
250+
251+ auto cb = LambdaAdapter{.fun = [&](const char * outname, const StorePath * outPath_) {
252+ auto is_valid_path = nix_store_is_valid_path (ctx, store, outPath_);
253+ ASSERT_EQ (is_valid_path, true );
254+ ASSERT_STREQ (outname, " out" ) << " Expected single 'out' output" ;
255+ ASSERT_EQ (outPath, nullptr ) << " Output path callback should only be called once" ;
256+ outPath = nix_store_path_clone (outPath_);
257+ }};
258+
259+ auto ret = nix_store_realise (
260+ ctx, store, drvPath, static_cast <void *>(&cb), decltype (cb)::call_void<const char *, const StorePath *>);
261+ assert_ctx_ok ();
262+ ASSERT_EQ (ret, NIX_OK);
263+ ASSERT_NE (outPath, nullptr ) << " Derivation should have produced an output" ;
264+ }
265+
266+ void TearDown () override
267+ {
268+ if (drvPath)
269+ nix_store_path_free (drvPath);
270+ if (outPath)
271+ nix_store_path_free (outPath);
272+ if (drv)
273+ nix_derivation_free (drv);
274+ if (store)
275+ nix_store_free (store);
276+
277+ nix_api_store_test_base::TearDown ();
278+ }
279+ };
280+
221281TEST_F (nix_api_store_test_base, build_from_json)
222282{
223283 // FIXME get rid of these
@@ -256,4 +316,184 @@ TEST_F(nix_api_store_test_base, build_from_json)
256316 nix_store_free (store);
257317}
258318
319+ TEST_F (NixApiStoreTestWithRealisedPath, nix_store_get_fs_closure_with_outputs)
320+ {
321+ // Test closure computation with include_outputs on a derivation path
322+ struct CallbackData
323+ {
324+ std::set<std::string> * paths;
325+ };
326+
327+ std::set<std::string> closure_paths;
328+ CallbackData data{&closure_paths};
329+
330+ auto ret = nix_store_get_fs_closure (
331+ ctx,
332+ store,
333+ drvPath, // Use derivation path
334+ false , // flip_direction
335+ true , // include_outputs - include the outputs in the closure
336+ false , // include_derivers
337+ &data,
338+ [](nix_c_context * context, void * userdata, const StorePath * path) {
339+ auto * data = static_cast <CallbackData *>(userdata);
340+ std::string path_str;
341+ nix_store_path_name (path, OBSERVE_STRING (path_str));
342+ auto [it, inserted] = data->paths ->insert (path_str);
343+ ASSERT_TRUE (inserted) << " Duplicate path in closure: " << path_str;
344+ });
345+ assert_ctx_ok ();
346+ ASSERT_EQ (ret, NIX_OK);
347+
348+ // The closure should contain the derivation and its outputs
349+ ASSERT_GE (closure_paths.size (), 2 );
350+
351+ // Verify the output path is in the closure
352+ std::string outPathName;
353+ nix_store_path_name (outPath, OBSERVE_STRING (outPathName));
354+ ASSERT_EQ (closure_paths.count (outPathName), 1 );
355+ }
356+
357+ TEST_F (NixApiStoreTestWithRealisedPath, nix_store_get_fs_closure_without_outputs)
358+ {
359+ // Test closure computation WITHOUT include_outputs on a derivation path
360+ struct CallbackData
361+ {
362+ std::set<std::string> * paths;
363+ };
364+
365+ std::set<std::string> closure_paths;
366+ CallbackData data{&closure_paths};
367+
368+ auto ret = nix_store_get_fs_closure (
369+ ctx,
370+ store,
371+ drvPath, // Use derivation path
372+ false , // flip_direction
373+ false , // include_outputs - do NOT include the outputs
374+ false , // include_derivers
375+ &data,
376+ [](nix_c_context * context, void * userdata, const StorePath * path) {
377+ auto * data = static_cast <CallbackData *>(userdata);
378+ std::string path_str;
379+ nix_store_path_name (path, OBSERVE_STRING (path_str));
380+ auto [it, inserted] = data->paths ->insert (path_str);
381+ ASSERT_TRUE (inserted) << " Duplicate path in closure: " << path_str;
382+ });
383+ assert_ctx_ok ();
384+ ASSERT_EQ (ret, NIX_OK);
385+
386+ // Verify the output path is NOT in the closure
387+ std::string outPathName;
388+ nix_store_path_name (outPath, OBSERVE_STRING (outPathName));
389+ ASSERT_EQ (closure_paths.count (outPathName), 0 ) << " Output path should not be in closure when includeOutputs=false" ;
390+ }
391+
392+ TEST_F (NixApiStoreTestWithRealisedPath, nix_store_get_fs_closure_flip_direction)
393+ {
394+ // Test closure computation with flip_direction on a derivation path
395+ // When flip_direction=true, we get the reverse dependencies (what depends on this path)
396+ // For a derivation, this should NOT include outputs even with include_outputs=true
397+ struct CallbackData
398+ {
399+ std::set<std::string> * paths;
400+ };
401+
402+ std::set<std::string> closure_paths;
403+ CallbackData data{&closure_paths};
404+
405+ auto ret = nix_store_get_fs_closure (
406+ ctx,
407+ store,
408+ drvPath, // Use derivation path
409+ true , // flip_direction - get reverse dependencies
410+ true , // include_outputs
411+ false , // include_derivers
412+ &data,
413+ [](nix_c_context * context, void * userdata, const StorePath * path) {
414+ auto * data = static_cast <CallbackData *>(userdata);
415+ std::string path_str;
416+ nix_store_path_name (path, OBSERVE_STRING (path_str));
417+ auto [it, inserted] = data->paths ->insert (path_str);
418+ ASSERT_TRUE (inserted) << " Duplicate path in closure: " << path_str;
419+ });
420+ assert_ctx_ok ();
421+ ASSERT_EQ (ret, NIX_OK);
422+
423+ // Verify the output path is NOT in the closure when direction is flipped
424+ std::string outPathName;
425+ nix_store_path_name (outPath, OBSERVE_STRING (outPathName));
426+ ASSERT_EQ (closure_paths.count (outPathName), 0 ) << " Output path should not be in closure when flip_direction=true" ;
427+ }
428+
429+ TEST_F (NixApiStoreTestWithRealisedPath, nix_store_get_fs_closure_include_derivers)
430+ {
431+ // Test closure computation with include_derivers on an output path
432+ // This should include the derivation that produced the output
433+ struct CallbackData
434+ {
435+ std::set<std::string> * paths;
436+ };
437+
438+ std::set<std::string> closure_paths;
439+ CallbackData data{&closure_paths};
440+
441+ auto ret = nix_store_get_fs_closure (
442+ ctx,
443+ store,
444+ outPath, // Use output path (not derivation)
445+ false , // flip_direction
446+ false , // include_outputs
447+ true , // include_derivers - include the derivation
448+ &data,
449+ [](nix_c_context * context, void * userdata, const StorePath * path) {
450+ auto * data = static_cast <CallbackData *>(userdata);
451+ std::string path_str;
452+ nix_store_path_name (path, OBSERVE_STRING (path_str));
453+ auto [it, inserted] = data->paths ->insert (path_str);
454+ ASSERT_TRUE (inserted) << " Duplicate path in closure: " << path_str;
455+ });
456+ assert_ctx_ok ();
457+ ASSERT_EQ (ret, NIX_OK);
458+
459+ // Verify the derivation path is in the closure
460+ // Deriver is nasty stateful, and this assertion is only guaranteed because
461+ // we're using an empty store as our starting point. Otherwise, if the
462+ // output happens to exist, the deriver could be anything.
463+ std::string drvPathName;
464+ nix_store_path_name (drvPath, OBSERVE_STRING (drvPathName));
465+ ASSERT_EQ (closure_paths.count (drvPathName), 1 ) << " Derivation should be in closure when include_derivers=true" ;
466+ }
467+
468+ TEST_F (NixApiStoreTestWithRealisedPath, nix_store_get_fs_closure_error_propagation)
469+ {
470+ // Test that errors in the callback abort the closure computation
471+ struct CallbackData
472+ {
473+ int * count;
474+ };
475+
476+ int call_count = 0 ;
477+ CallbackData data{&call_count};
478+
479+ auto ret = nix_store_get_fs_closure (
480+ ctx,
481+ store,
482+ drvPath, // Use derivation path
483+ false , // flip_direction
484+ true , // include_outputs
485+ false , // include_derivers
486+ &data,
487+ [](nix_c_context * context, void * userdata, const StorePath * path) {
488+ auto * data = static_cast <CallbackData *>(userdata);
489+ (*data->count )++;
490+ // Set an error immediately
491+ nix_set_err_msg (context, NIX_ERR_UNKNOWN, " Test error" );
492+ });
493+
494+ // Should have aborted with error
495+ ASSERT_EQ (ret, NIX_ERR_UNKNOWN);
496+ ASSERT_EQ (call_count, 1 ); // Should have been called exactly once, then aborted
497+ }
498+
259499} // namespace nixC
0 commit comments