aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Ericson <John.Ericson@Obsidian.Systems>2023-01-19 00:26:06 -0500
committerJohn Ericson <John.Ericson@Obsidian.Systems>2023-01-23 07:05:50 -0500
commit018e2571aad8c68c80207f84b6b20695f20e5c40 (patch)
tree786df005464ec975d2781c0f47822700487684e8 /src
parent685395332d75713bc7aca0c6408fc1b9d2c14bc5 (diff)
Test store paths, with property tests
The property test in fact found a bug: we were excluding numbers!
Diffstat (limited to 'src')
-rw-r--r--src/libstore/outputs-spec.cc15
-rw-r--r--src/libstore/path-regex.hh7
-rw-r--r--src/libstore/path.cc6
-rw-r--r--src/libstore/path.hh2
-rw-r--r--src/libstore/tests/libstoretests.hh23
-rw-r--r--src/libstore/tests/outputs-spec.cc7
-rw-r--r--src/libstore/tests/path.cc144
-rw-r--r--src/libutil/regex-combinators.hh30
8 files changed, 227 insertions, 7 deletions
diff --git a/src/libstore/outputs-spec.cc b/src/libstore/outputs-spec.cc
index 096443cb2..e26c38138 100644
--- a/src/libstore/outputs-spec.cc
+++ b/src/libstore/outputs-spec.cc
@@ -1,8 +1,10 @@
+#include <regex>
+#include <nlohmann/json.hpp>
+
#include "util.hh"
+#include "regex-combinators.hh"
#include "outputs-spec.hh"
-#include "nlohmann/json.hpp"
-
-#include <regex>
+#include "path-regex.hh"
namespace nix {
@@ -18,11 +20,14 @@ bool OutputsSpec::contains(const std::string & outputName) const
}, raw());
}
+static std::string outputSpecRegexStr =
+ regex::either(
+ regex::group(R"(\*)"),
+ regex::group(regex::list(nameRegexStr)));
std::optional<OutputsSpec> OutputsSpec::parseOpt(std::string_view s)
{
- // See checkName() for valid output name characters.
- static std::regex regex(R"((\*)|([a-zA-Z\+\-\._\?=]+(,[a-zA-Z\+\-\._\?=]+)*))");
+ static std::regex regex(std::string { outputSpecRegexStr });
std::smatch match;
std::string s2 { s }; // until some improves std::regex
diff --git a/src/libstore/path-regex.hh b/src/libstore/path-regex.hh
new file mode 100644
index 000000000..6893c3876
--- /dev/null
+++ b/src/libstore/path-regex.hh
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace nix {
+
+static constexpr std::string_view nameRegexStr = R"([0-9a-zA-Z\+\-\._\?=]+)";
+
+}
diff --git a/src/libstore/path.cc b/src/libstore/path.cc
index 392db225e..46be54281 100644
--- a/src/libstore/path.cc
+++ b/src/libstore/path.cc
@@ -8,8 +8,10 @@ static void checkName(std::string_view path, std::string_view name)
{
if (name.empty())
throw BadStorePath("store path '%s' has an empty name", path);
- if (name.size() > 211)
- throw BadStorePath("store path '%s' has a name longer than 211 characters", path);
+ if (name.size() > StorePath::MaxPathLen)
+ throw BadStorePath("store path '%s' has a name longer than '%d characters",
+ StorePath::MaxPathLen, path);
+ // See nameRegexStr for the definition
for (auto c : name)
if (!((c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'z')
diff --git a/src/libstore/path.hh b/src/libstore/path.hh
index 8e1cb5e55..6a8f027f9 100644
--- a/src/libstore/path.hh
+++ b/src/libstore/path.hh
@@ -16,6 +16,8 @@ public:
/* Size of the hash part of store paths, in base-32 characters. */
constexpr static size_t HashLen = 32; // i.e. 160 bits
+ constexpr static size_t MaxPathLen = 211;
+
StorePath() = delete;
StorePath(std::string_view baseName);
diff --git a/src/libstore/tests/libstoretests.hh b/src/libstore/tests/libstoretests.hh
new file mode 100644
index 000000000..05397659b
--- /dev/null
+++ b/src/libstore/tests/libstoretests.hh
@@ -0,0 +1,23 @@
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include "store-api.hh"
+
+namespace nix {
+
+class LibStoreTest : public ::testing::Test {
+ public:
+ static void SetUpTestSuite() {
+ initLibStore();
+ }
+
+ protected:
+ LibStoreTest()
+ : store(openStore("dummy://"))
+ { }
+
+ ref<Store> store;
+};
+
+
+} /* namespace nix */
diff --git a/src/libstore/tests/outputs-spec.cc b/src/libstore/tests/outputs-spec.cc
index 836ba7e82..06e4cabbd 100644
--- a/src/libstore/tests/outputs-spec.cc
+++ b/src/libstore/tests/outputs-spec.cc
@@ -47,6 +47,13 @@ TEST(OutputsSpec, names_underscore) {
ASSERT_EQ(expected.to_string(), str);
}
+TEST(OutputsSpec, names_numberic) {
+ std::string_view str = "01";
+ OutputsSpec expected = OutputsSpec::Names { "01" };
+ ASSERT_EQ(OutputsSpec::parse(str), expected);
+ ASSERT_EQ(expected.to_string(), str);
+}
+
TEST(OutputsSpec, names_out_bin) {
OutputsSpec expected = OutputsSpec::Names { "out", "bin" };
ASSERT_EQ(OutputsSpec::parse("out,bin"), expected);
diff --git a/src/libstore/tests/path.cc b/src/libstore/tests/path.cc
new file mode 100644
index 000000000..8ea252c92
--- /dev/null
+++ b/src/libstore/tests/path.cc
@@ -0,0 +1,144 @@
+#include <regex>
+
+#include <nlohmann/json.hpp>
+#include <gtest/gtest.h>
+#include <rapidcheck/gtest.h>
+
+#include "path-regex.hh"
+#include "store-api.hh"
+
+#include "libstoretests.hh"
+
+namespace nix {
+
+#define STORE_DIR "/nix/store/"
+#define HASH_PART "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q"
+
+class StorePathTest : public LibStoreTest
+{
+};
+
+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( \
+ store->parseStorePath(str), \
+ BadStorePath); \
+ std::string name { STR }; \
+ 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")
+
+#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_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")
+
+#undef TEST_DO_PARSE
+
+// For rapidcheck
+void showValue(const StorePath & p, std::ostream & os) {
+ os << p.to_string();
+}
+
+}
+
+namespace rc {
+using namespace nix;
+
+template<>
+struct Arbitrary<StorePath> {
+ static Gen<StorePath> arbitrary();
+};
+
+Gen<StorePath> Arbitrary<StorePath>::arbitrary()
+{
+ auto len = *gen::inRange<size_t>(1, StorePath::MaxPathLen);
+
+ std::string pre { HASH_PART "-" };
+ pre.reserve(pre.size() + len);
+
+ for (size_t c = 0; c < len; ++c) {
+ switch (auto i = *gen::inRange<uint8_t>(0, 10 + 2 * 26 + 6)) {
+ case 0 ... 9:
+ pre += '0' + i;
+ case 10 ... 35:
+ pre += 'A' + (i - 10);
+ break;
+ case 36 ... 61:
+ pre += 'a' + (i - 36);
+ break;
+ case 62:
+ pre += '+';
+ break;
+ case 63:
+ pre += '-';
+ break;
+ case 64:
+ pre += '.';
+ break;
+ case 65:
+ pre += '_';
+ break;
+ case 66:
+ pre += '?';
+ break;
+ case 67:
+ pre += '=';
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ return gen::just(StorePath { pre });
+}
+
+} // namespace rc
+
+namespace nix {
+
+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)));
+}
+
+}
diff --git a/src/libutil/regex-combinators.hh b/src/libutil/regex-combinators.hh
new file mode 100644
index 000000000..0b997b25a
--- /dev/null
+++ b/src/libutil/regex-combinators.hh
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <string_view>
+
+namespace nix::regex {
+
+// TODO use constexpr string building like
+// https://github.com/akrzemi1/static_string/blob/master/include/ak_toolkit/static_string.hpp
+
+static inline std::string either(std::string_view a, std::string_view b)
+{
+ return std::string { a } + "|" + b;
+}
+
+static inline std::string group(std::string_view a)
+{
+ return std::string { "(" } + a + ")";
+}
+
+static inline std::string many(std::string_view a)
+{
+ return std::string { "(?:" } + a + ")*";
+}
+
+static inline std::string list(std::string_view a)
+{
+ return std::string { a } + many(group("," + a));
+}
+
+}