From 0b4912a0fb1db636490914e49b0cf06496d1da69 Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Thu, 27 Feb 2025 16:08:50 +0100 Subject: [PATCH] libutil: add some c++ nar parser tests functional2 can't test the actual nar parser library :( Change-Id: I9f1d8412ba6ed912f74c9edcc2d54216fbda793e --- tests/unit/libutil/archive.cc | 260 ++++++++++++++++++++++++++++++++++ tests/unit/meson.build | 1 + 2 files changed, 261 insertions(+) create mode 100644 tests/unit/libutil/archive.cc diff --git a/tests/unit/libutil/archive.cc b/tests/unit/libutil/archive.cc new file mode 100644 index 000000000..16496b93a --- /dev/null +++ b/tests/unit/libutil/archive.cc @@ -0,0 +1,260 @@ +#include "lix/libutil/archive.hh" +#include "lix/libutil/serialise.hh" +#include +#include + +using namespace std::literals; + +namespace nix { + +using namespace nar; + +namespace { + +using Entries = Generator; +using Fragment = std::pair>; + +Fragment concat(std::vector fragments) +{ + std::string full; + for (auto & [part, _] : fragments) { + full.append(part); + } + return { + std::move(full), + [fragments] { + return [](auto fragments) -> Entries { + for (auto & [_, part] : fragments) { + co_yield part(); + } + }(fragments); + }, + }; +} + +Fragment metaString(std::string s) +{ + assert(s.size() <= 255); + return { + char(s.size()) + "\0\0\0\0\0\0\0"s + s + + "\0\0\0\0\0\0\0\0"s.substr(0, (8 - s.size() % 8) % 8), + [s] { return [](auto ms) -> Entries { co_yield ms; }(MetadataString{s}); }, + }; +} + +auto metaRaw(std::string_view s) +{ + return [s{std::string(s)}] { + return [](auto mr) -> Entries { co_yield mr; }(MetadataRaw{Bytes{s.data(), s.size()}}); + }; +} + +Fragment header = metaString("nix-archive-1"); +Fragment lparen = metaString("("); +Fragment rparen = metaString(")"); +Fragment type = metaString("type"); + +Fragment make_file(bool executable, Path path, std::string contents) +{ + assert(contents.size() <= 255); + return concat({ + lparen, + type, + metaString("regular"), + executable ? concat({metaString("executable"), metaString("")}) + : Fragment{"", [] -> Entries { co_return; }}, + metaString("contents"), + {char(contents.size()) + "\0\0\0\0\0\0\0"s + contents + + "\0\0\0\0\0\0\0\0"s.substr(0, (8 - contents.size() % 8) % 8), + [executable, path, contents] { + return [](auto executable, auto path, auto contents) -> Entries { + co_yield metaRaw(char(contents.size()) + "\0\0\0\0\0\0\0"s)(); + co_yield File{ + path, + executable, + contents.size(), + [](auto contents) -> Generator { + co_yield Bytes{contents.data(), contents.size()}; + }(contents) + }; + co_yield metaRaw("\0\0\0\0\0\0\0\0"sv.substr(0, (8 - contents.size() % 8) % 8))(); + }(executable, path, contents); + }}, + rparen, + }); +} + +Fragment make_symlink(Path path, std::string linkTarget) +{ + assert(linkTarget.size() <= 255); + return concat({ + lparen, + type, + metaString("symlink"), + metaString("target"), + metaString(linkTarget), + { + "", + [path, linkTarget] { + return [](auto link) -> Entries { co_yield link; }(Symlink{path, linkTarget}); + }, + }, + rparen, + }); +} + +Fragment make_directory(Path path, std::vector> entries) +{ + std::vector parts; + for (auto & [dentryName, dentry] : entries) { + assert(dentryName.size() <= 255); + parts.push_back(metaString("entry")); + parts.push_back(lparen); + parts.push_back(metaString("name")); + parts.emplace_back(metaString(dentryName)); + parts.push_back(metaString("node")); + parts.push_back(dentry); + parts.push_back(rparen); + } + + auto inner = concat(parts); + + return concat({ + lparen, + type, + metaString("directory"), + { + inner.first + rparen.first, + [path, inner] { + return ([](auto dir) -> Entries { co_yield std::move(dir); })(Directory{ + path, + [](auto inner) -> Entries { + co_yield std::move(inner); + // this should be a separate item, but the parser emits it + // from within the directory. but as long as it's there... + co_yield rparen.second(); + }(inner.second()), + }); + }, + }, + }); +} + +void assert_eq(Entry & a, Entry & b); + +void assert_eq(const MetadataString & a, const MetadataString & b) +{ + ASSERT_EQ(a.data, b.data); +} +void assert_eq(const MetadataRaw & a, const MetadataRaw & b) +{ + ASSERT_PRED2(std::ranges::equal, a.raw, b.raw); +} +void assert_eq(File & a, File & b) +{ + ASSERT_EQ(a.path, b.path); + ASSERT_EQ(a.executable, b.executable); + ASSERT_EQ(a.size, b.size); + auto acontents = GeneratorSource(std::move(a.contents)).drain(); + auto bcontents = GeneratorSource(std::move(b.contents)).drain(); + ASSERT_EQ(acontents, bcontents); +} +void assert_eq(const Symlink & a, const Symlink & b) +{ + ASSERT_EQ(a.path, b.path); + ASSERT_EQ(a.target, b.target); +} +void assert_eq(Directory & a, Directory & b) +{ + ASSERT_EQ(a.path, b.path); + while (true) { + auto ae = a.contents.next(); + auto be = b.contents.next(); + ASSERT_EQ(ae.has_value(), be.has_value()); + if (!ae.has_value()) { + break; + } + assert_eq(*ae, *be); + } +} + +void assert_eq(Entry & a, Entry & b) +{ + ASSERT_EQ(a.index(), b.index()); + return std ::visit( + overloaded{ + [](T & a, T & b) { assert_eq(a, b); }, + [](auto &, auto &) {}, + }, + a, + b + ); +} +} + +class NarTest : public testing::TestWithParam +{}; + +TEST_P(NarTest, parse) +{ + auto & [raw, entriesF] = GetParam(); + StringSource source(raw); + + auto entries = entriesF(); + 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); + } +} + +INSTANTIATE_TEST_SUITE_P( + , + NarTest, + testing::Values( + concat({header, make_file(false, "", "")}), + concat({header, make_file(false, "", "short")}), + concat({header, make_file(false, "", "block000")}), + concat({header, make_file(false, "", "block0001")}), + concat({header, make_file(true, "", "")}), + concat({header, make_file(true, "", "short")}), + concat({header, make_file(true, "", "block000")}), + concat({header, make_file(true, "", "block0001")}), + concat({header, make_symlink("", "")}), + concat({header, make_symlink("", "short")}), + concat({header, make_symlink("", "block000")}), + concat({header, make_symlink("", "block0001")}), + + concat({header, make_directory("", {{"a", make_file(false, "/a", "")}})}), + concat({header, make_directory("", {{"a", make_file(false, "/a", "short")}})}), + concat({header, make_directory("", {{"a", make_file(false, "/a", "block000")}})}), + concat({header, make_directory("", {{"a", make_file(false, "/a", "block0001")}})}), + concat({header, make_directory("", {{"a", make_file(true, "/a", "")}})}), + concat({header, make_directory("", {{"a", make_file(true, "/a", "short")}})}), + concat({header, make_directory("", {{"a", make_file(true, "/a", "block000")}})}), + concat({header, make_directory("", {{"a", make_file(true, "/a", "block0001")}})}), + concat({header, make_directory("", {{"a", make_symlink("/a", "")}})}), + concat({header, make_directory("", {{"a", make_symlink("/a", "short")}})}), + concat({header, make_directory("", {{"a", make_symlink("/a", "block000")}})}), + concat({header, make_directory("", {{"a", make_symlink("/a", "block0001")}})}), + + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_file(false, "/d/a", "")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_file(false, "/d/a", "short")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_file(false, "/d/a", "block000")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_file(false, "/d/a", "block0001")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_file(true, "/d/a", "")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_file(true, "/d/a", "short")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_file(true, "/d/a", "block000")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_file(true, "/d/a", "block0001")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_symlink("/d/a", "")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_symlink("/d/a", "short")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_symlink("/d/a", "block000")}})}})}), + concat({header, make_directory("", {{"d", make_directory("/d", {{"a", make_symlink("/d/a", "block0001")}})}})}) + ) +); +} diff --git a/tests/unit/meson.build b/tests/unit/meson.build index d82f12bf7..4e3dfa338 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -42,6 +42,7 @@ liblixutil_test_support = declare_dependency( ) libutil_tests_sources = files( + 'libutil/archive.cc', 'libutil/async-collect.cc', 'libutil/async-semaphore.cc', 'libutil/canon-path.cc',