libutil: fix copyNAR not reading the whole nar when dropped early

if a copyNAR generator was not drained to completion it would not read
the full nar data from its source. this could happen if the copier was
passed to parseAndDump wrapped as a source because copyNAR would yield
nar metadata *before* it had read it, and GeneratorSource will drain a
generator fully *only* if the source is allowed to throw EndOfFile. in
the parseAndDump case this never happened because parseAndDump expects
to be given an unterminated stream, and thus the combination left some
nar metadata in the input Source, breaking the remote store protocols.

fixes #732

Change-Id: Ia59a53375992bfcdb7bc6b37764ca779622bc8f7
This commit is contained in:
eldritch horrors
2025-03-18 19:32:58 +00:00
parent be4ab653eb
commit 27d5209f4d
2 changed files with 74 additions and 12 deletions
+59 -4
View File
@@ -243,8 +243,7 @@ TEST_P(NarTest, parse)
}
}
TEST_P(NarTest, parseAsync)
{
namespace parseAsync {
struct File
{
bool executable;
@@ -316,10 +315,15 @@ TEST_P(NarTest, parseAsync)
parent.emplace(name, Symlink{target});
}
};
}
TEST_P(NarTest, parseAsync)
{
using namespace parseAsync;
AsyncGeneratorInputStream source(rawStream());
std::map<std::string, Entry> contents;
std::map<std::string, parseAsync::Entry> contents;
ReconstructVisitor rv{contents};
kj::EventLoop el;
@@ -327,7 +331,7 @@ TEST_P(NarTest, parseAsync)
auto entries = entriesFn()();
parseDump(rv, source).wait(ws).value();
auto parsed = Directory::toNar(contents.at(""));
auto parsed = parseAsync::Directory::toNar(contents.at(""));
while (true) {
auto e = entries.next();
auto p = parsed.next();
@@ -357,6 +361,57 @@ TEST_P(NarTest, copyAsync)
ASSERT_EQ(raw(), copied);
}
TEST_P(NarTest, parseCopied)
{
GeneratorSource input(rawStream());
GeneratorSource source(copyNAR(input));
auto entries = entriesFn()();
auto parsed = parse(source);
while (true) {
auto e = entries.next();
auto p = parsed.next();
ASSERT_EQ(e.has_value(), p.has_value());
if (!e) {
break;
}
assert_eq(*e, *p);
}
char buf;
ASSERT_THROW(input(&buf, 1), EndOfFile);
}
TEST_P(NarTest, parseCopiedAsync)
{
using namespace parseAsync;
AsyncGeneratorInputStream input(rawStream());
auto source = copyNAR(input);
std::map<std::string, parseAsync::Entry> contents;
ReconstructVisitor rv{contents};
kj::EventLoop el;
kj::WaitScope ws{el};
auto entries = entriesFn()();
parseDump(rv, *source).wait(ws).value();
auto parsed = parseAsync::Directory::toNar(contents.at(""));
while (true) {
auto e = entries.next();
auto p = parsed.next();
ASSERT_EQ(e.has_value(), p.has_value());
if (!e) {
break;
}
assert_eq(*e, *p);
}
char buf;
ASSERT_EQ(input.read(&buf, 1).wait(ws).value(), 0);
}
TEST_P(NarTest, index)
{
GeneratorSource source(rawStream());