Skip to content

Commit aace1fb

Browse files
committed
C API: test nix_store_get_fs_closure
1 parent 9abcc68 commit aace1fb

File tree

1 file changed

+240
-0
lines changed

1 file changed

+240
-0
lines changed

src/libstore-tests/nix_api_store.cc

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
221281
TEST_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

Comments
 (0)