From c90198859ff0e9d5a1dead1907a93ba1e98af8a4 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 19 Jan 2024 21:11:56 -0500 Subject: [PATCH] `nix hash path`, and preperatory refactors - Proper `parse` and `render` functions for `FileIngestionMethod` and `ContentAddressMethod` Including unit tests! Older methods with same names that operate on on method + algo pair (for old-style `:algo`) are renamed to `*WithAlgo`.) - `nix store add` supports text hashing With functional test ensuring it matches `builtins.toFile`. - Factored-out flags for both - Move all common reusable flags to `libcmd` - They are not part of the *definition* of the CLI infra, just a usag of it. - The `libstore` flag couldn't go in `args.hh` in libutil anyways, would be awkward for it to live alone - Shuffle around `Cmd*` hierarchy so flags for deprecated commands don't end up on the new ones - Split `tests/functional/hash.sh` into `hash-path.sh` and `has-convert.sh` for performance and conceptual clarity. --- src/libcmd/misc-store-flags.cc | 121 ++++++++++++++ src/libcmd/misc-store-flags.hh | 21 +++ src/libstore/content-address.cc | 31 +++- src/libstore/content-address.hh | 22 ++- src/libstore/daemon.cc | 2 +- src/libstore/derivations.cc | 12 +- src/libstore/remote-store.cc | 2 +- src/libutil/args.cc | 67 -------- src/libutil/args.hh | 19 +-- src/libutil/file-content-address.cc | 25 +++ src/libutil/file-content-address.hh | 17 ++ src/nix/add-to-store.cc | 31 +--- src/nix/hash.cc | 131 +++++++++------ src/nix/prefetch.cc | 3 +- tests/functional/add.sh | 5 + tests/functional/hash-convert.sh | 105 ++++++++++++ tests/functional/hash-path.sh | 92 ++++++++++ tests/functional/hash.sh | 186 --------------------- tests/functional/local.mk | 3 +- tests/unit/libstore/content-address.cc | 35 ++++ tests/unit/libutil/file-content-address.cc | 33 ++++ 21 files changed, 600 insertions(+), 363 deletions(-) create mode 100644 src/libcmd/misc-store-flags.cc create mode 100644 src/libcmd/misc-store-flags.hh create mode 100644 tests/functional/hash-convert.sh create mode 100644 tests/functional/hash-path.sh delete mode 100644 tests/functional/hash.sh create mode 100644 tests/unit/libstore/content-address.cc create mode 100644 tests/unit/libutil/file-content-address.cc diff --git a/src/libcmd/misc-store-flags.cc b/src/libcmd/misc-store-flags.cc new file mode 100644 index 000000000000..1f494a1000ce --- /dev/null +++ b/src/libcmd/misc-store-flags.cc @@ -0,0 +1,121 @@ +#include "misc-store-flags.hh" + +namespace nix::flag +{ + +static void hashFormatCompleter(AddCompletions & completions, size_t index, std::string_view prefix) +{ + for (auto & format : hashFormats) { + if (hasPrefix(format, prefix)) { + completions.add(format); + } + } +} + +Args::Flag hashFormatWithDefault(std::string && longName, HashFormat * hf) +{ + assert(*hf == nix::HashFormat::SRI); + return Args::Flag { + .longName = std::move(longName), + .description = "Hash format (`base16`, `nix32`, `base64`, `sri`). Default: `sri`.", + .labels = {"hash-format"}, + .handler = {[hf](std::string s) { + *hf = parseHashFormat(s); + }}, + .completer = hashFormatCompleter, + }; +} + +Args::Flag hashFormatOpt(std::string && longName, std::optional * ohf) +{ + return Args::Flag { + .longName = std::move(longName), + .description = "Hash format (`base16`, `nix32`, `base64`, `sri`).", + .labels = {"hash-format"}, + .handler = {[ohf](std::string s) { + *ohf = std::optional{parseHashFormat(s)}; + }}, + .completer = hashFormatCompleter, + }; +} + +static void hashAlgoCompleter(AddCompletions & completions, size_t index, std::string_view prefix) +{ + for (auto & algo : hashAlgorithms) + if (hasPrefix(algo, prefix)) + completions.add(algo); +} + +Args::Flag hashAlgo(std::string && longName, HashAlgorithm * ha) +{ + return Args::Flag { + .longName = std::move(longName), + .description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`).", + .labels = {"hash-algo"}, + .handler = {[ha](std::string s) { + *ha = parseHashAlgo(s); + }}, + .completer = hashAlgoCompleter, + }; +} + +Args::Flag hashAlgoOpt(std::string && longName, std::optional * oha) +{ + return Args::Flag { + .longName = std::move(longName), + .description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`). Can be omitted for SRI hashes.", + .labels = {"hash-algo"}, + .handler = {[oha](std::string s) { + *oha = std::optional{parseHashAlgo(s)}; + }}, + .completer = hashAlgoCompleter, + }; +} + +Args::Flag fileIngestionMethod(FileIngestionMethod * method) +{ + return Args::Flag { + .longName = "mode", + // FIXME indentation carefully made for context, this is messed up. + .description = R"( + How to compute the hash of the input. + One of: + + - `nar` (the default): Serialises the input as an archive (following the [_Nix Archive Format_](https://edolstra.github.io/pubs/phd-thesis.pdf#page=101)) and passes that to the hash function. + + - `flat`: Assumes that the input is a single file and directly passes it to the hash function; + )", + .labels = {"file-ingestion-method"}, + .handler = {[method](std::string s) { + *method = parseFileIngestionMethod(s); + }}, + }; +} + +Args::Flag contentAddressMethod(ContentAddressMethod * method) +{ + return Args::Flag { + .longName = "mode", + // FIXME indentation carefully made for context, this is messed up. + .description = R"( + How to compute the content-address of the store object. + One of: + + - `nar` (the default): Serialises the input as an archive (following the [_Nix Archive Format_](https://edolstra.github.io/pubs/phd-thesis.pdf#page=101)) and passes that to the hash function. + + - `flat`: Assumes that the input is a single file and directly passes it to the hash function; + + - `text`: Like `flat`, but used for + [derivations](@docroot@/glossary.md#store-derivation) serialized in store object, + [`builtins.toFile`](@docroot@/language/builtins.html#builtins-toFile). + For advanced use-cases only; + for regular usage prefer `nar` and `flat. + )", + .labels = {"content-address-method"}, + .handler = {[method](std::string s) { + *method = ContentAddressMethod::parse(s); + }}, + }; +} + +} diff --git a/src/libcmd/misc-store-flags.hh b/src/libcmd/misc-store-flags.hh new file mode 100644 index 000000000000..124372af78cb --- /dev/null +++ b/src/libcmd/misc-store-flags.hh @@ -0,0 +1,21 @@ +#include "args.hh" +#include "content-address.hh" + +namespace nix::flag { + +Args::Flag hashAlgo(std::string && longName, HashAlgorithm * ha); +static inline Args::Flag hashAlgo(HashAlgorithm * ha) +{ + return hashAlgo("hash-algo", ha); +} +Args::Flag hashAlgoOpt(std::string && longName, std::optional * oha); +Args::Flag hashFormatWithDefault(std::string && longName, HashFormat * hf); +Args::Flag hashFormatOpt(std::string && longName, std::optional * ohf); +static inline Args::Flag hashAlgoOpt(std::optional * oha) +{ + return hashAlgoOpt("hash-algo", oha); +} +Args::Flag fileIngestionMethod(FileIngestionMethod * method); +Args::Flag contentAddressMethod(ContentAddressMethod * method); + +} diff --git a/src/libstore/content-address.cc b/src/libstore/content-address.cc index fc408f5afbdf..2091f8e0267b 100644 --- a/src/libstore/content-address.cc +++ b/src/libstore/content-address.cc @@ -4,7 +4,7 @@ namespace nix { -std::string makeFileIngestionPrefix(FileIngestionMethod m) +std::string_view makeFileIngestionPrefix(FileIngestionMethod m) { switch (m) { case FileIngestionMethod::Flat: @@ -16,10 +16,29 @@ std::string makeFileIngestionPrefix(FileIngestionMethod m) } } -std::string ContentAddressMethod::renderPrefix() const +std::string_view ContentAddressMethod::render() const { return std::visit(overloaded { - [](TextIngestionMethod) -> std::string { return "text:"; }, + [](TextIngestionMethod) -> std::string_view { return "text"; }, + [](FileIngestionMethod m2) { + /* Not prefixed for back compat with things that couldn't produce text before. */ + return renderFileIngestionMethod(m2); + }, + }, raw); +} + +ContentAddressMethod ContentAddressMethod::parse(std::string_view m) +{ + if (m == "text") + return TextIngestionMethod {}; + else + return parseFileIngestionMethod(m); +} + +std::string_view ContentAddressMethod::renderPrefix() const +{ + return std::visit(overloaded { + [](TextIngestionMethod) -> std::string_view { return "text:"; }, [](FileIngestionMethod m2) { /* Not prefixed for back compat with things that couldn't produce text before. */ return makeFileIngestionPrefix(m2); @@ -38,7 +57,7 @@ ContentAddressMethod ContentAddressMethod::parsePrefix(std::string_view & m) return FileIngestionMethod::Flat; } -std::string ContentAddressMethod::render(HashAlgorithm ha) const +std::string ContentAddressMethod::renderWithAlgo(HashAlgorithm ha) const { return std::visit(overloaded { [&](const TextIngestionMethod & th) { @@ -133,7 +152,7 @@ ContentAddress ContentAddress::parse(std::string_view rawCa) }; } -std::pair ContentAddressMethod::parse(std::string_view caMethod) +std::pair ContentAddressMethod::parseWithAlgo(std::string_view caMethod) { std::string asPrefix = std::string{caMethod} + ":"; // parseContentAddressMethodPrefix takes its argument by reference @@ -155,7 +174,7 @@ std::string renderContentAddress(std::optional ca) std::string ContentAddress::printMethodAlgo() const { - return method.renderPrefix() + return std::string { method.renderPrefix() } + printHashAlgo(hash.algo); } diff --git a/src/libstore/content-address.hh b/src/libstore/content-address.hh index f0973412b7b3..80538df50183 100644 --- a/src/libstore/content-address.hh +++ b/src/libstore/content-address.hh @@ -36,7 +36,7 @@ struct TextIngestionMethod : std::monostate { }; * Compute the prefix to the hash algorithm which indicates how the * files were ingested. */ -std::string makeFileIngestionPrefix(FileIngestionMethod m); +std::string_view makeFileIngestionPrefix(FileIngestionMethod m); /** * An enumeration of all the ways we can content-address store objects. @@ -59,6 +59,20 @@ struct ContentAddressMethod MAKE_WRAPPER_CONSTRUCTOR(ContentAddressMethod); + /** + * Parse a content addressing method (name). + * + * The inverse of `render`. + */ + static ContentAddressMethod parse(std::string_view rawCaMethod); + + /** + * Render a content addressing method (name). + * + * The inverse of `parse`. + */ + std::string_view render() const; + /** * Parse the prefix tag which indicates how the files * were ingested, with the fixed output case not prefixed for back @@ -74,12 +88,12 @@ struct ContentAddressMethod * * The rough inverse of `parsePrefix()`. */ - std::string renderPrefix() const; + std::string_view renderPrefix() const; /** * Parse a content addressing method and hash type. */ - static std::pair parse(std::string_view rawCaMethod); + static std::pair parseWithAlgo(std::string_view rawCaMethod); /** * Render a content addressing method and hash type in a @@ -87,7 +101,7 @@ struct ContentAddressMethod * * The rough inverse of `parse()`. */ - std::string render(HashAlgorithm ht) const; + std::string renderWithAlgo(HashAlgorithm ht) const; /** * Get the underlying way to content-address file system objects. diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc index 27ad14ed4508..dd3a6fda00e6 100644 --- a/src/libstore/daemon.cc +++ b/src/libstore/daemon.cc @@ -400,7 +400,7 @@ static void performOp(TunnelLogger * logger, ref store, logger->startWork(); auto pathInfo = [&]() { // NB: FramedSource must be out of scope before logger->stopWork(); - auto [contentAddressMethod, hashAlgo_] = ContentAddressMethod::parse(camStr); + auto [contentAddressMethod, hashAlgo_] = ContentAddressMethod::parseWithAlgo(camStr); auto hashAlgo = hashAlgo_; // work around clang bug FramedSource source(from); // TODO these two steps are essentially RemoteStore::addCAToStore. Move it up to Store. diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 39380665233c..36042c06c393 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -601,7 +601,7 @@ std::string Derivation::unparse(const StoreDirConfig & store, bool maskOutputs, }, [&](const DerivationOutput::CAFloating & dof) { s += ','; printUnquotedString(s, ""); - s += ','; printUnquotedString(s, dof.method.renderPrefix() + printHashAlgo(dof.hashAlgo)); + s += ','; printUnquotedString(s, std::string { dof.method.renderPrefix() } + printHashAlgo(dof.hashAlgo)); s += ','; printUnquotedString(s, ""); }, [&](const DerivationOutput::Deferred &) { @@ -612,7 +612,7 @@ std::string Derivation::unparse(const StoreDirConfig & store, bool maskOutputs, [&](const DerivationOutput::Impure & doi) { // FIXME s += ','; printUnquotedString(s, ""); - s += ','; printUnquotedString(s, doi.method.renderPrefix() + printHashAlgo(doi.hashAlgo)); + s += ','; printUnquotedString(s, std::string { doi.method.renderPrefix() } + printHashAlgo(doi.hashAlgo)); s += ','; printUnquotedString(s, "impure"); } }, i.second.raw); @@ -984,7 +984,7 @@ void writeDerivation(Sink & out, const StoreDirConfig & store, const BasicDeriva }, [&](const DerivationOutput::CAFloating & dof) { out << "" - << (dof.method.renderPrefix() + printHashAlgo(dof.hashAlgo)) + << (std::string { dof.method.renderPrefix() } + printHashAlgo(dof.hashAlgo)) << ""; }, [&](const DerivationOutput::Deferred &) { @@ -994,7 +994,7 @@ void writeDerivation(Sink & out, const StoreDirConfig & store, const BasicDeriva }, [&](const DerivationOutput::Impure & doi) { out << "" - << (doi.method.renderPrefix() + printHashAlgo(doi.hashAlgo)) + << (std::string { doi.method.renderPrefix() } + printHashAlgo(doi.hashAlgo)) << "impure"; }, }, i.second.raw); @@ -1221,11 +1221,11 @@ nlohmann::json DerivationOutput::toJSON( // FIXME print refs? }, [&](const DerivationOutput::CAFloating & dof) { - res["hashAlgo"] = dof.method.renderPrefix() + printHashAlgo(dof.hashAlgo); + res["hashAlgo"] = std::string { dof.method.renderPrefix() } + printHashAlgo(dof.hashAlgo); }, [&](const DerivationOutput::Deferred &) {}, [&](const DerivationOutput::Impure & doi) { - res["hashAlgo"] = doi.method.renderPrefix() + printHashAlgo(doi.hashAlgo); + res["hashAlgo"] = std::string { doi.method.renderPrefix() } + printHashAlgo(doi.hashAlgo); res["impure"] = true; }, }, raw); diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index ccf95beefbfd..fadef45ffcca 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -435,7 +435,7 @@ ref RemoteStore::addCAToStore( conn->to << WorkerProto::Op::AddToStore << name - << caMethod.render(hashAlgo); + << caMethod.renderWithAlgo(hashAlgo); WorkerProto::write(*this, *conn, references); conn->to << repair; diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 8996cbe5b00a..a981ed9fb696 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -544,73 +544,6 @@ nlohmann::json Args::toJSON() return res; } -static void hashFormatCompleter(AddCompletions & completions, size_t index, std::string_view prefix) -{ - for (auto & format : hashFormats) { - if (hasPrefix(format, prefix)) { - completions.add(format); - } - } -} - -Args::Flag Args::Flag::mkHashFormatFlagWithDefault(std::string &&longName, HashFormat * hf) { - assert(*hf == nix::HashFormat::SRI); - return Flag{ - .longName = std::move(longName), - .description = "Hash format (`base16`, `nix32`, `base64`, `sri`). Default: `sri`.", - .labels = {"hash-format"}, - .handler = {[hf](std::string s) { - *hf = parseHashFormat(s); - }}, - .completer = hashFormatCompleter, - }; -} - -Args::Flag Args::Flag::mkHashFormatOptFlag(std::string && longName, std::optional * ohf) { - return Flag{ - .longName = std::move(longName), - .description = "Hash format (`base16`, `nix32`, `base64`, `sri`).", - .labels = {"hash-format"}, - .handler = {[ohf](std::string s) { - *ohf = std::optional{parseHashFormat(s)}; - }}, - .completer = hashFormatCompleter, - }; -} - -static void hashAlgoCompleter(AddCompletions & completions, size_t index, std::string_view prefix) -{ - for (auto & algo : hashAlgorithms) - if (hasPrefix(algo, prefix)) - completions.add(algo); -} - -Args::Flag Args::Flag::mkHashAlgoFlag(std::string && longName, HashAlgorithm * ha) -{ - return Flag{ - .longName = std::move(longName), - .description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`).", - .labels = {"hash-algo"}, - .handler = {[ha](std::string s) { - *ha = parseHashAlgo(s); - }}, - .completer = hashAlgoCompleter, - }; -} - -Args::Flag Args::Flag::mkHashAlgoOptFlag(std::string && longName, std::optional * oha) -{ - return Flag{ - .longName = std::move(longName), - .description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`). Can be omitted for SRI hashes.", - .labels = {"hash-algo"}, - .handler = {[oha](std::string s) { - *oha = std::optional{parseHashAlgo(s)}; - }}, - .completer = hashAlgoCompleter, - }; -} - static void _completePath(AddCompletions & completions, std::string_view prefix, bool onlyDirs) { completions.setType(Completions::Type::Filenames); diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 6c9c48065a4a..4b2e1d96055a 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -155,6 +155,8 @@ protected: */ using CompleterClosure = std::function; +public: + /** * Description of flags / options * @@ -175,19 +177,10 @@ protected: CompleterClosure completer; std::optional experimentalFeature; - - static Flag mkHashAlgoFlag(std::string && longName, HashAlgorithm * ha); - static Flag mkHashAlgoFlag(HashAlgorithm * ha) { - return mkHashAlgoFlag("hash-algo", ha); - } - static Flag mkHashAlgoOptFlag(std::string && longName, std::optional * oha); - static Flag mkHashAlgoOptFlag(std::optional * oha) { - return mkHashAlgoOptFlag("hash-algo", oha); - } - static Flag mkHashFormatFlagWithDefault(std::string && longName, HashFormat * hf); - static Flag mkHashFormatOptFlag(std::string && longName, std::optional * ohf); }; +protected: + /** * Index of all registered "long" flag descriptions (flags like * `--long`). @@ -206,6 +199,8 @@ protected: */ virtual bool processFlag(Strings::iterator & pos, Strings::iterator end); +public: + /** * Description of positional arguments * @@ -220,6 +215,8 @@ protected: CompleterClosure completer; }; +protected: + /** * Queue of expected positional argument forms. * diff --git a/src/libutil/file-content-address.cc b/src/libutil/file-content-address.cc index 9917986f6bd6..6ea7b2ab4a66 100644 --- a/src/libutil/file-content-address.cc +++ b/src/libutil/file-content-address.cc @@ -3,6 +3,31 @@ namespace nix { +FileIngestionMethod parseFileIngestionMethod(std::string_view input) +{ + if (input == "flat") { + return FileIngestionMethod::Flat; + } else if (input == "nar") { + return FileIngestionMethod::Recursive; + } else { + throw UsageError("Unknown file ingestion method '%s', expect `flat` or `nar`"); + } +} + + +std::string_view renderFileIngestionMethod(FileIngestionMethod method) +{ + switch (method) { + case FileIngestionMethod::Flat: + return "flat"; + case FileIngestionMethod::Recursive: + return "nar"; + default: + assert(false); + } +} + + void dumpPath( SourceAccessor & accessor, const CanonPath & path, Sink & sink, diff --git a/src/libutil/file-content-address.hh b/src/libutil/file-content-address.hh index 7f7544e41451..41f23f2af169 100644 --- a/src/libutil/file-content-address.hh +++ b/src/libutil/file-content-address.hh @@ -23,6 +23,23 @@ enum struct FileIngestionMethod : uint8_t { Recursive = 1, }; +/** + * Parse a `FileIngestionMethod` by name. Choice of: + * + * - `flat`: `FileIngestionMethod::Flat` + * - `nar`: `FileIngestionMethod::Recursive` + * + * Oppostite of `renderFileIngestionMethod`. + */ +FileIngestionMethod parseFileIngestionMethod(std::string_view input); + +/** + * Render a `FileIngestionMethod` by name. + * + * Oppostite of `parseFileIngestionMethod`. + */ +std::string_view renderFileIngestionMethod(FileIngestionMethod method); + /** * Dump a serialization of the given file system object. */ diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc index 7c534517d25b..9b408ac4b70c 100644 --- a/src/nix/add-to-store.cc +++ b/src/nix/add-to-store.cc @@ -3,20 +3,10 @@ #include "store-api.hh" #include "archive.hh" #include "posix-source-accessor.hh" +#include "misc-store-flags.hh" using namespace nix; -static FileIngestionMethod parseIngestionMethod(std::string_view input) -{ - if (input == "flat") { - return FileIngestionMethod::Flat; - } else if (input == "nar") { - return FileIngestionMethod::Recursive; - } else { - throw UsageError("Unknown hash mode '%s', expect `flat` or `nar`"); - } -} - struct CmdAddToStore : MixDryRun, StoreCommand { Path path; @@ -37,23 +27,9 @@ struct CmdAddToStore : MixDryRun, StoreCommand .handler = {&namePart}, }); - addFlag({ - .longName = "mode", - .description = R"( - How to compute the hash of the input. - One of: - - - `nar` (the default): Serialises the input as an archive (following the [_Nix Archive Format_](https://edolstra.github.io/pubs/phd-thesis.pdf#page=101)) and passes that to the hash function. - - - `flat`: Assumes that the input is a single file and directly passes it to the hash function; - )", - .labels = {"hash-mode"}, - .handler = {[this](std::string s) { - this->caMethod = parseIngestionMethod(s); - }}, - }); + addFlag(flag::contentAddressMethod(&caMethod)); - addFlag(Flag::mkHashAlgoFlag(&hashAlgo)); + addFlag(flag::hashAlgo(&hashAlgo)); } void run(ref store) override @@ -76,7 +52,6 @@ struct CmdAddToStore : MixDryRun, StoreCommand struct CmdAdd : CmdAddToStore { - std::string description() override { return "Add a file or directory to the Nix store"; diff --git a/src/nix/hash.cc b/src/nix/hash.cc index 4837891c64ef..684dd3e24339 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -6,6 +6,7 @@ #include "references.hh" #include "archive.hh" #include "posix-source-accessor.hh" +#include "misc-store-flags.hh" using namespace nix; @@ -19,47 +20,12 @@ struct CmdHashBase : Command FileIngestionMethod mode; HashFormat hashFormat = HashFormat::SRI; bool truncate = false; - HashAlgorithm ha = HashAlgorithm::SHA256; + HashAlgorithm hashAlgo = HashAlgorithm::SHA256; std::vector paths; std::optional modulus; explicit CmdHashBase(FileIngestionMethod mode) : mode(mode) { - addFlag({ - .longName = "sri", - .description = "Print the hash in SRI format.", - .handler = {&hashFormat, HashFormat::SRI}, - }); - - addFlag({ - .longName = "base64", - .description = "Print the hash in base-64 format.", - .handler = {&hashFormat, HashFormat::Base64}, - }); - - addFlag({ - .longName = "base32", - .description = "Print the hash in base-32 (Nix-specific) format.", - .handler = {&hashFormat, HashFormat::Nix32}, - }); - - addFlag({ - .longName = "base16", - .description = "Print the hash in base-16 format.", - .handler = {&hashFormat, HashFormat::Base16}, - }); - - addFlag(Flag::mkHashAlgoFlag("type", &ha)); - - #if 0 - addFlag({ - .longName = "modulo", - .description = "Compute the hash modulo the specified string.", - .labels = {"modulus"}, - .handler = {&modulus}, - }); - #endif\ - expectArgs({ .label = "paths", .handler = {&paths}, @@ -85,9 +51,9 @@ struct CmdHashBase : Command std::unique_ptr hashSink; if (modulus) - hashSink = std::make_unique(ha, *modulus); + hashSink = std::make_unique(hashAlgo, *modulus); else - hashSink = std::make_unique(ha); + hashSink = std::make_unique(hashAlgo); PosixSourceAccessor accessor; dumpPath(accessor, CanonPath::fromCwd(path), *hashSink, mode); @@ -99,15 +65,78 @@ struct CmdHashBase : Command } }; +/** + * `nix hash path` + */ +struct CmdHashPath : CmdHashBase +{ + CmdHashPath() + : CmdHashBase(FileIngestionMethod::Recursive) + { + addFlag(flag::hashAlgo("algo", &hashAlgo)); + addFlag(flag::fileIngestionMethod(&mode)); + addFlag(flag::hashFormatWithDefault("format", &hashFormat)); + #if 0 + addFlag({ + .longName = "modulo", + .description = "Compute the hash modulo the specified string.", + .labels = {"modulus"}, + .handler = {&modulus}, + }); + #endif + } +}; + +/** + * For deprecated `nix hash file` + * + * Deprecation Issue: https://github.com/NixOS/nix/issues/8876 + */ +struct CmdHashFile : CmdHashBase +{ + CmdHashFile() + : CmdHashBase(FileIngestionMethod::Flat) + { + addFlag({ + .longName = "sri", + .description = "Print the hash in SRI format.", + .handler = {&hashFormat, HashFormat::SRI}, + }); + + addFlag({ + .longName = "base64", + .description = "Print the hash in base-64 format.", + .handler = {&hashFormat, HashFormat::Base64}, + }); + + addFlag({ + .longName = "base32", + .description = "Print the hash in base-32 (Nix-specific) format.", + .handler = {&hashFormat, HashFormat::Nix32}, + }); + + addFlag({ + .longName = "base16", + .description = "Print the hash in base-16 format.", + .handler = {&hashFormat, HashFormat::Base16}, + }); + + addFlag(flag::hashAlgo("type", &hashAlgo)); + } +}; + +/** + * For deprecated `nix hash to-*` + */ struct CmdToBase : Command { HashFormat hashFormat; - std::optional ht; + std::optional hashAlgo; std::vector args; CmdToBase(HashFormat hashFormat) : hashFormat(hashFormat) { - addFlag(Flag::mkHashAlgoOptFlag("type", &ht)); + addFlag(flag::hashAlgoOpt("type", &hashAlgo)); expectArgs("strings", &args); } @@ -124,7 +153,7 @@ struct CmdToBase : Command { warn("The old format conversion sub commands of `nix hash` where deprecated in favor of `nix hash convert`."); for (auto s : args) - logger->cout(Hash::parseAny(s, ht).to_string(hashFormat, hashFormat == HashFormat::SRI)); + logger->cout(Hash::parseAny(s, hashAlgo).to_string(hashFormat, hashFormat == HashFormat::SRI)); } }; @@ -139,9 +168,9 @@ struct CmdHashConvert : Command std::vector hashStrings; CmdHashConvert(): to(HashFormat::SRI) { - addFlag(Args::Flag::mkHashFormatOptFlag("from", &from)); - addFlag(Args::Flag::mkHashFormatFlagWithDefault("to", &to)); - addFlag(Args::Flag::mkHashAlgoOptFlag(&algo)); + addFlag(flag::hashFormatOpt("from", &from)); + addFlag(flag::hashFormatWithDefault("to", &to)); + addFlag(flag::hashAlgoOpt(&algo)); expectArgs({ .label = "hashes", .handler = {&hashStrings}, @@ -181,8 +210,8 @@ struct CmdHash : NixMultiCommand "hash", { {"convert", []() { return make_ref();}}, - {"file", []() { return make_ref(FileIngestionMethod::Flat);; }}, - {"path", []() { return make_ref(FileIngestionMethod::Recursive); }}, + {"path", []() { return make_ref(); }}, + {"file", []() { return make_ref(); }}, {"to-base16", []() { return make_ref(HashFormat::Base16); }}, {"to-base32", []() { return make_ref(HashFormat::Nix32); }}, {"to-base64", []() { return make_ref(HashFormat::Base64); }}, @@ -206,7 +235,7 @@ static int compatNixHash(int argc, char * * argv) // Wait until `nix hash convert` is not hidden behind experimental flags anymore. // warn("`nix-hash` has been deprecated in favor of `nix hash convert`."); - std::optional ha; + std::optional hashAlgo; bool flat = false; HashFormat hashFormat = HashFormat::Base16; bool truncate = false; @@ -226,7 +255,7 @@ static int compatNixHash(int argc, char * * argv) else if (*arg == "--truncate") truncate = true; else if (*arg == "--type") { std::string s = getArg(*arg, arg, end); - ha = parseHashAlgo(s); + hashAlgo = parseHashAlgo(s); } else if (*arg == "--to-base16") { op = opTo; @@ -253,8 +282,8 @@ static int compatNixHash(int argc, char * * argv) if (op == opHash) { CmdHashBase cmd(flat ? FileIngestionMethod::Flat : FileIngestionMethod::Recursive); - if (!ha.has_value()) ha = HashAlgorithm::MD5; - cmd.ha = ha.value(); + if (!hashAlgo.has_value()) hashAlgo = HashAlgorithm::MD5; + cmd.hashAlgo = hashAlgo.value(); cmd.hashFormat = hashFormat; cmd.truncate = truncate; cmd.paths = ss; @@ -264,7 +293,7 @@ static int compatNixHash(int argc, char * * argv) else { CmdToBase cmd(hashFormat); cmd.args = ss; - if (ha.has_value()) cmd.ht = ha; + if (hashAlgo.has_value()) cmd.hashAlgo = hashAlgo; cmd.run(); } diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc index 84b79ea282e3..d9e97dc24461 100644 --- a/src/nix/prefetch.cc +++ b/src/nix/prefetch.cc @@ -10,6 +10,7 @@ #include "eval-inline.hh" #include "legacy.hh" #include "posix-source-accessor.hh" +#include "misc-store-flags.hh" #include @@ -285,7 +286,7 @@ struct CmdStorePrefetchFile : StoreCommand, MixJSON }} }); - addFlag(Flag::mkHashAlgoFlag("hash-type", &hashAlgo)); + addFlag(flag::hashAlgo("hash-type", &hashAlgo)); addFlag({ .longName = "executable", diff --git a/tests/functional/add.sh b/tests/functional/add.sh index 762e01dbe379..a4bb0e225abc 100644 --- a/tests/functional/add.sh +++ b/tests/functional/add.sh @@ -45,3 +45,8 @@ clearStore [[ "$path1" == "$path2" ]] path4=$(nix store add --mode flat --hash-algo sha1 ./dummy) ) +( + path1=$(nix store add --mode text ./dummy) + path2=$(nix eval --impure --raw --expr 'builtins.toFile "dummy" (builtins.readFile ./dummy)') + [[ "$path1" == "$path2" ]] +) diff --git a/tests/functional/hash-convert.sh b/tests/functional/hash-convert.sh new file mode 100644 index 000000000000..9b3afc10b27b --- /dev/null +++ b/tests/functional/hash-convert.sh @@ -0,0 +1,105 @@ +source common.sh + +# Conversion with `nix hash` `nix-hash` and `nix hash convert` +try3() { + # $1 = hash algo + # $2 = expected hash in base16 + # $3 = expected hash in base32 + # $4 = expected hash in base64 + h64=$(nix hash convert --hash-algo "$1" --to base64 "$2") + [ "$h64" = "$4" ] + h64=$(nix-hash --type "$1" --to-base64 "$2") + [ "$h64" = "$4" ] + # Deprecated experiment + h64=$(nix hash to-base64 --type "$1" "$2") + [ "$h64" = "$4" ] + + sri=$(nix hash convert --hash-algo "$1" --to sri "$2") + [ "$sri" = "$1-$4" ] + sri=$(nix-hash --type "$1" --to-sri "$2") + [ "$sri" = "$1-$4" ] + sri=$(nix hash to-sri --type "$1" "$2") + [ "$sri" = "$1-$4" ] + h32=$(nix hash convert --hash-algo "$1" --to base32 "$2") + [ "$h32" = "$3" ] + h32=$(nix-hash --type "$1" --to-base32 "$2") + [ "$h32" = "$3" ] + h32=$(nix hash to-base32 --type "$1" "$2") + [ "$h32" = "$3" ] + h16=$(nix-hash --type "$1" --to-base16 "$h32") + [ "$h16" = "$2" ] + + h16=$(nix hash convert --hash-algo "$1" --to base16 "$h64") + [ "$h16" = "$2" ] + h16=$(nix hash to-base16 --type "$1" "$h64") + [ "$h16" = "$2" ] + h16=$(nix hash convert --to base16 "$sri") + [ "$h16" = "$2" ] + h16=$(nix hash to-base16 "$sri") + [ "$h16" = "$2" ] + + # + # Converting from SRI + # + + # Input hash algo auto-detected from SRI and output defaults to SRI as well. + sri=$(nix hash convert "$1-$4") + [ "$sri" = "$1-$4" ] + + sri=$(nix hash convert --from sri "$1-$4") + [ "$sri" = "$1-$4" ] + + sri=$(nix hash convert --to sri "$1-$4") + [ "$sri" = "$1-$4" ] + + sri=$(nix hash convert --from sri --to sri "$1-$4") + [ "$sri" = "$1-$4" ] + + sri=$(nix hash convert --to base64 "$1-$4") + [ "$sri" = "$4" ] + + # + # Auto-detecting the input from algo and length. + # + + sri=$(nix hash convert --hash-algo "$1" "$2") + [ "$sri" = "$1-$4" ] + sri=$(nix hash convert --hash-algo "$1" "$3") + [ "$sri" = "$1-$4" ] + sri=$(nix hash convert --hash-algo "$1" "$4") + [ "$sri" = "$1-$4" ] + + sri=$(nix hash convert --hash-algo "$1" "$2") + [ "$sri" = "$1-$4" ] + sri=$(nix hash convert --hash-algo "$1" "$3") + [ "$sri" = "$1-$4" ] + sri=$(nix hash convert --hash-algo "$1" "$4") + [ "$sri" = "$1-$4" ] + + # + # Asserting input format succeeds. + # + + sri=$(nix hash convert --hash-algo "$1" --from base16 "$2") + [ "$sri" = "$1-$4" ] + sri=$(nix hash convert --hash-algo "$1" --from nix32 "$3") + [ "$sri" = "$1-$4" ] + sri=$(nix hash convert --hash-algo "$1" --from base64 "$4") + [ "$sri" = "$1-$4" ] + + # + # Asserting input format fails. + # + + fail=$(nix hash convert --hash-algo "$1" --from nix32 "$2" 2>&1 || echo "exit: $?") + [[ "$fail" == *"error: input hash"*"exit: 1" ]] + fail=$(nix hash convert --hash-algo "$1" --from base16 "$3" 2>&1 || echo "exit: $?") + [[ "$fail" == *"error: input hash"*"exit: 1" ]] + fail=$(nix hash convert --hash-algo "$1" --from nix32 "$4" 2>&1 || echo "exit: $?") + [[ "$fail" == *"error: input hash"*"exit: 1" ]] + +} + +try3 sha1 "800d59cfcd3c05e900cb4e214be48f6b886a08df" "vw46m23bizj4n8afrc0fj19wrp7mj3c0" "gA1Zz808BekAy04hS+SPa4hqCN8=" +try3 sha256 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" "1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s" "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=" +try3 sha512 "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445" "12k9jiq29iyqm03swfsgiw5mlqs173qazm3n7daz43infy12pyrcdf30fkk3qwv4yl2ick8yipc2mqnlh48xsvvxl60lbx8vp38yji0" "IEqPxt2oLwoM7XvrjgikFlfBbvRosiioJ5vjMacDwzWW/RXBOxsH+aodO+pXeJygMa2Fx6cd1wNU7GMSOMo0RQ==" diff --git a/tests/functional/hash-path.sh b/tests/functional/hash-path.sh new file mode 100644 index 000000000000..4ad9f8ff2ec3 --- /dev/null +++ b/tests/functional/hash-path.sh @@ -0,0 +1,92 @@ +source common.sh + +try () { + printf "%s" "$2" > $TEST_ROOT/vector + hash="$(nix-hash --flat ${FORMAT+--$FORMAT} --type "$1" "$TEST_ROOT/vector")" + if ! (( "${NO_TEST_CLASSIC-}" )) && test "$hash" != "$3"; then + echo "try nix-hash: hash $1, expected $3, got $hash" + exit 1 + fi + hash="$(nix hash file ${FORMAT+--$FORMAT} --type "$1" "$TEST_ROOT/vector")" + if ! (( "${NO_TEST_NIX_COMMAND-}" )) && test "$hash" != "$3"; then + echo "try nix hash: hash $1, expected $3, got $hash" + exit 1 + fi + hash="$(nix hash path --mode flat ${FORMAT+--format $FORMAT} --algo "$1" "$TEST_ROOT/vector")" + if ! (( "${NO_TEST_NIX_COMMAND-}" )) && test "$hash" != "$3"; then + echo "try nix hash: hash $1, expected $3, got $hash" + exit 1 + fi +} + +FORMAT=base16 +try md5 "" "d41d8cd98f00b204e9800998ecf8427e" +try md5 "a" "0cc175b9c0f1b6a831c399e269772661" +try md5 "abc" "900150983cd24fb0d6963f7d28e17f72" +try md5 "message digest" "f96b697d7cb7938d525a2f31aaf161d0" +try md5 "abcdefghijklmnopqrstuvwxyz" "c3fcd3d76192e4007dfb496cca67e13b" +try md5 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" "d174ab98d277d9f5a5611c2c9f419d9f" +try md5 "12345678901234567890123456789012345678901234567890123456789012345678901234567890" "57edf4a22be3c955ac49da2e2107b67a" + +try sha1 "" "da39a3ee5e6b4b0d3255bfef95601890afd80709" +try sha1 "abc" "a9993e364706816aba3e25717850c26c9cd0d89d" +try sha1 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "84983e441c3bd26ebaae4aa1f95129e5e54670f1" + +try sha256 "" "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +try sha256 "abc" "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" +try sha256 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" + +try sha512 "" "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" +try sha512 "abc" "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" +try sha512 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445" +unset FORMAT + +FORMAT=base32 +try sha256 "abc" "1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s" +unset FORMAT + +FORMAT=sri +try sha512 "" "sha512-z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==" +try sha512 "abc" "sha512-3a81oZNherrMQXNJriBBMRLm+k6JqX6iCp7u5ktV05ohkpkqJ0/BqDa6PCOj/uu9RU1EI2Q86A4qmslPpUyknw==" +try sha512 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "sha512-IEqPxt2oLwoM7XvrjgikFlfBbvRosiioJ5vjMacDwzWW/RXBOxsH+aodO+pXeJygMa2Fx6cd1wNU7GMSOMo0RQ==" +try sha256 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "sha256-JI1qYdIGOLjlwCaTDD5gOaM85Flk/yFn9uzt1BnbBsE=" +unset FORMAT + +# nix-hash [--flat] defaults to the Base16 format +NO_TEST_NIX_COMMAND=1 try sha512 "abc" "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" + +# nix hash [file|path] defaults to the SRI format +NO_TEST_CLASSIC=1 try sha512 "abc" "sha512-3a81oZNherrMQXNJriBBMRLm+k6JqX6iCp7u5ktV05ohkpkqJ0/BqDa6PCOj/uu9RU1EI2Q86A4qmslPpUyknw==" + +try2 () { + hash=$(nix-hash --type "$1" $TEST_ROOT/hash-path) + if test "$hash" != "$2"; then + echo "try nix-hash; hash $1, expected $2, got $hash" + exit 1 + fi + hash="$(nix hash path --mode nar --format base16 --algo "$1" "$TEST_ROOT/hash-path")" + if test "$hash" != "$2"; then + echo "try nix hash: hash $1, expected $2, got $hash" + exit 1 + fi +} + +rm -rf $TEST_ROOT/hash-path +mkdir $TEST_ROOT/hash-path +echo "Hello World" > $TEST_ROOT/hash-path/hello + +try2 md5 "ea9b55537dd4c7e104515b2ccfaf4100" + +# Execute bit matters. +chmod +x $TEST_ROOT/hash-path/hello +try2 md5 "20f3ffe011d4cfa7d72bfabef7882836" + +# Mtime and other bits don't. +touch -r . $TEST_ROOT/hash-path/hello +chmod 744 $TEST_ROOT/hash-path/hello +try2 md5 "20f3ffe011d4cfa7d72bfabef7882836" + +# File type (e.g., symlink) does. +rm $TEST_ROOT/hash-path/hello +ln -s x $TEST_ROOT/hash-path/hello +try2 md5 "f78b733a68f5edbdf9413899339eaa4a" diff --git a/tests/functional/hash.sh b/tests/functional/hash.sh deleted file mode 100644 index ff270076e7dd..000000000000 --- a/tests/functional/hash.sh +++ /dev/null @@ -1,186 +0,0 @@ -source common.sh - -try () { - printf "%s" "$2" > $TEST_ROOT/vector - hash="$(nix-hash --flat ${FORMAT_FLAG-} --type "$1" "$TEST_ROOT/vector")" - if ! (( "${NO_TEST_CLASSIC-}" )) && test "$hash" != "$3"; then - echo "try nix-hash: hash $1, expected $3, got $hash" - exit 1 - fi - hash="$(nix hash file ${FORMAT_FLAG-} --type "$1" "$TEST_ROOT/vector")" - if ! (( "${NO_TEST_NIX_COMMAND-}" )) && test "$hash" != "$3"; then - echo "try nix hash: hash $1, expected $3, got $hash" - exit 1 - fi -} - -FORMAT_FLAG=--base16 -try md5 "" "d41d8cd98f00b204e9800998ecf8427e" -try md5 "a" "0cc175b9c0f1b6a831c399e269772661" -try md5 "abc" "900150983cd24fb0d6963f7d28e17f72" -try md5 "message digest" "f96b697d7cb7938d525a2f31aaf161d0" -try md5 "abcdefghijklmnopqrstuvwxyz" "c3fcd3d76192e4007dfb496cca67e13b" -try md5 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" "d174ab98d277d9f5a5611c2c9f419d9f" -try md5 "12345678901234567890123456789012345678901234567890123456789012345678901234567890" "57edf4a22be3c955ac49da2e2107b67a" - -try sha1 "" "da39a3ee5e6b4b0d3255bfef95601890afd80709" -try sha1 "abc" "a9993e364706816aba3e25717850c26c9cd0d89d" -try sha1 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "84983e441c3bd26ebaae4aa1f95129e5e54670f1" - -try sha256 "" "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" -try sha256 "abc" "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" -try sha256 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" - -try sha512 "" "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" -try sha512 "abc" "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" -try sha512 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445" -unset FORMAT_FLAG - -FORMAT_FLAG=--base32 -try sha256 "abc" "1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s" -unset FORMAT_FLAG - -FORMAT_FLAG=--sri -try sha512 "" "sha512-z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==" -try sha512 "abc" "sha512-3a81oZNherrMQXNJriBBMRLm+k6JqX6iCp7u5ktV05ohkpkqJ0/BqDa6PCOj/uu9RU1EI2Q86A4qmslPpUyknw==" -try sha512 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "sha512-IEqPxt2oLwoM7XvrjgikFlfBbvRosiioJ5vjMacDwzWW/RXBOxsH+aodO+pXeJygMa2Fx6cd1wNU7GMSOMo0RQ==" -try sha256 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "sha256-JI1qYdIGOLjlwCaTDD5gOaM85Flk/yFn9uzt1BnbBsE=" -unset FORMAT_FLAG - -# nix-hash [--flat] defaults to the Base16 format -NO_TEST_NIX_COMMAND=1 try sha512 "abc" "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" - -# nix hash [file|path] defaults to the SRI format -NO_TEST_CLASSIC=1 try sha512 "abc" "sha512-3a81oZNherrMQXNJriBBMRLm+k6JqX6iCp7u5ktV05ohkpkqJ0/BqDa6PCOj/uu9RU1EI2Q86A4qmslPpUyknw==" - -try2 () { - hash=$(nix-hash --type "$1" $TEST_ROOT/hash-path) - if test "$hash" != "$2"; then - echo "hash $1, expected $2, got $hash" - exit 1 - fi -} - -rm -rf $TEST_ROOT/hash-path -mkdir $TEST_ROOT/hash-path -echo "Hello World" > $TEST_ROOT/hash-path/hello - -try2 md5 "ea9b55537dd4c7e104515b2ccfaf4100" - -# Execute bit matters. -chmod +x $TEST_ROOT/hash-path/hello -try2 md5 "20f3ffe011d4cfa7d72bfabef7882836" - -# Mtime and other bits don't. -touch -r . $TEST_ROOT/hash-path/hello -chmod 744 $TEST_ROOT/hash-path/hello -try2 md5 "20f3ffe011d4cfa7d72bfabef7882836" - -# File type (e.g., symlink) does. -rm $TEST_ROOT/hash-path/hello -ln -s x $TEST_ROOT/hash-path/hello -try2 md5 "f78b733a68f5edbdf9413899339eaa4a" - -# Conversion with `nix hash` `nix-hash` and `nix hash convert` -try3() { - # $1 = hash algo - # $2 = expected hash in base16 - # $3 = expected hash in base32 - # $4 = expected hash in base64 - h64=$(nix hash convert --hash-algo "$1" --to base64 "$2") - [ "$h64" = "$4" ] - h64=$(nix-hash --type "$1" --to-base64 "$2") - [ "$h64" = "$4" ] - # Deprecated experiment - h64=$(nix hash to-base64 --type "$1" "$2") - [ "$h64" = "$4" ] - - sri=$(nix hash convert --hash-algo "$1" --to sri "$2") - [ "$sri" = "$1-$4" ] - sri=$(nix-hash --type "$1" --to-sri "$2") - [ "$sri" = "$1-$4" ] - sri=$(nix hash to-sri --type "$1" "$2") - [ "$sri" = "$1-$4" ] - h32=$(nix hash convert --hash-algo "$1" --to base32 "$2") - [ "$h32" = "$3" ] - h32=$(nix-hash --type "$1" --to-base32 "$2") - [ "$h32" = "$3" ] - h32=$(nix hash to-base32 --type "$1" "$2") - [ "$h32" = "$3" ] - h16=$(nix-hash --type "$1" --to-base16 "$h32") - [ "$h16" = "$2" ] - - h16=$(nix hash convert --hash-algo "$1" --to base16 "$h64") - [ "$h16" = "$2" ] - h16=$(nix hash to-base16 --type "$1" "$h64") - [ "$h16" = "$2" ] - h16=$(nix hash convert --to base16 "$sri") - [ "$h16" = "$2" ] - h16=$(nix hash to-base16 "$sri") - [ "$h16" = "$2" ] - - # - # Converting from SRI - # - - # Input hash algo auto-detected from SRI and output defaults to SRI as well. - sri=$(nix hash convert "$1-$4") - [ "$sri" = "$1-$4" ] - - sri=$(nix hash convert --from sri "$1-$4") - [ "$sri" = "$1-$4" ] - - sri=$(nix hash convert --to sri "$1-$4") - [ "$sri" = "$1-$4" ] - - sri=$(nix hash convert --from sri --to sri "$1-$4") - [ "$sri" = "$1-$4" ] - - sri=$(nix hash convert --to base64 "$1-$4") - [ "$sri" = "$4" ] - - # - # Auto-detecting the input from algo and length. - # - - sri=$(nix hash convert --hash-algo "$1" "$2") - [ "$sri" = "$1-$4" ] - sri=$(nix hash convert --hash-algo "$1" "$3") - [ "$sri" = "$1-$4" ] - sri=$(nix hash convert --hash-algo "$1" "$4") - [ "$sri" = "$1-$4" ] - - sri=$(nix hash convert --hash-algo "$1" "$2") - [ "$sri" = "$1-$4" ] - sri=$(nix hash convert --hash-algo "$1" "$3") - [ "$sri" = "$1-$4" ] - sri=$(nix hash convert --hash-algo "$1" "$4") - [ "$sri" = "$1-$4" ] - - # - # Asserting input format succeeds. - # - - sri=$(nix hash convert --hash-algo "$1" --from base16 "$2") - [ "$sri" = "$1-$4" ] - sri=$(nix hash convert --hash-algo "$1" --from nix32 "$3") - [ "$sri" = "$1-$4" ] - sri=$(nix hash convert --hash-algo "$1" --from base64 "$4") - [ "$sri" = "$1-$4" ] - - # - # Asserting input format fails. - # - - fail=$(nix hash convert --hash-algo "$1" --from nix32 "$2" 2>&1 || echo "exit: $?") - [[ "$fail" == *"error: input hash"*"exit: 1" ]] - fail=$(nix hash convert --hash-algo "$1" --from base16 "$3" 2>&1 || echo "exit: $?") - [[ "$fail" == *"error: input hash"*"exit: 1" ]] - fail=$(nix hash convert --hash-algo "$1" --from nix32 "$4" 2>&1 || echo "exit: $?") - [[ "$fail" == *"error: input hash"*"exit: 1" ]] - -} - -try3 sha1 "800d59cfcd3c05e900cb4e214be48f6b886a08df" "vw46m23bizj4n8afrc0fj19wrp7mj3c0" "gA1Zz808BekAy04hS+SPa4hqCN8=" -try3 sha256 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" "1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s" "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=" -try3 sha512 "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445" "12k9jiq29iyqm03swfsgiw5mlqs173qazm3n7daz43infy12pyrcdf30fkk3qwv4yl2ick8yipc2mqnlh48xsvvxl60lbx8vp38yji0" "IEqPxt2oLwoM7XvrjgikFlfBbvRosiioJ5vjMacDwzWW/RXBOxsH+aodO+pXeJygMa2Fx6cd1wNU7GMSOMo0RQ==" diff --git a/tests/functional/local.mk b/tests/functional/local.mk index 888c7e18a679..0677e09f4330 100644 --- a/tests/functional/local.mk +++ b/tests/functional/local.mk @@ -47,7 +47,8 @@ nix_tests = \ optimise-store.sh \ substitute-with-invalid-ca.sh \ signing.sh \ - hash.sh \ + hash-convert.sh \ + hash-path.sh \ gc-non-blocking.sh \ check.sh \ nix-shell.sh \ diff --git a/tests/unit/libstore/content-address.cc b/tests/unit/libstore/content-address.cc new file mode 100644 index 000000000000..98c1eace3684 --- /dev/null +++ b/tests/unit/libstore/content-address.cc @@ -0,0 +1,35 @@ +#include + +#include "content-address.hh" + +namespace nix { + +/* ---------------------------------------------------------------------------- + * ContentAddressMethod::parse, ContentAddressMethod::render + * --------------------------------------------------------------------------*/ + +TEST(ContentAddressMethod, testRoundTripPrintParse_1) { + for (const ContentAddressMethod & cam : { + ContentAddressMethod { TextIngestionMethod {} }, + ContentAddressMethod { FileIngestionMethod::Flat }, + ContentAddressMethod { FileIngestionMethod::Recursive }, + }) { + EXPECT_EQ(ContentAddressMethod::parse(cam.render()), cam); + } +} + +TEST(ContentAddressMethod, testRoundTripPrintParse_2) { + for (const std::string_view camS : { + "text", + "flat", + "nar", + }) { + EXPECT_EQ(ContentAddressMethod::parse(camS).render(), camS); + } +} + +TEST(ContentAddressMethod, testParseContentAddressMethodOptException) { + EXPECT_THROW(ContentAddressMethod::parse("narwhal"), UsageError); +} + +} diff --git a/tests/unit/libutil/file-content-address.cc b/tests/unit/libutil/file-content-address.cc new file mode 100644 index 000000000000..2e819ce408dc --- /dev/null +++ b/tests/unit/libutil/file-content-address.cc @@ -0,0 +1,33 @@ +#include + +#include "file-content-address.hh" + +namespace nix { + +/* ---------------------------------------------------------------------------- + * parseFileIngestionMethod, renderFileIngestionMethod + * --------------------------------------------------------------------------*/ + +TEST(FileIngestionMethod, testRoundTripPrintParse_1) { + for (const FileIngestionMethod fim : { + FileIngestionMethod::Flat, + FileIngestionMethod::Recursive, + }) { + EXPECT_EQ(parseFileIngestionMethod(renderFileIngestionMethod(fim)), fim); + } +} + +TEST(FileIngestionMethod, testRoundTripPrintParse_2) { + for (const std::string_view fimS : { + "flat", + "nar", + }) { + EXPECT_EQ(renderFileIngestionMethod(parseFileIngestionMethod(fimS)), fimS); + } +} + +TEST(FileIngestionMethod, testParseFileIngestionMethodOptException) { + EXPECT_THROW(parseFileIngestionMethod("narwhal"), UsageError); +} + +}