mirror of
https://github.com/NixOS/nix.git
synced 2025-12-09 18:41:03 +01:00
See #13570 for details --- the idea is that included the store dir in store paths makes systematic JSON parting with e.g. Serde, Aeson, nlohmann, or similiar harder. After talking to Eelco, we are changing the `Derivation` format right away because not only is `nix derivation` technically experimental, we think it is also less widely used in practice than, say, `nix path-info`. Progress on #13570
175 lines
7 KiB
C++
175 lines
7 KiB
C++
#include <regex>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
#include <gtest/gtest.h>
|
|
#include <rapidcheck/gtest.h>
|
|
|
|
#include "nix/store/path-regex.hh"
|
|
#include "nix/store/store-api.hh"
|
|
|
|
#include "nix/util/tests/characterization.hh"
|
|
#include "nix/store/tests/libstore.hh"
|
|
#include "nix/store/tests/path.hh"
|
|
|
|
namespace nix {
|
|
|
|
#define STORE_DIR "/nix/store/"
|
|
#define HASH_PART "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q"
|
|
|
|
class StorePathTest : public CharacterizationTest, public LibStoreTest
|
|
{
|
|
std::filesystem::path unitTestData = getUnitTestData() / "store-path";
|
|
|
|
public:
|
|
|
|
std::filesystem::path goldenMaster(std::string_view testStem) const override
|
|
{
|
|
return unitTestData / testStem;
|
|
}
|
|
};
|
|
|
|
static std::regex nameRegex{std::string{nameRegexStr}};
|
|
|
|
#define TEST_DONT_PARSE(NAME, STR) \
|
|
TEST_F(StorePathTest, bad_##NAME) \
|
|
{ \
|
|
std::string_view str = STORE_DIR HASH_PART "-" STR; \
|
|
/* ASSERT_THROW generates a duplicate goto label */ \
|
|
/* A lambda isolates those labels. */ \
|
|
[&]() { ASSERT_THROW(store->parseStorePath(str), BadStorePath); }(); \
|
|
std::string name{STR}; \
|
|
[&]() { ASSERT_THROW(nix::checkName(name), BadStorePathName); }(); \
|
|
EXPECT_FALSE(std::regex_match(name, nameRegex)); \
|
|
}
|
|
|
|
TEST_DONT_PARSE(empty, "")
|
|
TEST_DONT_PARSE(garbage, "&*()")
|
|
TEST_DONT_PARSE(double_star, "**")
|
|
TEST_DONT_PARSE(star_first, "*,foo")
|
|
TEST_DONT_PARSE(star_second, "foo,*")
|
|
TEST_DONT_PARSE(bang, "foo!o")
|
|
TEST_DONT_PARSE(dot, ".")
|
|
TEST_DONT_PARSE(dot_dot, "..")
|
|
TEST_DONT_PARSE(dot_dot_dash, "..-1")
|
|
TEST_DONT_PARSE(dot_dash, ".-1")
|
|
TEST_DONT_PARSE(dot_dot_dash_a, "..-a")
|
|
TEST_DONT_PARSE(dot_dash_a, ".-a")
|
|
|
|
#undef TEST_DONT_PARSE
|
|
|
|
#define TEST_DO_PARSE(NAME, STR) \
|
|
TEST_F(StorePathTest, good_##NAME) \
|
|
{ \
|
|
std::string_view str = STORE_DIR HASH_PART "-" STR; \
|
|
auto p = store->parseStorePath(str); \
|
|
std::string name{p.name()}; \
|
|
EXPECT_EQ(p.name(), STR); \
|
|
EXPECT_TRUE(std::regex_match(name, nameRegex)); \
|
|
}
|
|
|
|
// 0-9 a-z A-Z + - . _ ? =
|
|
|
|
TEST_DO_PARSE(numbers, "02345")
|
|
TEST_DO_PARSE(lower_case, "foo")
|
|
TEST_DO_PARSE(upper_case, "FOO")
|
|
TEST_DO_PARSE(plus, "foo+bar")
|
|
TEST_DO_PARSE(dash, "foo-dev")
|
|
TEST_DO_PARSE(underscore, "foo_bar")
|
|
TEST_DO_PARSE(period, "foo.txt")
|
|
TEST_DO_PARSE(question_mark, "foo?why")
|
|
TEST_DO_PARSE(equals_sign, "foo=foo")
|
|
TEST_DO_PARSE(dotfile, ".gitignore")
|
|
TEST_DO_PARSE(triple_dot_a, "...a")
|
|
TEST_DO_PARSE(triple_dot_1, "...1")
|
|
TEST_DO_PARSE(triple_dot_dash, "...-")
|
|
TEST_DO_PARSE(triple_dot, "...")
|
|
|
|
#undef TEST_DO_PARSE
|
|
|
|
#ifndef COVERAGE
|
|
|
|
RC_GTEST_FIXTURE_PROP(StorePathTest, prop_regex_accept, (const StorePath & p))
|
|
{
|
|
RC_ASSERT(std::regex_match(std::string{p.name()}, nameRegex));
|
|
}
|
|
|
|
RC_GTEST_FIXTURE_PROP(StorePathTest, prop_round_rip, (const StorePath & p))
|
|
{
|
|
RC_ASSERT(p == store->parseStorePath(store->printStorePath(p)));
|
|
}
|
|
|
|
RC_GTEST_FIXTURE_PROP(StorePathTest, prop_check_regex_eq_parse, ())
|
|
{
|
|
static auto nameFuzzer = rc::gen::container<std::string>(rc::gen::oneOf(
|
|
// alphanum, repeated to weigh heavier
|
|
rc::gen::oneOf(rc::gen::inRange('0', '9'), rc::gen::inRange('a', 'z'), rc::gen::inRange('A', 'Z')),
|
|
// valid symbols
|
|
rc::gen::oneOf(
|
|
rc::gen::just('+'),
|
|
rc::gen::just('-'),
|
|
rc::gen::just('.'),
|
|
rc::gen::just('_'),
|
|
rc::gen::just('?'),
|
|
rc::gen::just('=')),
|
|
// symbols for scary .- and ..- cases, repeated for weight
|
|
rc::gen::just('.'),
|
|
rc::gen::just('.'),
|
|
rc::gen::just('.'),
|
|
rc::gen::just('.'),
|
|
rc::gen::just('-'),
|
|
rc::gen::just('-'),
|
|
// ascii symbol ranges
|
|
rc::gen::oneOf(
|
|
rc::gen::inRange(' ', '/'),
|
|
rc::gen::inRange(':', '@'),
|
|
rc::gen::inRange('[', '`'),
|
|
rc::gen::inRange('{', '~')),
|
|
// typical whitespace
|
|
rc::gen::oneOf(rc::gen::just(' '), rc::gen::just('\t'), rc::gen::just('\n'), rc::gen::just('\r')),
|
|
// some chance of control codes, non-ascii or other garbage we missed
|
|
rc::gen::inRange('\0', '\xff')));
|
|
|
|
auto name = *nameFuzzer;
|
|
|
|
std::string path = store->storeDir + "/575s52sh487i0ylmbs9pvi606ljdszr0-" + name;
|
|
bool parsed = false;
|
|
try {
|
|
store->parseStorePath(path);
|
|
parsed = true;
|
|
} catch (const BadStorePath &) {
|
|
}
|
|
RC_ASSERT(parsed == std::regex_match(std::string{name}, nameRegex));
|
|
}
|
|
|
|
#endif
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
* JSON
|
|
* --------------------------------------------------------------------------*/
|
|
|
|
using nlohmann::json;
|
|
|
|
#define TEST_JSON(FIXTURE, NAME, VAL) \
|
|
static const StorePath NAME = VAL; \
|
|
\
|
|
TEST_F(FIXTURE, NAME##_from_json) \
|
|
{ \
|
|
readTest(#NAME ".json", [&](const auto & encoded_) { \
|
|
auto encoded = json::parse(encoded_); \
|
|
StorePath got = static_cast<StorePath>(encoded); \
|
|
ASSERT_EQ(got, NAME); \
|
|
}); \
|
|
} \
|
|
\
|
|
TEST_F(FIXTURE, NAME##_to_json) \
|
|
{ \
|
|
writeTest( \
|
|
#NAME ".json", \
|
|
[&]() -> json { return static_cast<json>(NAME); }, \
|
|
[](const auto & file) { return json::parse(readFile(file)); }, \
|
|
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
|
|
}
|
|
|
|
TEST_JSON(StorePathTest, simple, StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"});
|
|
|
|
} // namespace nix
|