aboutsummaryrefslogtreecommitdiff
path: root/src/libstore
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstore')
-rw-r--r--src/libstore/binary-cache-store.cc50
-rw-r--r--src/libstore/build/local-derivation-goal.cc12
-rw-r--r--src/libstore/build/substitution-goal.cc7
-rw-r--r--src/libstore/content-address.hh7
-rw-r--r--src/libstore/derivations.cc7
-rw-r--r--src/libstore/derivations.hh1
-rw-r--r--src/libstore/local-store.cc35
-rw-r--r--src/libstore/make-content-addressed.cc14
-rw-r--r--src/libstore/misc.cc2
-rw-r--r--src/libstore/nar-info.hh4
-rw-r--r--src/libstore/outputs-spec.cc22
-rw-r--r--src/libstore/path-info.cc64
-rw-r--r--src/libstore/path-info.hh4
-rw-r--r--src/libstore/path-regex.hh7
-rw-r--r--src/libstore/path.cc6
-rw-r--r--src/libstore/path.hh3
-rw-r--r--src/libstore/realisation.hh2
-rw-r--r--src/libstore/store-api.cc28
-rw-r--r--src/libstore/store-api.hh2
-rw-r--r--src/libstore/tests/libstoretests.hh23
-rw-r--r--src/libstore/tests/local.mk2
-rw-r--r--src/libstore/tests/outputs-spec.cc14
-rw-r--r--src/libstore/tests/path.cc144
23 files changed, 320 insertions, 140 deletions
diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc
index ac41add2c..9058bb8b1 100644
--- a/src/libstore/binary-cache-store.cc
+++ b/src/libstore/binary-cache-store.cc
@@ -307,17 +307,15 @@ StorePath BinaryCacheStore::addToStoreFromDump(Source & dump, std::string_view n
return addToStoreCommon(dump, repair, CheckSigs, [&](HashResult nar) {
ValidPathInfo info {
*this,
- {
- .name = std::string { name },
- .info = FixedOutputInfo {
- {
- .method = method,
- .hash = nar.first,
- },
- .references = {
- .others = references,
- .self = false,
- },
+ name,
+ FixedOutputInfo {
+ {
+ .method = method,
+ .hash = nar.first,
+ },
+ .references = {
+ .others = references,
+ .self = false,
},
},
nar.first,
@@ -427,17 +425,15 @@ StorePath BinaryCacheStore::addToStore(
return addToStoreCommon(*source, repair, CheckSigs, [&](HashResult nar) {
ValidPathInfo info {
*this,
- {
- .name = std::string { name },
- .info = FixedOutputInfo {
- {
- .method = method,
- .hash = h,
- },
- .references = {
- .others = references,
- .self = false,
- },
+ name,
+ FixedOutputInfo {
+ {
+ .method = method,
+ .hash = h,
+ },
+ .references = {
+ .others = references,
+ .self = false,
},
},
nar.first,
@@ -465,12 +461,10 @@ StorePath BinaryCacheStore::addTextToStore(
return addToStoreCommon(source, repair, CheckSigs, [&](HashResult nar) {
ValidPathInfo info {
*this,
- {
- .name = std::string { name },
- .info = TextInfo {
- { .hash = textHash },
- references,
- },
+ std::string { name },
+ TextInfo {
+ { .hash = textHash },
+ references,
},
nar.first,
};
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 88cb33d0f..6f3048b63 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -2484,13 +2484,11 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
auto got = caSink.finish().first;
ValidPathInfo newInfo0 {
worker.store,
- {
- .name = outputPathName(drv->name, outputName),
- .info = contentAddressFromMethodHashAndRefs(
- outputHash.method,
- std::move(got),
- rewriteRefs()),
- },
+ outputPathName(drv->name, outputName),
+ contentAddressFromMethodHashAndRefs(
+ outputHash.method,
+ std::move(got),
+ rewriteRefs()),
Hash::dummy,
};
if (*scratchPath != newInfo0.path) {
diff --git a/src/libstore/build/substitution-goal.cc b/src/libstore/build/substitution-goal.cc
index 994cb4ac2..87fed495c 100644
--- a/src/libstore/build/substitution-goal.cc
+++ b/src/libstore/build/substitution-goal.cc
@@ -95,10 +95,9 @@ void PathSubstitutionGoal::tryNext()
subs.pop_front();
if (ca) {
- subPath = sub->makeFixedOutputPathFromCA({
- .name = std::string { storePath.name() },
- .info = caWithoutRefs(*ca),
- });
+ subPath = sub->makeFixedOutputPathFromCA(
+ std::string { storePath.name() },
+ caWithoutRefs(*ca));
if (sub->storeDir == worker.store.storeDir)
assert(subPath == storePath);
} else if (sub->storeDir != worker.store.storeDir) {
diff --git a/src/libstore/content-address.hh b/src/libstore/content-address.hh
index a251aeb01..729b1078d 100644
--- a/src/libstore/content-address.hh
+++ b/src/libstore/content-address.hh
@@ -147,11 +147,4 @@ Hash getContentAddressHash(const ContentAddressWithReferences & ca);
std::string printMethodAlgo(const ContentAddressWithReferences &);
-struct StorePathDescriptor {
- std::string name;
- ContentAddressWithReferences info;
-
- GENERATE_CMP(StorePathDescriptor, me->name, me->info);
-};
-
}
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index 2a1fbe0f6..b19ee5ed1 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -35,10 +35,9 @@ std::optional<StorePath> DerivationOutput::path(const Store & store, std::string
StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const
{
- return store.makeFixedOutputPathFromCA(StorePathDescriptor {
- .name = outputPathName(drvName, outputName),
- .info = ca,
- });
+ return store.makeFixedOutputPathFromCA(
+ outputPathName(drvName, outputName),
+ ca);
}
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index c57a6f808..803ab3cd6 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -13,6 +13,7 @@
namespace nix {
+class Store;
/* Abstract syntax of derivations. */
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 654aee973..94c005130 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -1136,10 +1136,9 @@ void LocalStore::querySubstitutablePathInfos(const StorePathCAMap & paths, Subst
// Recompute store path so that we can use a different store root.
if (path.second) {
- subPath = makeFixedOutputPathFromCA({
- .name = std::string { path.first.name() },
- .info = caWithoutRefs(*path.second),
- });
+ subPath = makeFixedOutputPathFromCA(
+ path.first.name(),
+ caWithoutRefs(*path.second));
if (sub->storeDir == storeDir)
assert(subPath == path.first);
if (subPath != path.first)
@@ -1417,21 +1416,18 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
auto [hash, size] = hashSink->finish();
- auto desc = StorePathDescriptor {
- std::string { name },
- FixedOutputInfo {
- {
- .method = method,
- .hash = hash,
- },
- .references = {
- .others = references,
- .self = false,
- },
+ ContentAddressWithReferences desc = FixedOutputInfo {
+ {
+ .method = method,
+ .hash = hash,
+ },
+ .references = {
+ .others = references,
+ .self = false,
},
};
- auto dstPath = makeFixedOutputPathFromCA(desc);
+ auto dstPath = makeFixedOutputPathFromCA(name, desc);
addTempRoot(dstPath);
@@ -1475,7 +1471,12 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
optimisePath(realPath, repair);
- ValidPathInfo info { *this, std::move(desc), narHash.first };
+ ValidPathInfo info {
+ *this,
+ name,
+ std::move(desc),
+ narHash.first
+ };
info.narSize = narHash.second;
registerValidPath(info);
}
diff --git a/src/libstore/make-content-addressed.cc b/src/libstore/make-content-addressed.cc
index 09f615439..3ee64c77a 100644
--- a/src/libstore/make-content-addressed.cc
+++ b/src/libstore/make-content-addressed.cc
@@ -49,15 +49,13 @@ std::map<StorePath, StorePath> makeContentAddressed(
ValidPathInfo info {
dstStore,
- StorePathDescriptor {
- .name = std::string { path.name() },
- .info = FixedOutputInfo {
- {
- .method = FileIngestionMethod::Recursive,
- .hash = narModuloHash,
- },
- .references = std::move(refs),
+ path.name(),
+ FixedOutputInfo {
+ {
+ .method = FileIngestionMethod::Recursive,
+ .hash = narModuloHash,
},
+ .references = std::move(refs),
},
Hash::dummy,
};
diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc
index dace653d6..acd4e0929 100644
--- a/src/libstore/misc.cc
+++ b/src/libstore/misc.cc
@@ -331,7 +331,7 @@ OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd,
[&](const OutputsSpec::Names & names) {
return static_cast<std::set<std::string>>(names);
},
- }, bfd.outputs);
+ }, bfd.outputs.raw());
for (auto & output : outputNames) {
auto outputHash = get(outputHashes, output);
if (!outputHash)
diff --git a/src/libstore/nar-info.hh b/src/libstore/nar-info.hh
index f1e3aabbd..a4dccb397 100644
--- a/src/libstore/nar-info.hh
+++ b/src/libstore/nar-info.hh
@@ -16,8 +16,8 @@ struct NarInfo : ValidPathInfo
uint64_t fileSize = 0;
NarInfo() = delete;
- NarInfo(const Store & store, StorePathDescriptor && ca, Hash narHash)
- : ValidPathInfo(store, std::move(ca), narHash)
+ NarInfo(const Store & store, std::string && name, ContentAddressWithReferences && ca, Hash narHash)
+ : ValidPathInfo(store, std::move(name), std::move(ca), narHash)
{ }
NarInfo(StorePath && path, Hash narHash) : ValidPathInfo(std::move(path), narHash) { }
NarInfo(const ValidPathInfo & info) : ValidPathInfo(info) { }
diff --git a/src/libstore/outputs-spec.cc b/src/libstore/outputs-spec.cc
index d0f39a854..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,10 +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)
{
- static std::regex regex(R"((\*)|([a-z]+(,[a-z]+)*))");
+ static std::regex regex(std::string { outputSpecRegexStr });
std::smatch match;
std::string s2 { s }; // until some improves std::regex
@@ -42,7 +48,7 @@ OutputsSpec OutputsSpec::parse(std::string_view s)
{
std::optional spec = parseOpt(s);
if (!spec)
- throw Error("Invalid outputs specifier: '%s'", s);
+ throw Error("invalid outputs specifier '%s'", s);
return *spec;
}
@@ -65,7 +71,7 @@ std::pair<std::string_view, ExtendedOutputsSpec> ExtendedOutputsSpec::parse(std:
{
std::optional spec = parseOpt(s);
if (!spec)
- throw Error("Invalid extended outputs specifier: '%s'", s);
+ throw Error("invalid extended outputs specifier '%s'", s);
return *spec;
}
@@ -163,7 +169,7 @@ void adl_serializer<OutputsSpec>::to_json(json & json, OutputsSpec t) {
[&](const OutputsSpec::Names & names) {
json = names;
},
- }, t);
+ }, t.raw());
}
@@ -183,7 +189,7 @@ void adl_serializer<ExtendedOutputsSpec>::to_json(json & json, ExtendedOutputsSp
[&](const ExtendedOutputsSpec::Explicit & e) {
adl_serializer<OutputsSpec>::to_json(json, e);
},
- }, t);
+ }, t.raw());
}
}
diff --git a/src/libstore/path-info.cc b/src/libstore/path-info.cc
index 93f91e702..5944afd06 100644
--- a/src/libstore/path-info.cc
+++ b/src/libstore/path-info.cc
@@ -21,48 +21,45 @@ void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey)
sigs.insert(secretKey.signDetached(fingerprint(store)));
}
-std::optional<StorePathDescriptor> ValidPathInfo::fullStorePathDescriptorOpt() const
+std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithReferenences() const
{
if (! ca)
return std::nullopt;
- return StorePathDescriptor {
- .name = std::string { path.name() },
- .info = std::visit(overloaded {
- [&](const TextHash & th) -> ContentAddressWithReferences {
- assert(references.count(path) == 0);
- return TextInfo {
- th,
- .references = references,
- };
- },
- [&](const FixedOutputHash & foh) -> ContentAddressWithReferences {
- auto refs = references;
- bool hasSelfReference = false;
- if (refs.count(path)) {
- hasSelfReference = true;
- refs.erase(path);
- }
- return FixedOutputInfo {
- foh,
- .references = {
- .others = std::move(refs),
- .self = hasSelfReference,
- },
- };
- },
- }, *ca),
- };
+ return std::visit(overloaded {
+ [&](const TextHash & th) -> ContentAddressWithReferences {
+ assert(references.count(path) == 0);
+ return TextInfo {
+ th,
+ .references = references,
+ };
+ },
+ [&](const FixedOutputHash & foh) -> ContentAddressWithReferences {
+ auto refs = references;
+ bool hasSelfReference = false;
+ if (refs.count(path)) {
+ hasSelfReference = true;
+ refs.erase(path);
+ }
+ return FixedOutputInfo {
+ foh,
+ .references = {
+ .others = std::move(refs),
+ .self = hasSelfReference,
+ },
+ };
+ },
+ }, *ca);
}
bool ValidPathInfo::isContentAddressed(const Store & store) const
{
- auto fullCaOpt = fullStorePathDescriptorOpt();
+ auto fullCaOpt = contentAddressWithReferenences();
if (! fullCaOpt)
return false;
- auto caPath = store.makeFixedOutputPathFromCA(*fullCaOpt);
+ auto caPath = store.makeFixedOutputPathFromCA(path.name(), *fullCaOpt);
bool res = caPath == path;
@@ -102,9 +99,10 @@ Strings ValidPathInfo::shortRefs() const
ValidPathInfo::ValidPathInfo(
const Store & store,
- StorePathDescriptor && info,
+ std::string_view name,
+ ContentAddressWithReferences && ca,
Hash narHash)
- : path(store.makeFixedOutputPathFromCA(info))
+ : path(store.makeFixedOutputPathFromCA(name, ca))
, narHash(narHash)
{
std::visit(overloaded {
@@ -118,7 +116,7 @@ ValidPathInfo::ValidPathInfo(
this->references.insert(path);
this->ca = std::move((FixedOutputHash &&) foi);
},
- }, std::move(info.info));
+ }, std::move(ca));
}
diff --git a/src/libstore/path-info.hh b/src/libstore/path-info.hh
index 476df79c2..663d94540 100644
--- a/src/libstore/path-info.hh
+++ b/src/libstore/path-info.hh
@@ -77,7 +77,7 @@ struct ValidPathInfo
void sign(const Store & store, const SecretKey & secretKey);
- std::optional<StorePathDescriptor> fullStorePathDescriptorOpt() const;
+ std::optional<ContentAddressWithReferences> contentAddressWithReferenences() const;
/* Return true iff the path is verifiably content-addressed. */
bool isContentAddressed(const Store & store) const;
@@ -100,7 +100,7 @@ struct ValidPathInfo
ValidPathInfo(const StorePath & path, Hash narHash) : path(path), narHash(narHash) { };
ValidPathInfo(const Store & store,
- StorePathDescriptor && ca, Hash narHash);
+ std::string_view name, ContentAddressWithReferences && ca, Hash narHash);
virtual ~ValidPathInfo() { }
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 7f13c11e9..1e5579b90 100644
--- a/src/libstore/path.hh
+++ b/src/libstore/path.hh
@@ -6,7 +6,6 @@
namespace nix {
-class Store;
struct Hash;
class StorePath
@@ -18,6 +17,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/realisation.hh b/src/libstore/realisation.hh
index 9429c7004..48d0283de 100644
--- a/src/libstore/realisation.hh
+++ b/src/libstore/realisation.hh
@@ -10,6 +10,8 @@
namespace nix {
+class Store;
+
struct DrvOutput {
// The hash modulo of the derivation
Hash drvHash;
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index c39e50d14..3c0c26706 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -209,17 +209,17 @@ StorePath Store::makeTextPath(std::string_view name, const TextInfo & info) cons
}
-StorePath Store::makeFixedOutputPathFromCA(const StorePathDescriptor & desc) const
+StorePath Store::makeFixedOutputPathFromCA(std::string_view name, const ContentAddressWithReferences & ca) const
{
// New template
return std::visit(overloaded {
[&](const TextInfo & ti) {
- return makeTextPath(desc.name, ti);
+ return makeTextPath(name, ti);
},
[&](const FixedOutputInfo & foi) {
- return makeFixedOutputPath(desc.name, foi);
+ return makeFixedOutputPath(name, foi);
}
- }, desc.info);
+ }, ca);
}
@@ -437,15 +437,13 @@ ValidPathInfo Store::addToStoreSlow(std::string_view name, const Path & srcPath,
ValidPathInfo info {
*this,
- StorePathDescriptor {
- std::string { name },
- FixedOutputInfo {
- {
- .method = method,
- .hash = hash,
- },
- .references = {},
+ name,
+ FixedOutputInfo {
+ {
+ .method = method,
+ .hash = hash,
},
+ .references = {},
},
narHash,
};
@@ -997,7 +995,8 @@ void copyStorePath(
if (info->ca && info->references.empty()) {
auto info2 = make_ref<ValidPathInfo>(*info);
info2->path = dstStore.makeFixedOutputPathFromCA(
- info->fullStorePathDescriptorOpt().value());
+ info->path.name(),
+ info->contentAddressWithReferenences().value());
if (dstStore.storeDir == srcStore.storeDir)
assert(info->path == info2->path);
info = info2;
@@ -1110,7 +1109,8 @@ std::map<StorePath, StorePath> copyPaths(
auto storePathForDst = storePathForSrc;
if (currentPathInfo.ca && currentPathInfo.references.empty()) {
storePathForDst = dstStore.makeFixedOutputPathFromCA(
- currentPathInfo.fullStorePathDescriptorOpt().value());
+ currentPathInfo.path.name(),
+ currentPathInfo.contentAddressWithReferenences().value());
if (dstStore.storeDir == srcStore.storeDir)
assert(storePathForDst == storePathForSrc);
if (storePathForDst != storePathForSrc)
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index d77aea338..2d252db84 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -216,7 +216,7 @@ public:
StorePath makeTextPath(std::string_view name, const TextInfo & info) const;
- StorePath makeFixedOutputPathFromCA(const StorePathDescriptor & info) const;
+ StorePath makeFixedOutputPathFromCA(std::string_view name, const ContentAddressWithReferences & ca) const;
/* This is the preparatory part of addToStore(); it computes the
store path to which srcPath is to be copied. Returns the store
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/local.mk b/src/libstore/tests/local.mk
index f74295d97..a2cf8a0cf 100644
--- a/src/libstore/tests/local.mk
+++ b/src/libstore/tests/local.mk
@@ -12,4 +12,4 @@ libstore-tests_CXXFLAGS += -I src/libstore -I src/libutil
libstore-tests_LIBS = libstore libutil
-libstore-tests_LDFLAGS := $(GTEST_LIBS)
+libstore-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS)
diff --git a/src/libstore/tests/outputs-spec.cc b/src/libstore/tests/outputs-spec.cc
index c9c2cafd0..06e4cabbd 100644
--- a/src/libstore/tests/outputs-spec.cc
+++ b/src/libstore/tests/outputs-spec.cc
@@ -40,6 +40,20 @@ TEST(OutputsSpec, names_out) {
ASSERT_EQ(expected.to_string(), str);
}
+TEST(OutputsSpec, names_underscore) {
+ std::string_view str = "a_b";
+ OutputsSpec expected = OutputsSpec::Names { "a_b" };
+ ASSERT_EQ(OutputsSpec::parse(str), expected);
+ 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)));
+}
+
+}