Skip to content

Commit 6036aaf

Browse files
committed
C API: Check output callback order
1 parent a9d9b50 commit 6036aaf

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed

src/libstore-tests/nix_api_store.cc

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,155 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_store_get_fs_closure_include_deriver
613613
ASSERT_EQ(closure_paths.count(drvPathName), 1) << "Derivation should be in closure when include_derivers=true";
614614
}
615615

616+
TEST_F(NixApiStoreTestWithRealisedPath, nix_store_realise_output_ordering)
617+
{
618+
// Test that nix_store_realise returns outputs in alphabetical order by output name.
619+
// This test uses a CA derivation with 10 outputs in randomized input order
620+
// to verify that the callback order is deterministic and alphabetical.
621+
nix::experimentalFeatureSettings.set("extra-experimental-features", "ca-derivations");
622+
nix::settings.substituters = {};
623+
624+
auto * store = open_local_store();
625+
626+
// Create a CA derivation with 10 outputs using proper placeholders
627+
auto outa_ph = nix::hashPlaceholder("outa");
628+
auto outb_ph = nix::hashPlaceholder("outb");
629+
auto outc_ph = nix::hashPlaceholder("outc");
630+
auto outd_ph = nix::hashPlaceholder("outd");
631+
auto oute_ph = nix::hashPlaceholder("oute");
632+
auto outf_ph = nix::hashPlaceholder("outf");
633+
auto outg_ph = nix::hashPlaceholder("outg");
634+
auto outh_ph = nix::hashPlaceholder("outh");
635+
auto outi_ph = nix::hashPlaceholder("outi");
636+
auto outj_ph = nix::hashPlaceholder("outj");
637+
638+
std::string drvJson = R"({
639+
"version": 3,
640+
"name": "multi-output-test",
641+
"system": ")" + nix::settings.thisSystem.get()
642+
+ R"(",
643+
"builder": "/bin/sh",
644+
"args": ["-c", "echo a > $outa; echo b > $outb; echo c > $outc; echo d > $outd; echo e > $oute; echo f > $outf; echo g > $outg; echo h > $outh; echo i > $outi; echo j > $outj"],
645+
"env": {
646+
"builder": "/bin/sh",
647+
"name": "multi-output-test",
648+
"system": ")" + nix::settings.thisSystem.get()
649+
+ R"(",
650+
"outf": ")" + outf_ph
651+
+ R"(",
652+
"outd": ")" + outd_ph
653+
+ R"(",
654+
"outi": ")" + outi_ph
655+
+ R"(",
656+
"oute": ")" + oute_ph
657+
+ R"(",
658+
"outh": ")" + outh_ph
659+
+ R"(",
660+
"outc": ")" + outc_ph
661+
+ R"(",
662+
"outb": ")" + outb_ph
663+
+ R"(",
664+
"outg": ")" + outg_ph
665+
+ R"(",
666+
"outj": ")" + outj_ph
667+
+ R"(",
668+
"outa": ")" + outa_ph
669+
+ R"("
670+
},
671+
"inputDrvs": {},
672+
"inputSrcs": [],
673+
"outputs": {
674+
"outd": { "hashAlgo": "sha256", "method": "nar" },
675+
"outf": { "hashAlgo": "sha256", "method": "nar" },
676+
"outg": { "hashAlgo": "sha256", "method": "nar" },
677+
"outb": { "hashAlgo": "sha256", "method": "nar" },
678+
"outc": { "hashAlgo": "sha256", "method": "nar" },
679+
"outi": { "hashAlgo": "sha256", "method": "nar" },
680+
"outj": { "hashAlgo": "sha256", "method": "nar" },
681+
"outh": { "hashAlgo": "sha256", "method": "nar" },
682+
"outa": { "hashAlgo": "sha256", "method": "nar" },
683+
"oute": { "hashAlgo": "sha256", "method": "nar" }
684+
}
685+
})";
686+
687+
auto * drv = nix_derivation_from_json(ctx, store, drvJson.c_str());
688+
assert_ctx_ok();
689+
ASSERT_NE(drv, nullptr);
690+
691+
auto * drvPath = nix_add_derivation(ctx, store, drv);
692+
assert_ctx_ok();
693+
ASSERT_NE(drvPath, nullptr);
694+
695+
// Realise the derivation - capture the order outputs are returned
696+
std::map<std::string, nix::StorePath> outputs;
697+
std::vector<std::string> output_order;
698+
auto cb = LambdaAdapter{.fun = [&](const char * outname, const StorePath * outPath) {
699+
ASSERT_NE(outname, nullptr);
700+
ASSERT_NE(outPath, nullptr);
701+
output_order.push_back(outname);
702+
outputs.emplace(outname, outPath->path);
703+
}};
704+
705+
auto ret = nix_store_realise(
706+
ctx, store, drvPath, static_cast<void *>(&cb), decltype(cb)::call_void<const char *, const StorePath *>);
707+
assert_ctx_ok();
708+
ASSERT_EQ(ret, NIX_OK);
709+
ASSERT_EQ(outputs.size(), 10);
710+
711+
// Verify outputs are returned in alphabetical order by output name
712+
std::vector<std::string> expected_order = {
713+
"outa", "outb", "outc", "outd", "oute", "outf", "outg", "outh", "outi", "outj"};
714+
ASSERT_EQ(output_order, expected_order) << "Outputs should be returned in alphabetical order by output name";
715+
716+
// Now compute closure with include_outputs and collect paths in order
717+
struct CallbackData
718+
{
719+
std::vector<std::string> * paths;
720+
};
721+
722+
std::vector<std::string> closure_paths;
723+
CallbackData data{&closure_paths};
724+
725+
ret = nix_store_get_fs_closure(
726+
ctx,
727+
store,
728+
drvPath,
729+
false, // flip_direction
730+
true, // include_outputs - include the outputs in the closure
731+
false, // include_derivers
732+
&data,
733+
[](nix_c_context * context, void * userdata, const StorePath * path) {
734+
auto * data = static_cast<CallbackData *>(userdata);
735+
std::string path_str;
736+
nix_store_path_name(path, OBSERVE_STRING(path_str));
737+
data->paths->push_back(path_str);
738+
});
739+
assert_ctx_ok();
740+
ASSERT_EQ(ret, NIX_OK);
741+
742+
// Should contain at least the derivation and 10 outputs
743+
ASSERT_GE(closure_paths.size(), 11);
744+
745+
// Verify all outputs are present in the closure
746+
for (const auto & [outname, outPath] : outputs) {
747+
std::string outPathName = store->ptr->printStorePath(outPath);
748+
749+
bool found = false;
750+
for (const auto & p : closure_paths) {
751+
// nix_store_path_name returns just the name part, so match against full path name
752+
if (outPathName.find(p) != std::string::npos) {
753+
found = true;
754+
break;
755+
}
756+
}
757+
ASSERT_TRUE(found) << "Output " << outname << " (" << outPathName << ") not found in closure";
758+
}
759+
760+
nix_store_path_free(drvPath);
761+
nix_derivation_free(drv);
762+
nix_store_free(store);
763+
}
764+
616765
TEST_F(NixApiStoreTestWithRealisedPath, nix_store_get_fs_closure_error_propagation)
617766
{
618767
// Test that errors in the callback abort the closure computation

0 commit comments

Comments
 (0)