1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-09 03:56:01 +01:00

Minimize the use of C Macros for characterization tests

Fewer macros is better!

Introduce a new `JsonChacterizationTest` mixin class to help with this.

Also, avoid some needless copies with `GetParam`.

Part of my effort shoring up the JSON formats with #13570.
This commit is contained in:
John Ericson 2025-09-24 11:55:21 -04:00
parent 3bf1268ac6
commit 01b2037bc0
15 changed files with 364 additions and 252 deletions

View file

@ -642,7 +642,7 @@ class ToStringPrimOpTest : public PrimOpTest,
TEST_P(ToStringPrimOpTest, toString) TEST_P(ToStringPrimOpTest, toString)
{ {
const auto [input, output] = GetParam(); const auto & [input, output] = GetParam();
auto v = eval(input); auto v = eval(input);
ASSERT_THAT(v, IsStringEq(output)); ASSERT_THAT(v, IsStringEq(output));
} }
@ -798,7 +798,7 @@ class CompareVersionsPrimOpTest : public PrimOpTest,
TEST_P(CompareVersionsPrimOpTest, compareVersions) TEST_P(CompareVersionsPrimOpTest, compareVersions)
{ {
auto [expression, expectation] = GetParam(); const auto & [expression, expectation] = GetParam();
auto v = eval(expression); auto v = eval(expression);
ASSERT_THAT(v, IsIntEq(expectation)); ASSERT_THAT(v, IsIntEq(expectation));
} }
@ -834,7 +834,7 @@ class ParseDrvNamePrimOpTest
TEST_P(ParseDrvNamePrimOpTest, parseDrvName) TEST_P(ParseDrvNamePrimOpTest, parseDrvName)
{ {
auto [input, expectedName, expectedVersion] = GetParam(); const auto & [input, expectedName, expectedVersion] = GetParam();
const auto expr = fmt("builtins.parseDrvName \"%1%\"", input); const auto expr = fmt("builtins.parseDrvName \"%1%\"", input);
auto v = eval(expr); auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(2)); ASSERT_THAT(v, IsAttrsOfSize(2));

View file

@ -1,14 +1,14 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "nix/fetchers/fetchers.hh" #include "nix/fetchers/fetchers.hh"
#include "nix/util/json-utils.hh" #include "nix/util/json-utils.hh"
#include <nlohmann/json.hpp> #include "nix/util/tests/json-characterization.hh"
#include "nix/util/tests/characterization.hh"
namespace nix { namespace nix {
using nlohmann::json; using nlohmann::json;
class PublicKeyTest : public CharacterizationTest class PublicKeyTest : public JsonCharacterizationTest<fetchers::PublicKey>,
public ::testing::WithParamInterface<std::pair<std::string_view, fetchers::PublicKey>>
{ {
std::filesystem::path unitTestData = getUnitTestData() / "public-key"; std::filesystem::path unitTestData = getUnitTestData() / "public-key";
@ -19,30 +19,35 @@ public:
} }
}; };
#define TEST_JSON(FIXTURE, NAME, VAL) \ TEST_P(PublicKeyTest, from_json)
TEST_F(FIXTURE, PublicKey_##NAME##_from_json) \ {
{ \ const auto & [name, expected] = GetParam();
readTest(#NAME ".json", [&](const auto & encoded_) { \ readJsonTest(name, expected);
fetchers::PublicKey expected{VAL}; \ }
fetchers::PublicKey got = nlohmann::json::parse(encoded_); \
ASSERT_EQ(got, expected); \
}); \
} \
\
TEST_F(FIXTURE, PublicKey_##NAME##_to_json) \
{ \
writeTest( \
#NAME ".json", \
[&]() -> json { return nlohmann::json(fetchers::PublicKey{VAL}); }, \
[](const auto & file) { return json::parse(readFile(file)); }, \
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
}
TEST_JSON(PublicKeyTest, simple, (fetchers::PublicKey{.type = "ssh-rsa", .key = "ABCDE"})) TEST_P(PublicKeyTest, to_json)
{
const auto & [name, value] = GetParam();
writeJsonTest(name, value);
}
TEST_JSON(PublicKeyTest, defaultType, fetchers::PublicKey{.key = "ABCDE"}) INSTANTIATE_TEST_SUITE_P(
PublicKeyJSON,
#undef TEST_JSON PublicKeyTest,
::testing::Values(
std::pair{
"simple",
fetchers::PublicKey{
.type = "ssh-rsa",
.key = "ABCDE",
},
},
std::pair{
"defaultType",
fetchers::PublicKey{
.key = "ABCDE",
},
}));
TEST_F(PublicKeyTest, PublicKey_noRoundTrip_from_json) TEST_F(PublicKeyTest, PublicKey_noRoundTrip_from_json)
{ {

View file

@ -5,13 +5,13 @@
#include "nix/store/derivations.hh" #include "nix/store/derivations.hh"
#include "nix/store/tests/libstore.hh" #include "nix/store/tests/libstore.hh"
#include "nix/util/tests/characterization.hh" #include "nix/util/tests/json-characterization.hh"
namespace nix { namespace nix {
using nlohmann::json; using nlohmann::json;
class DerivationTest : public CharacterizationTest, public LibStoreTest class DerivationTest : public virtual CharacterizationTest, public LibStoreTest
{ {
std::filesystem::path unitTestData = getUnitTestData() / "derivation"; std::filesystem::path unitTestData = getUnitTestData() / "derivation";
@ -66,146 +66,183 @@ TEST_F(DynDerivationTest, BadATerm_oldVersionDynDeps)
FormatError); FormatError);
} }
#define TEST_JSON(FIXTURE, NAME, VAL, DRV_NAME, OUTPUT_NAME) \ #define MAKE_OUTPUT_JSON_TEST_P(FIXTURE) \
TEST_F(FIXTURE, DerivationOutput_##NAME##_from_json) \ TEST_P(FIXTURE, from_json) \
{ \ { \
readTest("output-" #NAME ".json", [&](const auto & encoded_) { \ const auto & [name, expected] = GetParam(); \
auto encoded = json::parse(encoded_); \ /* Don't use readJsonTest because we want to check experimental \
DerivationOutput got = DerivationOutput::fromJSON(DRV_NAME, OUTPUT_NAME, encoded, mockXpSettings); \ features. */ \
DerivationOutput expected{VAL}; \ readTest(Path{"output-"} + name + ".json", [&](const auto & encoded_) { \
ASSERT_EQ(got, expected); \ json j = json::parse(encoded_); \
}); \ DerivationOutput got = DerivationOutput::fromJSON(j, mockXpSettings); \
} \ ASSERT_EQ(got, expected); \
\ }); \
TEST_F(FIXTURE, DerivationOutput_##NAME##_to_json) \ } \
{ \ \
writeTest( \ TEST_P(FIXTURE, to_json) \
"output-" #NAME ".json", \ { \
[&]() -> json { return DerivationOutput{(VAL)}.toJSON((DRV_NAME), (OUTPUT_NAME)); }, \ const auto & [name, value] = GetParam(); \
[](const auto & file) { return json::parse(readFile(file)); }, \ writeJsonTest("output-" + name, value); \
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
} }
TEST_JSON( struct DerivationOutputJsonTest : DerivationTest,
DerivationTest, JsonCharacterizationTest<DerivationOutput>,
inputAddressed, ::testing::WithParamInterface<std::pair<std::string_view, DerivationOutput>>
(DerivationOutput::InputAddressed{ {};
.path = store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"),
}),
"drv-name",
"output-name")
TEST_JSON( MAKE_OUTPUT_JSON_TEST_P(DerivationOutputJsonTest)
DerivationTest,
caFixedFlat,
(DerivationOutput::CAFixed{
.ca =
{
.method = ContentAddressMethod::Raw::Flat,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
},
}),
"drv-name",
"output-name")
TEST_JSON( INSTANTIATE_TEST_SUITE_P(
DerivationTest, DerivationOutputJSON,
caFixedNAR, DerivationOutputJsonTest,
(DerivationOutput::CAFixed{ ::testing::Values(
.ca = std::pair{
{ "inputAddressed",
DerivationOutput{DerivationOutput::InputAddressed{
.path = StorePath{"c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"},
}},
},
std::pair{
"caFixedFlat",
DerivationOutput{DerivationOutput::CAFixed{
.ca =
{
.method = ContentAddressMethod::Raw::Flat,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
},
}},
},
std::pair{
"caFixedNAR",
DerivationOutput{DerivationOutput::CAFixed{
.ca =
{
.method = ContentAddressMethod::Raw::NixArchive,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
},
}},
},
std::pair{
"deferred",
DerivationOutput{DerivationOutput::Deferred{}},
}));
struct DynDerivationOutputJsonTest : DynDerivationTest,
JsonCharacterizationTest<DerivationOutput>,
::testing::WithParamInterface<std::pair<std::string_view, DerivationOutput>>
{};
MAKE_OUTPUT_JSON_TEST_P(DynDerivationOutputJsonTest);
INSTANTIATE_TEST_SUITE_P(
DynDerivationOutputJSON,
DynDerivationOutputJsonTest,
::testing::Values(
std::pair{
"caFixedText",
DerivationOutput{DerivationOutput::CAFixed{
.ca =
{
.method = ContentAddressMethod::Raw::Text,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
},
}},
}));
struct CaDerivationOutputJsonTest : CaDerivationTest,
JsonCharacterizationTest<DerivationOutput>,
::testing::WithParamInterface<std::pair<std::string_view, DerivationOutput>>
{};
MAKE_OUTPUT_JSON_TEST_P(CaDerivationOutputJsonTest);
INSTANTIATE_TEST_SUITE_P(
CaDerivationOutputJSON,
CaDerivationOutputJsonTest,
::testing::Values(
std::pair{
"caFloating",
DerivationOutput{DerivationOutput::CAFloating{
.method = ContentAddressMethod::Raw::NixArchive, .method = ContentAddressMethod::Raw::NixArchive,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="), .hashAlgo = HashAlgorithm::SHA256,
}, }},
}), }));
"drv-name",
"output-name")
TEST_JSON( struct ImpureDerivationOutputJsonTest : ImpureDerivationTest,
DynDerivationTest, JsonCharacterizationTest<DerivationOutput>,
caFixedText, ::testing::WithParamInterface<std::pair<std::string_view, DerivationOutput>>
(DerivationOutput::CAFixed{ {};
.ca =
{
.method = ContentAddressMethod::Raw::Text,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
},
}),
"drv-name",
"output-name")
TEST_JSON( MAKE_OUTPUT_JSON_TEST_P(ImpureDerivationOutputJsonTest);
CaDerivationTest,
caFloating,
(DerivationOutput::CAFloating{
.method = ContentAddressMethod::Raw::NixArchive,
.hashAlgo = HashAlgorithm::SHA256,
}),
"drv-name",
"output-name")
TEST_JSON(DerivationTest, deferred, DerivationOutput::Deferred{}, "drv-name", "output-name") INSTANTIATE_TEST_SUITE_P(
ImpureDerivationOutputJSON,
ImpureDerivationOutputJsonTest,
::testing::Values(
std::pair{
"impure",
DerivationOutput{DerivationOutput::Impure{
.method = ContentAddressMethod::Raw::NixArchive,
.hashAlgo = HashAlgorithm::SHA256,
}},
}));
TEST_JSON( #undef MAKE_OUTPUT_JSON_TEST_P
ImpureDerivationTest,
impure,
(DerivationOutput::Impure{
.method = ContentAddressMethod::Raw::NixArchive,
.hashAlgo = HashAlgorithm::SHA256,
}),
"drv-name",
"output-name")
#undef TEST_JSON #define MAKE_TEST_P(FIXTURE) \
TEST_P(FIXTURE, from_json) \
#define TEST_JSON(FIXTURE, NAME, VAL) \ { \
TEST_F(FIXTURE, Derivation_##NAME##_from_json) \ const auto & drv = GetParam(); \
{ \ /* Don't use readJsonTest because we want to check experimental \
readTest(#NAME ".json", [&](const auto & encoded_) { \ features. */ \
auto encoded = json::parse(encoded_); \ readTest(drv.name + ".json", [&](const auto & encoded_) { \
Derivation expected{VAL}; \ auto encoded = json::parse(encoded_); \
Derivation got = Derivation::fromJSON(encoded, mockXpSettings); \ Derivation got = Derivation::fromJSON(encoded, mockXpSettings); \
ASSERT_EQ(got, expected); \ ASSERT_EQ(got, drv); \
}); \ }); \
} \ } \
\ \
TEST_F(FIXTURE, Derivation_##NAME##_to_json) \ TEST_P(FIXTURE, to_json) \
{ \ { \
writeTest( \ const auto & drv = GetParam(); \
#NAME ".json", \ writeJsonTest(drv.name, drv); \
[&]() -> json { return Derivation{VAL}.toJSON(); }, \ } \
[](const auto & file) { return json::parse(readFile(file)); }, \ \
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \ TEST_P(FIXTURE, from_aterm) \
{ \
const auto & drv = GetParam(); \
readTest(drv.name + ".drv", [&](auto encoded) { \
auto got = parseDerivation(*store, std::move(encoded), drv.name, mockXpSettings); \
ASSERT_EQ(got.toJSON(), drv.toJSON()); \
ASSERT_EQ(got, drv); \
}); \
} \
\
TEST_P(FIXTURE, to_aterm) \
{ \
const auto & drv = GetParam(); \
writeTest(drv.name + ".drv", [&]() -> std::string { return drv.unparse(*store, false); }); \
} }
#define TEST_ATERM(FIXTURE, NAME, VAL, DRV_NAME) \ struct DerivationJsonAtermTest : DerivationTest,
TEST_F(FIXTURE, Derivation_##NAME##_from_aterm) \ JsonCharacterizationTest<Derivation>,
{ \ ::testing::WithParamInterface<Derivation>
readTest(#NAME ".drv", [&](auto encoded) { \ {};
Derivation expected{VAL}; \
auto got = parseDerivation(*store, std::move(encoded), DRV_NAME, mockXpSettings); \
ASSERT_EQ(got.toJSON(), expected.toJSON()); \
ASSERT_EQ(got, expected); \
}); \
} \
\
TEST_F(FIXTURE, Derivation_##NAME##_to_aterm) \
{ \
writeTest(#NAME ".drv", [&]() -> std::string { return (VAL).unparse(*store, false); }); \
}
Derivation makeSimpleDrv(const Store & store) MAKE_TEST_P(DerivationJsonAtermTest);
Derivation makeSimpleDrv()
{ {
Derivation drv; Derivation drv;
drv.name = "simple-derivation"; drv.name = "simple-derivation";
drv.inputSrcs = { drv.inputSrcs = {
store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"), StorePath("c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"),
}; };
drv.inputDrvs = { drv.inputDrvs = {
.map = .map =
{ {
{ {
store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"), StorePath("c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"),
{ {
.value = .value =
{ {
@ -231,22 +268,27 @@ Derivation makeSimpleDrv(const Store & store)
return drv; return drv;
} }
TEST_JSON(DerivationTest, simple, makeSimpleDrv(*store)) INSTANTIATE_TEST_SUITE_P(DerivationJSONATerm, DerivationJsonAtermTest, ::testing::Values(makeSimpleDrv()));
TEST_ATERM(DerivationTest, simple, makeSimpleDrv(*store), "simple-derivation") struct DynDerivationJsonAtermTest : DynDerivationTest,
JsonCharacterizationTest<Derivation>,
::testing::WithParamInterface<Derivation>
{};
Derivation makeDynDepDerivation(const Store & store) MAKE_TEST_P(DynDerivationJsonAtermTest);
Derivation makeDynDepDerivation()
{ {
Derivation drv; Derivation drv;
drv.name = "dyn-dep-derivation"; drv.name = "dyn-dep-derivation";
drv.inputSrcs = { drv.inputSrcs = {
store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"), StorePath{"c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"},
}; };
drv.inputDrvs = { drv.inputDrvs = {
.map = .map =
{ {
{ {
store.parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"), StorePath{"c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"},
DerivedPathMap<StringSet>::ChildNode{ DerivedPathMap<StringSet>::ChildNode{
.value = .value =
{ {
@ -293,11 +335,8 @@ Derivation makeDynDepDerivation(const Store & store)
return drv; return drv;
} }
TEST_JSON(DynDerivationTest, dynDerivationDeps, makeDynDepDerivation(*store)) INSTANTIATE_TEST_SUITE_P(DynDerivationJSONATerm, DynDerivationJsonAtermTest, ::testing::Values(makeDynDepDerivation()));
TEST_ATERM(DynDerivationTest, dynDerivationDeps, makeDynDepDerivation(*store), "dyn-dep-derivation") #undef MAKE_TEST_P
#undef TEST_JSON
#undef TEST_ATERM
} // namespace nix } // namespace nix

View file

@ -3,12 +3,11 @@
#include <rapidcheck/gtest.h> #include <rapidcheck/gtest.h>
#include "nix/store/tests/outputs-spec.hh" #include "nix/store/tests/outputs-spec.hh"
#include "nix/util/tests/json-characterization.hh"
#include "nix/util/tests/characterization.hh"
namespace nix { namespace nix {
class OutputsSpecTest : public CharacterizationTest class OutputsSpecTest : public virtual CharacterizationTest
{ {
std::filesystem::path unitTestData = getUnitTestData() / "outputs-spec"; std::filesystem::path unitTestData = getUnitTestData() / "outputs-spec";
@ -20,7 +19,7 @@ public:
} }
}; };
class ExtendedOutputsSpecTest : public CharacterizationTest class ExtendedOutputsSpecTest : public virtual CharacterizationTest
{ {
std::filesystem::path unitTestData = getUnitTestData() / "outputs-spec" / "extended"; std::filesystem::path unitTestData = getUnitTestData() / "outputs-spec" / "extended";
@ -214,40 +213,49 @@ TEST_F(ExtendedOutputsSpecTest, many_carrot)
ASSERT_EQ(std::string{prefix} + expected.to_string(), "foo^bar^bin,out"); ASSERT_EQ(std::string{prefix} + expected.to_string(), "foo^bar^bin,out");
} }
#define TEST_JSON(FIXTURE, TYPE, NAME, VAL) \ #define MAKE_TEST_P(FIXTURE, TYPE) \
static const TYPE FIXTURE##_##NAME = VAL; \ TEST_P(FIXTURE, from_json) \
\ { \
TEST_F(FIXTURE, NAME##_from_json) \ const auto & [name, value] = GetParam(); \
{ \ readJsonTest(name, value); \
using namespace nlohmann; \ } \
\ \
readTest(#NAME ".json", [&](const auto & encoded_) { \ TEST_P(FIXTURE, to_json) \
auto encoded = json::parse(encoded_); \ { \
TYPE got = adl_serializer<TYPE>::from_json(encoded); \ const auto & [name, value] = GetParam(); \
ASSERT_EQ(got, FIXTURE##_##NAME); \ writeJsonTest(name, value); \
}); \
} \
\
TEST_F(FIXTURE, NAME##_to_json) \
{ \
using namespace nlohmann; \
\
writeTest( \
#NAME ".json", \
[&]() -> json { return static_cast<json>(FIXTURE##_##NAME); }, \
[](const auto & file) { return json::parse(readFile(file)); }, \
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
} }
TEST_JSON(OutputsSpecTest, OutputsSpec, all, OutputsSpec::All{}) struct OutputsSpecJsonTest : OutputsSpecTest,
TEST_JSON(OutputsSpecTest, OutputsSpec, name, OutputsSpec::Names{"a"}) JsonCharacterizationTest<OutputsSpec>,
TEST_JSON(OutputsSpecTest, OutputsSpec, names, (OutputsSpec::Names{"a", "b"})) ::testing::WithParamInterface<std::pair<std::string_view, OutputsSpec>>
{};
TEST_JSON(ExtendedOutputsSpecTest, ExtendedOutputsSpec, def, ExtendedOutputsSpec::Default{}) MAKE_TEST_P(OutputsSpecJsonTest, OutputsSpec);
TEST_JSON(ExtendedOutputsSpecTest, ExtendedOutputsSpec, all, ExtendedOutputsSpec::Explicit{OutputsSpec::All{}})
TEST_JSON(ExtendedOutputsSpecTest, ExtendedOutputsSpec, name, ExtendedOutputsSpec::Explicit{OutputsSpec::Names{"a"}}) INSTANTIATE_TEST_SUITE_P(
TEST_JSON( OutputsSpecJSON,
ExtendedOutputsSpecTest, ExtendedOutputsSpec, names, (ExtendedOutputsSpec::Explicit{OutputsSpec::Names{"a", "b"}})) OutputsSpecJsonTest,
::testing::Values(
std::pair{"all", OutputsSpec{OutputsSpec::All{}}},
std::pair{"name", OutputsSpec{OutputsSpec::Names{"a"}}},
std::pair{"names", OutputsSpec{OutputsSpec::Names{"a", "b"}}}));
struct ExtendedOutputsSpecJsonTest : ExtendedOutputsSpecTest,
JsonCharacterizationTest<ExtendedOutputsSpec>,
::testing::WithParamInterface<std::pair<std::string_view, ExtendedOutputsSpec>>
{};
MAKE_TEST_P(ExtendedOutputsSpecJsonTest, ExtendedOutputsSpec);
INSTANTIATE_TEST_SUITE_P(
ExtendedOutputsSpecJSON,
ExtendedOutputsSpecJsonTest,
::testing::Values(
std::pair{"def", ExtendedOutputsSpec{ExtendedOutputsSpec::Default{}}},
std::pair{"all", ExtendedOutputsSpec{ExtendedOutputsSpec::Explicit{OutputsSpec::All{}}}},
std::pair{"name", ExtendedOutputsSpec{ExtendedOutputsSpec::Explicit{OutputsSpec::Names{"a"}}}},
std::pair{"names", ExtendedOutputsSpec{ExtendedOutputsSpec::Explicit{OutputsSpec::Names{"a", "b"}}}}));
#undef TEST_JSON #undef TEST_JSON

View file

@ -7,7 +7,7 @@
#include "nix/store/path-regex.hh" #include "nix/store/path-regex.hh"
#include "nix/store/store-api.hh" #include "nix/store/store-api.hh"
#include "nix/util/tests/characterization.hh" #include "nix/util/tests/json-characterization.hh"
#include "nix/store/tests/libstore.hh" #include "nix/store/tests/libstore.hh"
#include "nix/store/tests/path.hh" #include "nix/store/tests/path.hh"
@ -16,7 +16,7 @@ namespace nix {
#define STORE_DIR "/nix/store/" #define STORE_DIR "/nix/store/"
#define HASH_PART "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q" #define HASH_PART "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q"
class StorePathTest : public CharacterizationTest, public LibStoreTest class StorePathTest : public virtual CharacterizationTest, public LibStoreTest
{ {
std::filesystem::path unitTestData = getUnitTestData() / "store-path"; std::filesystem::path unitTestData = getUnitTestData() / "store-path";
@ -149,27 +149,30 @@ RC_GTEST_FIXTURE_PROP(StorePathTest, prop_check_regex_eq_parse, ())
using nlohmann::json; using nlohmann::json;
#define TEST_JSON(FIXTURE, NAME, VAL) \ struct StorePathJsonTest : StorePathTest,
static const StorePath NAME = VAL; \ JsonCharacterizationTest<StorePath>,
\ ::testing::WithParamInterface<std::pair<std::string_view, StorePath>>
TEST_F(FIXTURE, NAME##_from_json) \ {};
{ \
readTest(#NAME ".json", [&](const auto & encoded_) { \
auto encoded = json::parse(encoded_); \
StorePath got = static_cast<StorePath>(encoded); \
ASSERT_EQ(got, NAME); \
}); \
} \
\
TEST_F(FIXTURE, NAME##_to_json) \
{ \
writeTest( \
#NAME ".json", \
[&]() -> json { return static_cast<json>(NAME); }, \
[](const auto & file) { return json::parse(readFile(file)); }, \
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
}
TEST_JSON(StorePathTest, simple, StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"}); TEST_P(StorePathJsonTest, from_json)
{
auto & [name, expected] = GetParam();
readJsonTest(name, expected);
}
TEST_P(StorePathJsonTest, to_json)
{
auto & [name, value] = GetParam();
writeJsonTest(name, value);
}
INSTANTIATE_TEST_SUITE_P(
StorePathJSON,
StorePathJsonTest,
::testing::Values(
std::pair{
"simple",
StorePath{"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"},
}));
} // namespace nix } // namespace nix

View file

@ -6,12 +6,12 @@
#include "nix/store/store-api.hh" #include "nix/store/store-api.hh"
#include "nix/util/tests/characterization.hh" #include "nix/util/tests/json-characterization.hh"
#include "nix/store/tests/libstore.hh" #include "nix/store/tests/libstore.hh"
namespace nix { namespace nix {
class RealisationTest : public CharacterizationTest, public LibStoreTest class RealisationTest : public JsonCharacterizationTest<Realisation>, public LibStoreTest
{ {
std::filesystem::path unitTestData = getUnitTestData() / "realisation"; std::filesystem::path unitTestData = getUnitTestData() / "realisation";
@ -34,22 +34,14 @@ struct RealisationJsonTest : RealisationTest, ::testing::WithParamInterface<std:
TEST_P(RealisationJsonTest, from_json) TEST_P(RealisationJsonTest, from_json)
{ {
auto [name, expected] = GetParam(); const auto & [name, expected] = GetParam();
readTest(name + ".json", [&](const auto & encoded_) { readJsonTest(name, expected);
auto encoded = json::parse(encoded_);
Realisation got = static_cast<Realisation>(encoded);
ASSERT_EQ(got, expected);
});
} }
TEST_P(RealisationJsonTest, to_json) TEST_P(RealisationJsonTest, to_json)
{ {
auto [name, value] = GetParam(); const auto & [name, value] = GetParam();
writeTest( writeJsonTest(name, value);
name + ".json",
[&]() -> json { return static_cast<json>(value); },
[](const auto & file) { return json::parse(readFile(file)); },
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); });
} }
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(

View file

@ -1257,14 +1257,18 @@ void Derivation::checkInvariants(Store & store, const StorePath & drvPath) const
const Hash impureOutputHash = hashString(HashAlgorithm::SHA256, "impure"); const Hash impureOutputHash = hashString(HashAlgorithm::SHA256, "impure");
nlohmann::json DerivationOutput::toJSON(std::string_view drvName, OutputNameView outputName) const nlohmann::json DerivationOutput::toJSON() const
{ {
nlohmann::json res = nlohmann::json::object(); nlohmann::json res = nlohmann::json::object();
std::visit( std::visit(
overloaded{ overloaded{
[&](const DerivationOutput::InputAddressed & doi) { res["path"] = doi.path; }, [&](const DerivationOutput::InputAddressed & doi) { res["path"] = doi.path; },
[&](const DerivationOutput::CAFixed & dof) { [&](const DerivationOutput::CAFixed & dof) {
// res["path"] = dof.path(store, drvName, outputName); /* it would be nice to output the path for user convenience, but
this would require us to know the store dir. */
#if 0
res["path"] = dof.path(store, drvName, outputName);
#endif
res["method"] = std::string{dof.ca.method.render()}; res["method"] = std::string{dof.ca.method.render()};
res["hashAlgo"] = printHashAlgo(dof.ca.hash.algo); res["hashAlgo"] = printHashAlgo(dof.ca.hash.algo);
res["hash"] = dof.ca.hash.to_string(HashFormat::Base16, false); res["hash"] = dof.ca.hash.to_string(HashFormat::Base16, false);
@ -1285,11 +1289,8 @@ nlohmann::json DerivationOutput::toJSON(std::string_view drvName, OutputNameView
return res; return res;
} }
DerivationOutput DerivationOutput::fromJSON( DerivationOutput
std::string_view drvName, DerivationOutput::fromJSON(const nlohmann::json & _json, const ExperimentalFeatureSettings & xpSettings)
OutputNameView outputName,
const nlohmann::json & _json,
const ExperimentalFeatureSettings & xpSettings)
{ {
std::set<std::string_view> keys; std::set<std::string_view> keys;
auto & json = getObject(_json); auto & json = getObject(_json);
@ -1321,6 +1322,8 @@ DerivationOutput DerivationOutput::fromJSON(
.hash = Hash::parseNonSRIUnprefixed(getString(valueAt(json, "hash")), hashAlgo), .hash = Hash::parseNonSRIUnprefixed(getString(valueAt(json, "hash")), hashAlgo),
}, },
}; };
/* We no longer produce this (denormalized) field (for the
reasons described above), so we don't need to check it. */
#if 0 #if 0
if (dof.path(store, drvName, outputName) != static_cast<StorePath>(valueAt(json, "path"))) if (dof.path(store, drvName, outputName) != static_cast<StorePath>(valueAt(json, "path")))
throw Error("Path doesn't match derivation output"); throw Error("Path doesn't match derivation output");
@ -1367,7 +1370,7 @@ nlohmann::json Derivation::toJSON() const
nlohmann::json & outputsObj = res["outputs"]; nlohmann::json & outputsObj = res["outputs"];
outputsObj = nlohmann::json::object(); outputsObj = nlohmann::json::object();
for (auto & [outputName, output] : outputs) { for (auto & [outputName, output] : outputs) {
outputsObj[outputName] = output.toJSON(name, outputName); outputsObj[outputName] = output;
} }
} }
@ -1427,8 +1430,7 @@ Derivation Derivation::fromJSON(const nlohmann::json & _json, const Experimental
try { try {
auto outputs = getObject(valueAt(json, "outputs")); auto outputs = getObject(valueAt(json, "outputs"));
for (auto & [outputName, output] : outputs) { for (auto & [outputName, output] : outputs) {
res.outputs.insert_or_assign( res.outputs.insert_or_assign(outputName, DerivationOutput::fromJSON(output, xpSettings));
outputName, DerivationOutput::fromJSON(res.name, outputName, output, xpSettings));
} }
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while reading key 'outputs'"); e.addTrace({}, "while reading key 'outputs'");
@ -1489,6 +1491,16 @@ namespace nlohmann {
using namespace nix; using namespace nix;
DerivationOutput adl_serializer<DerivationOutput>::from_json(const json & json)
{
return DerivationOutput::fromJSON(json);
}
void adl_serializer<DerivationOutput>::to_json(json & json, const DerivationOutput & c)
{
json = c.toJSON();
}
Derivation adl_serializer<Derivation>::from_json(const json & json) Derivation adl_serializer<Derivation>::from_json(const json & json)
{ {
return Derivation::fromJSON(json); return Derivation::fromJSON(json);

View file

@ -135,15 +135,12 @@ struct DerivationOutput
std::optional<StorePath> std::optional<StorePath>
path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const; path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const;
nlohmann::json toJSON(std::string_view drvName, OutputNameView outputName) const; nlohmann::json toJSON() const;
/** /**
* @param xpSettings Stop-gap to avoid globals during unit tests. * @param xpSettings Stop-gap to avoid globals during unit tests.
*/ */
static DerivationOutput fromJSON( static DerivationOutput
std::string_view drvName, fromJSON(const nlohmann::json & json, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
OutputNameView outputName,
const nlohmann::json & json,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
}; };
typedef std::map<std::string, DerivationOutput> DerivationOutputs; typedef std::map<std::string, DerivationOutput> DerivationOutputs;
@ -540,4 +537,5 @@ std::string hashPlaceholder(const OutputNameView outputName);
} // namespace nix } // namespace nix
JSON_IMPL(nix::DerivationOutput)
JSON_IMPL(nix::Derivation) JSON_IMPL(nix::Derivation)

View file

@ -0,0 +1,54 @@
#pragma once
///@file
#include <gtest/gtest.h>
#include <nlohmann/json.hpp>
#include "nix/util/types.hh"
#include "nix/util/file-system.hh"
#include "nix/util/tests/characterization.hh"
namespace nix {
/**
* Mixin class for writing characterization tests for `nlohmann::json`
* conversions for a given type.
*/
template<typename T>
struct JsonCharacterizationTest : virtual CharacterizationTest
{
/**
* Golden test for reading
*
* @param test hook that takes the contents of the file and does the
* actual work
*/
void readJsonTest(PathView testStem, const T & expected)
{
using namespace nlohmann;
readTest(Path{testStem} + ".json", [&](const auto & encodedRaw) {
auto encoded = json::parse(encodedRaw);
T decoded = adl_serializer<T>::from_json(encoded);
ASSERT_EQ(decoded, expected);
});
}
/**
* Golden test for writing
*
* @param test hook that produces contents of the file and does the
* actual work
*/
void writeJsonTest(PathView testStem, const T & value)
{
using namespace nlohmann;
writeTest(
Path{testStem} + ".json",
[&]() -> json { return static_cast<json>(value); },
[](const auto & file) { return json::parse(readFile(file)); },
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); });
}
};
} // namespace nix

View file

@ -7,6 +7,7 @@ headers = files(
'gmock-matchers.hh', 'gmock-matchers.hh',
'gtest-with-params.hh', 'gtest-with-params.hh',
'hash.hh', 'hash.hh',
'json-characterization.hh',
'nix_api_util.hh', 'nix_api_util.hh',
'string_callback.hh', 'string_callback.hh',
) )

View file

@ -102,14 +102,14 @@ struct RandomPeekSort : public ::testing::TestWithParam<
void SetUp() override void SetUp() override
{ {
auto [maxSize, min, max, iterations] = GetParam(); const auto & [maxSize, min, max, iterations] = GetParam();
urng_ = std::mt19937(GTEST_FLAG_GET(random_seed)); urng_ = std::mt19937(GTEST_FLAG_GET(random_seed));
distribution_ = std::uniform_int_distribution<int>(min, max); distribution_ = std::uniform_int_distribution<int>(min, max);
} }
auto regenerate() auto regenerate()
{ {
auto [maxSize, min, max, iterations] = GetParam(); const auto & [maxSize, min, max, iterations] = GetParam();
std::size_t dataSize = std::uniform_int_distribution<std::size_t>(0, maxSize)(urng_); std::size_t dataSize = std::uniform_int_distribution<std::size_t>(0, maxSize)(urng_);
data_.resize(dataSize); data_.resize(dataSize);
std::generate(data_.begin(), data_.end(), [&]() { return distribution_(urng_); }); std::generate(data_.begin(), data_.end(), [&]() { return distribution_(urng_); });
@ -118,7 +118,7 @@ struct RandomPeekSort : public ::testing::TestWithParam<
TEST_P(RandomPeekSort, defaultComparator) TEST_P(RandomPeekSort, defaultComparator)
{ {
auto [maxSize, min, max, iterations] = GetParam(); const auto & [maxSize, min, max, iterations] = GetParam();
for (std::size_t i = 0; i < iterations; ++i) { for (std::size_t i = 0; i < iterations; ++i) {
regenerate(); regenerate();
@ -132,7 +132,7 @@ TEST_P(RandomPeekSort, defaultComparator)
TEST_P(RandomPeekSort, greater) TEST_P(RandomPeekSort, greater)
{ {
auto [maxSize, min, max, iterations] = GetParam(); const auto & [maxSize, min, max, iterations] = GetParam();
for (std::size_t i = 0; i < iterations; ++i) { for (std::size_t i = 0; i < iterations; ++i) {
regenerate(); regenerate();
@ -146,7 +146,7 @@ TEST_P(RandomPeekSort, greater)
TEST_P(RandomPeekSort, brokenComparator) TEST_P(RandomPeekSort, brokenComparator)
{ {
auto [maxSize, min, max, iterations] = GetParam(); const auto & [maxSize, min, max, iterations] = GetParam();
/* This is a pretty nice way of modeling a worst-case scenario for a broken comparator. /* This is a pretty nice way of modeling a worst-case scenario for a broken comparator.
If the sorting algorithm doesn't break in such case, then surely all deterministic If the sorting algorithm doesn't break in such case, then surely all deterministic
@ -170,7 +170,7 @@ TEST_P(RandomPeekSort, brokenComparator)
TEST_P(RandomPeekSort, stability) TEST_P(RandomPeekSort, stability)
{ {
auto [maxSize, min, max, iterations] = GetParam(); const auto & [maxSize, min, max, iterations] = GetParam();
for (std::size_t i = 0; i < iterations; ++i) { for (std::size_t i = 0; i < iterations; ++i) {
regenerate(); regenerate();