diff options
Diffstat (limited to 'src/libutil')
-rw-r--r-- | src/libutil/args.cc | 14 | ||||
-rw-r--r-- | src/libutil/args.hh | 1 | ||||
-rw-r--r-- | src/libutil/fmt.hh | 17 | ||||
-rw-r--r-- | src/libutil/hash.cc | 69 | ||||
-rw-r--r-- | src/libutil/hash.hh | 18 | ||||
-rw-r--r-- | src/libutil/tests/compression.cc | 78 | ||||
-rw-r--r-- | src/libutil/tests/hash.cc | 5 | ||||
-rw-r--r-- | src/libutil/tests/logging.cc | 99 |
8 files changed, 234 insertions, 67 deletions
diff --git a/src/libutil/args.cc b/src/libutil/args.cc index ce6580119..1f0e4f803 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -162,8 +162,18 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) .labels = {"hash-algo"}, .handler = {[ht](std::string s) { *ht = parseHashType(s); - if (*ht == htUnknown) - throw UsageError("unknown hash type '%1%'", s); + }} + }; +} + +Args::Flag Args::Flag::mkHashTypeOptFlag(std::string && longName, std::optional<HashType> * oht) +{ + return Flag { + .longName = std::move(longName), + .description = "hash algorithm ('md5', 'sha1', 'sha256', or 'sha512'). Optional as can also be gotten from SRI hash itself.", + .labels = {"hash-algo"}, + .handler = {[oht](std::string s) { + *oht = std::optional<HashType> { parseHashType(s) }; }} }; } diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 154d1e6aa..433d26bba 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -85,6 +85,7 @@ protected: Handler handler; static Flag mkHashTypeFlag(std::string && longName, HashType * ht); + static Flag mkHashTypeOptFlag(std::string && longName, std::optional<HashType> * oht); }; std::map<std::string, Flag::ptr> longFlags; diff --git a/src/libutil/fmt.hh b/src/libutil/fmt.hh index 12ab9c407..a39de041f 100644 --- a/src/libutil/fmt.hh +++ b/src/libutil/fmt.hh @@ -1,6 +1,7 @@ #pragma once #include <boost/format.hpp> +#include <boost/algorithm/string/replace.hpp> #include <string> #include "ansicolor.hh" @@ -103,7 +104,9 @@ class hintformat public: hintformat(const string &format) :fmt(format) { - fmt.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); + fmt.exceptions(boost::io::all_error_bits ^ + boost::io::too_many_args_bit ^ + boost::io::too_few_args_bit); } hintformat(const hintformat &hf) @@ -117,6 +120,13 @@ public: return *this; } + template<class T> + hintformat& operator%(const normaltxt<T> &value) + { + fmt % value.value; + return *this; + } + std::string str() const { return fmt.str(); @@ -136,4 +146,9 @@ inline hintformat hintfmt(const std::string & fs, const Args & ... args) return f; } +inline hintformat hintfmt(std::string plain_string) +{ + // we won't be receiving any args in this case, so just print the original string + return hintfmt("%s", normaltxt(plain_string)); +} } diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 460d479a3..c8fcdfed0 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -4,6 +4,7 @@ #include <openssl/md5.h> #include <openssl/sha.h> +#include "args.hh" #include "hash.hh" #include "archive.hh" #include "util.hh" @@ -18,11 +19,13 @@ namespace nix { void Hash::init() { - if (type == htMD5) hashSize = md5HashSize; - else if (type == htSHA1) hashSize = sha1HashSize; - else if (type == htSHA256) hashSize = sha256HashSize; - else if (type == htSHA512) hashSize = sha512HashSize; - else abort(); + if (!type) abort(); + switch (*type) { + case htMD5: hashSize = md5HashSize; break; + case htSHA1: hashSize = sha1HashSize; break; + case htSHA256: hashSize = sha256HashSize; break; + case htSHA512: hashSize = sha512HashSize; break; + } assert(hashSize <= maxHashSize); memset(hash, 0, maxHashSize); } @@ -102,11 +105,18 @@ string printHash16or32(const Hash & hash) } +HashType assertInitHashType(const Hash & h) { + if (h.type) + return *h.type; + else + abort(); +} + std::string Hash::to_string(Base base, bool includeType) const { std::string s; if (base == SRI || includeType) { - s += printHashType(type); + s += printHashType(assertInitHashType(*this)); s += base == SRI ? '-' : ':'; } switch (base) { @@ -124,8 +134,10 @@ std::string Hash::to_string(Base base, bool includeType) const return s; } +Hash::Hash(std::string_view s, HashType type) : Hash(s, std::optional { type }) { } +Hash::Hash(std::string_view s) : Hash(s, std::optional<HashType>{}) { } -Hash::Hash(std::string_view s, HashType type) +Hash::Hash(std::string_view s, std::optional<HashType> type) : type(type) { size_t pos = 0; @@ -136,17 +148,17 @@ Hash::Hash(std::string_view s, HashType type) sep = s.find('-'); if (sep != string::npos) { isSRI = true; - } else if (type == htUnknown) + } else if (! type) throw BadHash("hash '%s' does not include a type", s); } if (sep != string::npos) { string hts = string(s, 0, sep); this->type = parseHashType(hts); - if (this->type == htUnknown) + if (!this->type) throw BadHash("unknown hash type '%s'", hts); - if (type != htUnknown && type != this->type) - throw BadHash("hash '%s' should have type '%s'", s, printHashType(type)); + if (type && type != this->type) + throw BadHash("hash '%s' should have type '%s'", s, printHashType(*type)); pos = sep + 1; } @@ -202,13 +214,15 @@ Hash::Hash(std::string_view s, HashType type) } else - throw BadHash("hash '%s' has wrong length for hash type '%s'", s, printHashType(type)); + throw BadHash("hash '%s' has wrong length for hash type '%s'", s, printHashType(*type)); } -Hash newHashAllowEmpty(std::string hashStr, HashType ht) +Hash newHashAllowEmpty(std::string hashStr, std::optional<HashType> ht) { if (hashStr.empty()) { - Hash h(ht); + if (!ht) + throw BadHash("empty hash requires explicit hash type"); + Hash h(*ht); warn("found empty hash, assuming '%s'", h.to_string(SRI, true)); return h; } else @@ -328,24 +342,35 @@ Hash compressHash(const Hash & hash, unsigned int newSize) } -HashType parseHashType(const string & s) +std::optional<HashType> parseHashTypeOpt(const string & s) { if (s == "md5") return htMD5; else if (s == "sha1") return htSHA1; else if (s == "sha256") return htSHA256; else if (s == "sha512") return htSHA512; - else return htUnknown; + else return std::optional<HashType> {}; } +HashType parseHashType(const string & s) +{ + auto opt_h = parseHashTypeOpt(s); + if (opt_h) + return *opt_h; + else + throw UsageError("unknown hash algorithm '%1%'", s); +} string printHashType(HashType ht) { - if (ht == htMD5) return "md5"; - else if (ht == htSHA1) return "sha1"; - else if (ht == htSHA256) return "sha256"; - else if (ht == htSHA512) return "sha512"; - else abort(); + switch (ht) { + case htMD5: return "md5"; break; + case htSHA1: return "sha1"; break; + case htSHA256: return "sha256"; break; + case htSHA512: return "sha512"; break; + } + // illegal hash type enum value internally, as opposed to external input + // which should be validated with nice error message. + abort(); } - } diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh index 180fb7633..0d9916508 100644 --- a/src/libutil/hash.hh +++ b/src/libutil/hash.hh @@ -10,7 +10,7 @@ namespace nix { MakeError(BadHash, Error); -enum HashType : char { htUnknown, htMD5, htSHA1, htSHA256, htSHA512 }; +enum HashType : char { htMD5, htSHA1, htSHA256, htSHA512 }; const int md5HashSize = 16; @@ -29,7 +29,7 @@ struct Hash unsigned int hashSize = 0; unsigned char hash[maxHashSize] = {}; - HashType type = htUnknown; + std::optional<HashType> type = {}; /* Create an unset hash object. */ Hash() { }; @@ -40,14 +40,18 @@ struct Hash /* Initialize the hash from a string representation, in the format "[<type>:]<base16|base32|base64>" or "<type>-<base64>" (a Subresource Integrity hash expression). If the 'type' argument - is htUnknown, then the hash type must be specified in the + is not present, then the hash type must be specified in the string. */ - Hash(std::string_view s, HashType type = htUnknown); + Hash(std::string_view s, std::optional<HashType> type); + // type must be provided + Hash(std::string_view s, HashType type); + // hash type must be part of string + Hash(std::string_view s); void init(); /* Check whether a hash is set. */ - operator bool () const { return type != htUnknown; } + operator bool () const { return (bool) type; } /* Check whether two hash are equal. */ bool operator == (const Hash & h2) const; @@ -95,7 +99,7 @@ struct Hash }; /* Helper that defaults empty hashes to the 0 hash. */ -Hash newHashAllowEmpty(std::string hashStr, HashType ht); +Hash newHashAllowEmpty(std::string hashStr, std::optional<HashType> ht); /* Print a hash in base-16 if it's MD5, or base-32 otherwise. */ string printHash16or32(const Hash & hash); @@ -118,6 +122,8 @@ Hash compressHash(const Hash & hash, unsigned int newSize); /* Parse a string representing a hash type. */ HashType parseHashType(const string & s); +/* Will return nothing on parse error */ +std::optional<HashType> parseHashTypeOpt(const string & s); /* And the reverse. */ string printHashType(HashType ht); diff --git a/src/libutil/tests/compression.cc b/src/libutil/tests/compression.cc new file mode 100644 index 000000000..5b7a2c5b9 --- /dev/null +++ b/src/libutil/tests/compression.cc @@ -0,0 +1,78 @@ +#include "compression.hh" +#include <gtest/gtest.h> + +namespace nix { + + /* ---------------------------------------------------------------------------- + * compress / decompress + * --------------------------------------------------------------------------*/ + + TEST(compress, compressWithUnknownMethod) { + ASSERT_THROW(compress("invalid-method", "something-to-compress"), UnknownCompressionMethod); + } + + TEST(compress, noneMethodDoesNothingToTheInput) { + ref<std::string> o = compress("none", "this-is-a-test"); + + ASSERT_EQ(*o, "this-is-a-test"); + } + + TEST(decompress, decompressXzCompressed) { + auto method = "xz"; + auto str = "slfja;sljfklsa;jfklsjfkl;sdjfkl;sadjfkl;sdjf;lsdfjsadlf"; + ref<std::string> o = decompress(method, *compress(method, str)); + + ASSERT_EQ(*o, str); + } + + TEST(decompress, decompressBzip2Compressed) { + auto method = "bzip2"; + auto str = "slfja;sljfklsa;jfklsjfkl;sdjfkl;sadjfkl;sdjf;lsdfjsadlf"; + ref<std::string> o = decompress(method, *compress(method, str)); + + ASSERT_EQ(*o, str); + } + + TEST(decompress, decompressBrCompressed) { + auto method = "br"; + auto str = "slfja;sljfklsa;jfklsjfkl;sdjfkl;sadjfkl;sdjf;lsdfjsadlf"; + ref<std::string> o = decompress(method, *compress(method, str)); + + ASSERT_EQ(*o, str); + } + + TEST(decompress, decompressInvalidInputThrowsCompressionError) { + auto method = "bzip2"; + auto str = "this is a string that does not qualify as valid bzip2 data"; + + ASSERT_THROW(decompress(method, str), CompressionError); + } + + /* ---------------------------------------------------------------------------- + * compression sinks + * --------------------------------------------------------------------------*/ + + TEST(makeCompressionSink, noneSinkDoesNothingToInput) { + StringSink strSink; + auto inputString = "slfja;sljfklsa;jfklsjfkl;sdjfkl;sadjfkl;sdjf;lsdfjsadlf"; + auto sink = makeCompressionSink("none", strSink); + (*sink)(inputString); + sink->finish(); + + ASSERT_STREQ((*strSink.s).c_str(), inputString); + } + + TEST(makeCompressionSink, compressAndDecompress) { + StringSink strSink; + auto inputString = "slfja;sljfklsa;jfklsjfkl;sdjfkl;sadjfkl;sdjf;lsdfjsadlf"; + auto decompressionSink = makeDecompressionSink("bzip2", strSink); + auto sink = makeCompressionSink("bzip2", *decompressionSink); + + (*sink)(inputString); + sink->finish(); + decompressionSink->finish(); + + ASSERT_STREQ((*strSink.s).c_str(), inputString); + } + +} diff --git a/src/libutil/tests/hash.cc b/src/libutil/tests/hash.cc index 5334b046e..412c03030 100644 --- a/src/libutil/tests/hash.cc +++ b/src/libutil/tests/hash.cc @@ -72,9 +72,4 @@ namespace nix { "7299aeadb6889018501d289e4900f7e4331b99dec4b5433a" "c7d329eeb6dd26545e96e55b874be909"); } - - TEST(hashString, hashingWithUnknownAlgoExits) { - auto s = "unknown"; - ASSERT_DEATH(hashString(HashType::htUnknown, s), ""); - } } diff --git a/src/libutil/tests/logging.cc b/src/libutil/tests/logging.cc index 8c7bb16b3..6a6fb4ac3 100644 --- a/src/libutil/tests/logging.cc +++ b/src/libutil/tests/logging.cc @@ -1,6 +1,7 @@ #include "logging.hh" #include "nixexpr.hh" #include "util.hh" +#include <fstream> #include <gtest/gtest.h> @@ -42,7 +43,7 @@ namespace nix { logger->logEI(ei); auto str = testing::internal::GetCapturedStderr(); - ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- TestError --- error-unit-test\x1B[0m\n\x1B[33;1m\x1B[0minitial error\x1B[0m; subsequent error message.\n"); + ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- TestError --- error-unit-test\x1B[0m\ninitial error; subsequent error message.\n"); } } @@ -60,8 +61,7 @@ namespace nix { logError(e.info()); auto str = testing::internal::GetCapturedStderr(); - ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- SysError --- error-unit-test\x1B[0m\n\x1B[33;1m\x1B[0mstatting file\x1B[0m: \x1B[33;1mBad file descriptor\x1B[0m\n"); - + ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- SysError --- error-unit-test\x1B[0m\nstatting file: \x1B[33;1mBad file descriptor\x1B[0m\n"); } } @@ -69,9 +69,9 @@ namespace nix { testing::internal::CaptureStderr(); logger->logEI({ .level = lvlInfo, - .name = "Info name", - .description = "Info description", - }); + .name = "Info name", + .description = "Info description", + }); auto str = testing::internal::GetCapturedStderr(); ASSERT_STREQ(str.c_str(), "\x1B[32;1minfo:\x1B[0m\x1B[34;1m --- Info name --- error-unit-test\x1B[0m\nInfo description\n"); @@ -85,7 +85,7 @@ namespace nix { logger->logEI({ .level = lvlTalkative, .name = "Talkative name", .description = "Talkative description", - }); + }); auto str = testing::internal::GetCapturedStderr(); ASSERT_STREQ(str.c_str(), "\x1B[32;1mtalk:\x1B[0m\x1B[34;1m --- Talkative name --- error-unit-test\x1B[0m\nTalkative description\n"); @@ -99,7 +99,7 @@ namespace nix { logger->logEI({ .level = lvlChatty, .name = "Chatty name", .description = "Talkative description", - }); + }); auto str = testing::internal::GetCapturedStderr(); ASSERT_STREQ(str.c_str(), "\x1B[32;1mchat:\x1B[0m\x1B[34;1m --- Chatty name --- error-unit-test\x1B[0m\nTalkative description\n"); @@ -113,7 +113,7 @@ namespace nix { logger->logEI({ .level = lvlDebug, .name = "Debug name", .description = "Debug description", - }); + }); auto str = testing::internal::GetCapturedStderr(); ASSERT_STREQ(str.c_str(), "\x1B[33;1mdebug:\x1B[0m\x1B[34;1m --- Debug name --- error-unit-test\x1B[0m\nDebug description\n"); @@ -127,7 +127,7 @@ namespace nix { logger->logEI({ .level = lvlVomit, .name = "Vomit name", .description = "Vomit description", - }); + }); auto str = testing::internal::GetCapturedStderr(); ASSERT_STREQ(str.c_str(), "\x1B[32;1mvomit:\x1B[0m\x1B[34;1m --- Vomit name --- error-unit-test\x1B[0m\nVomit description\n"); @@ -144,7 +144,7 @@ namespace nix { logError({ .name = "name", .description = "error description", - }); + }); auto str = testing::internal::GetCapturedStderr(); ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- name --- error-unit-test\x1B[0m\nerror description\n"); @@ -160,13 +160,13 @@ namespace nix { .name = "error name", .description = "error with code lines", .hint = hintfmt("this hint has %1% templated %2%!!", - "yellow", - "values"), + "yellow", + "values"), .nixCode = NixCode { - .errPos = Pos(foFile, problem_file, 40, 13), - .prevLineOfCode = "previous line of code", - .errLineOfCode = "this is the problem line of code", - .nextLineOfCode = "next line of code", + .errPos = Pos(problem_file, 40, 13), + .prevLineOfCode = "previous line of code", + .errLineOfCode = "this is the problem line of code", + .nextLineOfCode = "next line of code", }}); @@ -183,10 +183,10 @@ namespace nix { .name = "error name", .description = "error without any code lines.", .hint = hintfmt("this hint has %1% templated %2%!!", - "yellow", - "values"), + "yellow", + "values"), .nixCode = NixCode { - .errPos = Pos(foFile, problem_file, 40, 13) + .errPos = Pos(problem_file, 40, 13) }}); auto str = testing::internal::GetCapturedStderr(); @@ -202,7 +202,7 @@ namespace nix { .name = "error name", .hint = hintfmt("hint %1%", "only"), .nixCode = NixCode { - .errPos = Pos(foFile, problem_file, 40, 13) + .errPos = Pos(problem_file, 40, 13) }}); auto str = testing::internal::GetCapturedStderr(); @@ -218,10 +218,10 @@ namespace nix { testing::internal::CaptureStderr(); logWarning({ - .name = "name", - .description = "error description", - .hint = hintfmt("there was a %1%", "warning"), - }); + .name = "name", + .description = "error description", + .hint = hintfmt("there was a %1%", "warning"), + }); auto str = testing::internal::GetCapturedStderr(); ASSERT_STREQ(str.c_str(), "\x1B[33;1mwarning:\x1B[0m\x1B[34;1m --- name --- error-unit-test\x1B[0m\nerror description\n\nthere was a \x1B[33;1mwarning\x1B[0m\n"); @@ -238,13 +238,13 @@ namespace nix { .name = "warning name", .description = "warning description", .hint = hintfmt("this hint has %1% templated %2%!!", - "yellow", - "values"), + "yellow", + "values"), .nixCode = NixCode { - .errPos = Pos(foFile, problem_file, 40, 13), - .prevLineOfCode = std::nullopt, - .errLineOfCode = "this is the problem line of code", - .nextLineOfCode = std::nullopt + .errPos = Pos(problem_file, 40, 13), + .prevLineOfCode = std::nullopt, + .errLineOfCode = "this is the problem line of code", + .nextLineOfCode = std::nullopt }}); @@ -252,4 +252,41 @@ namespace nix { ASSERT_STREQ(str.c_str(), "\x1B[33;1mwarning:\x1B[0m\x1B[34;1m --- warning name --- error-unit-test\x1B[0m\nin file: \x1B[34;1mmyfile.nix (40:13)\x1B[0m\n\nwarning description\n\n 40| this is the problem line of code\n | \x1B[31;1m^\x1B[0m\n\nthis hint has \x1B[33;1myellow\x1B[0m templated \x1B[33;1mvalues\x1B[0m!!\n"); } + /* ---------------------------------------------------------------------------- + * hintfmt + * --------------------------------------------------------------------------*/ + + TEST(hintfmt, percentStringWithoutArgs) { + + const char *teststr = "this is 100%s correct!"; + + ASSERT_STREQ( + hintfmt(teststr).str().c_str(), + teststr); + + } + + TEST(hintfmt, fmtToHintfmt) { + + ASSERT_STREQ( + hintfmt(fmt("the color of this this text is %1%", "not yellow")).str().c_str(), + "the color of this this text is not yellow"); + + } + + TEST(hintfmt, tooFewArguments) { + + ASSERT_STREQ( + hintfmt("only one arg %1% %2%", "fulfilled").str().c_str(), + "only one arg " ANSI_YELLOW "fulfilled" ANSI_NORMAL " "); + + } + + TEST(hintfmt, tooManyArguments) { + + ASSERT_STREQ( + hintfmt("what about this %1% %2%", "%3%", "one", "two").str().c_str(), + "what about this " ANSI_YELLOW "%3%" ANSI_NORMAL " " ANSI_YELLOW "one" ANSI_NORMAL); + + } } |