mirror of
https://github.com/NixOS/nix.git
synced 2025-12-17 14:31:06 +01:00
For example, instead of doing
#include "nix/store-config.hh"
#include "nix/derived-path.hh"
Now do
#include "nix/store/config.hh"
#include "nix/store/derived-path.hh"
This was originally planned in the issue, and also recent requested by
Eelco.
Most of the change is purely mechanical. There is just one small
additional issue. See how, in the example above, we took this
opportunity to also turn `<comp>-config.hh` into `<comp>/config.hh`.
Well, there was already a `nix/util/config.{cc,hh}`. Even though there
is not a public configuration header for libutil (which also would be
called `nix/util/config.{cc,hh}`) that's still confusing, To avoid any
such confusion, we renamed that to `nix/util/configuration.{cc,hh}`.
Finally, note that the libflake headers already did this, so we didn't
need to do anything to them. We wouldn't want to mistakenly get
`nix/flake/flake/flake.hh`!
Progress on #7876
262 lines
7.5 KiB
C++
262 lines
7.5 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include "nix/util/git.hh"
|
|
#include "nix/util/memory-source-accessor.hh"
|
|
|
|
#include "nix/util/tests/characterization.hh"
|
|
|
|
namespace nix {
|
|
|
|
using namespace git;
|
|
|
|
class GitTest : public CharacterizationTest
|
|
{
|
|
std::filesystem::path unitTestData = getUnitTestData() / "git";
|
|
|
|
public:
|
|
|
|
std::filesystem::path goldenMaster(std::string_view testStem) const override {
|
|
return unitTestData / std::string(testStem);
|
|
}
|
|
|
|
/**
|
|
* We set these in tests rather than the regular globals so we don't have
|
|
* to worry about race conditions if the tests run concurrently.
|
|
*/
|
|
ExperimentalFeatureSettings mockXpSettings;
|
|
|
|
private:
|
|
|
|
void SetUp() override
|
|
{
|
|
mockXpSettings.set("experimental-features", "git-hashing");
|
|
}
|
|
};
|
|
|
|
TEST(GitMode, gitMode_directory) {
|
|
Mode m = Mode::Directory;
|
|
RawMode r = 0040000;
|
|
ASSERT_EQ(static_cast<RawMode>(m), r);
|
|
ASSERT_EQ(decodeMode(r), std::optional { m });
|
|
};
|
|
|
|
TEST(GitMode, gitMode_executable) {
|
|
Mode m = Mode::Executable;
|
|
RawMode r = 0100755;
|
|
ASSERT_EQ(static_cast<RawMode>(m), r);
|
|
ASSERT_EQ(decodeMode(r), std::optional { m });
|
|
};
|
|
|
|
TEST(GitMode, gitMode_regular) {
|
|
Mode m = Mode::Regular;
|
|
RawMode r = 0100644;
|
|
ASSERT_EQ(static_cast<RawMode>(m), r);
|
|
ASSERT_EQ(decodeMode(r), std::optional { m });
|
|
};
|
|
|
|
TEST(GitMode, gitMode_symlink) {
|
|
Mode m = Mode::Symlink;
|
|
RawMode r = 0120000;
|
|
ASSERT_EQ(static_cast<RawMode>(m), r);
|
|
ASSERT_EQ(decodeMode(r), std::optional { m });
|
|
};
|
|
|
|
TEST_F(GitTest, blob_read) {
|
|
readTest("hello-world-blob.bin", [&](const auto & encoded) {
|
|
StringSource in { encoded };
|
|
StringSink out;
|
|
RegularFileSink out2 { out };
|
|
ASSERT_EQ(parseObjectType(in, mockXpSettings), ObjectType::Blob);
|
|
parseBlob(out2, CanonPath::root, in, BlobMode::Regular, mockXpSettings);
|
|
|
|
auto expected = readFile(goldenMaster("hello-world.bin"));
|
|
|
|
ASSERT_EQ(out.s, expected);
|
|
});
|
|
}
|
|
|
|
TEST_F(GitTest, blob_write) {
|
|
writeTest("hello-world-blob.bin", [&]() {
|
|
auto decoded = readFile(goldenMaster("hello-world.bin"));
|
|
StringSink s;
|
|
dumpBlobPrefix(decoded.size(), s, mockXpSettings);
|
|
s(decoded);
|
|
return s.s;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This data is for "shallow" tree tests. However, we use "real" hashes
|
|
* so that we can check our test data in a small shell script test test
|
|
* (`src/libutil-tests/data/git/check-data.sh`).
|
|
*/
|
|
const static Tree tree = {
|
|
{
|
|
"Foo",
|
|
{
|
|
.mode = Mode::Regular,
|
|
// hello world with special chars from above
|
|
.hash = Hash::parseAny("63ddb340119baf8492d2da53af47e8c7cfcd5eb2", HashAlgorithm::SHA1),
|
|
},
|
|
},
|
|
{
|
|
"bAr",
|
|
{
|
|
.mode = Mode::Executable,
|
|
// ditto
|
|
.hash = Hash::parseAny("63ddb340119baf8492d2da53af47e8c7cfcd5eb2", HashAlgorithm::SHA1),
|
|
},
|
|
},
|
|
{
|
|
"baZ/",
|
|
{
|
|
.mode = Mode::Directory,
|
|
// Empty directory hash
|
|
.hash = Hash::parseAny("4b825dc642cb6eb9a060e54bf8d69288fbee4904", HashAlgorithm::SHA1),
|
|
},
|
|
},
|
|
{
|
|
"quuX",
|
|
{
|
|
.mode = Mode::Symlink,
|
|
// hello world with special chars from above (symlink target
|
|
// can be anything)
|
|
.hash = Hash::parseAny("63ddb340119baf8492d2da53af47e8c7cfcd5eb2", HashAlgorithm::SHA1),
|
|
},
|
|
},
|
|
};
|
|
|
|
TEST_F(GitTest, tree_read) {
|
|
readTest("tree.bin", [&](const auto & encoded) {
|
|
StringSource in { encoded };
|
|
NullFileSystemObjectSink out;
|
|
Tree got;
|
|
ASSERT_EQ(parseObjectType(in, mockXpSettings), ObjectType::Tree);
|
|
parseTree(out, CanonPath::root, in, [&](auto & name, auto entry) {
|
|
auto name2 = std::string{name.rel()};
|
|
if (entry.mode == Mode::Directory)
|
|
name2 += '/';
|
|
got.insert_or_assign(name2, std::move(entry));
|
|
}, mockXpSettings);
|
|
|
|
ASSERT_EQ(got, tree);
|
|
});
|
|
}
|
|
|
|
TEST_F(GitTest, tree_write) {
|
|
writeTest("tree.bin", [&]() {
|
|
StringSink s;
|
|
dumpTree(tree, s, mockXpSettings);
|
|
return s.s;
|
|
});
|
|
}
|
|
|
|
TEST_F(GitTest, both_roundrip) {
|
|
using File = MemorySourceAccessor::File;
|
|
|
|
auto files = make_ref<MemorySourceAccessor>();
|
|
files->root = File::Directory {
|
|
.contents {
|
|
{
|
|
"foo",
|
|
File::Regular {
|
|
.contents = "hello\n\0\n\tworld!",
|
|
},
|
|
},
|
|
{
|
|
"bar",
|
|
File::Directory {
|
|
.contents = {
|
|
{
|
|
"baz",
|
|
File::Regular {
|
|
.executable = true,
|
|
.contents = "good day,\n\0\n\tworld!",
|
|
},
|
|
},
|
|
{
|
|
"quux",
|
|
File::Symlink {
|
|
.target = "/over/there",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
std::map<Hash, std::string> cas;
|
|
|
|
std::function<DumpHook> dumpHook;
|
|
dumpHook = [&](const SourcePath & path) {
|
|
StringSink s;
|
|
HashSink hashSink { HashAlgorithm::SHA1 };
|
|
TeeSink s2 { s, hashSink };
|
|
auto mode = dump(
|
|
path, s2, dumpHook,
|
|
defaultPathFilter, mockXpSettings);
|
|
auto hash = hashSink.finish().first;
|
|
cas.insert_or_assign(hash, std::move(s.s));
|
|
return TreeEntry {
|
|
.mode = mode,
|
|
.hash = hash,
|
|
};
|
|
};
|
|
|
|
auto root = dumpHook({files});
|
|
|
|
auto files2 = make_ref<MemorySourceAccessor>();
|
|
|
|
MemorySink sinkFiles2 { *files2 };
|
|
|
|
std::function<void(const CanonPath, const Hash &, BlobMode)> mkSinkHook;
|
|
mkSinkHook = [&](auto prefix, auto & hash, auto blobMode) {
|
|
StringSource in { cas[hash] };
|
|
parse(
|
|
sinkFiles2, prefix, in, blobMode,
|
|
[&](const CanonPath & name, const auto & entry) {
|
|
mkSinkHook(
|
|
prefix / name,
|
|
entry.hash,
|
|
// N.B. this cast would not be acceptable in real
|
|
// code, because it would make an assert reachable,
|
|
// but it should harmless in this test.
|
|
static_cast<BlobMode>(entry.mode));
|
|
},
|
|
mockXpSettings);
|
|
};
|
|
|
|
mkSinkHook(CanonPath::root, root.hash, BlobMode::Regular);
|
|
|
|
ASSERT_EQ(files->root, files2->root);
|
|
}
|
|
|
|
TEST(GitLsRemote, parseSymrefLineWithReference) {
|
|
auto line = "ref: refs/head/main HEAD";
|
|
auto res = parseLsRemoteLine(line);
|
|
ASSERT_TRUE(res.has_value());
|
|
ASSERT_EQ(res->kind, LsRemoteRefLine::Kind::Symbolic);
|
|
ASSERT_EQ(res->target, "refs/head/main");
|
|
ASSERT_EQ(res->reference, "HEAD");
|
|
}
|
|
|
|
TEST(GitLsRemote, parseSymrefLineWithNoReference) {
|
|
auto line = "ref: refs/head/main";
|
|
auto res = parseLsRemoteLine(line);
|
|
ASSERT_TRUE(res.has_value());
|
|
ASSERT_EQ(res->kind, LsRemoteRefLine::Kind::Symbolic);
|
|
ASSERT_EQ(res->target, "refs/head/main");
|
|
ASSERT_EQ(res->reference, std::nullopt);
|
|
}
|
|
|
|
TEST(GitLsRemote, parseObjectRefLine) {
|
|
auto line = "abc123 refs/head/main";
|
|
auto res = parseLsRemoteLine(line);
|
|
ASSERT_TRUE(res.has_value());
|
|
ASSERT_EQ(res->kind, LsRemoteRefLine::Kind::Object);
|
|
ASSERT_EQ(res->target, "abc123");
|
|
ASSERT_EQ(res->reference, "refs/head/main");
|
|
}
|
|
|
|
}
|