mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-12 17:26:19 +02:00
Unit test some worker protocol serializers
Continue with the characterization testing idioms begun in
c70484454f
, but this time for unit tests.
Co-authored-by: Andreas Rammhold <andreas@rammhold.de>
This commit is contained in:
parent
f878b422b0
commit
7ff43435f9
11 changed files with 228 additions and 8 deletions
1
Makefile
1
Makefile
|
@ -22,6 +22,7 @@ makefiles = \
|
||||||
-include Makefile.config
|
-include Makefile.config
|
||||||
|
|
||||||
ifeq ($(tests), yes)
|
ifeq ($(tests), yes)
|
||||||
|
UNIT_TEST_ENV = _NIX_TEST_UNIT_DATA=unit-test-data
|
||||||
makefiles += \
|
makefiles += \
|
||||||
src/libutil/tests/local.mk \
|
src/libutil/tests/local.mk \
|
||||||
src/libstore/tests/local.mk \
|
src/libstore/tests/local.mk \
|
||||||
|
|
|
@ -2,14 +2,70 @@
|
||||||
|
|
||||||
## Unit-tests
|
## Unit-tests
|
||||||
|
|
||||||
The unit-tests for each Nix library (`libexpr`, `libstore`, etc..) are defined
|
The unit tests are defined using the [googletest] and [rapidcheck] frameworks.
|
||||||
under `src/{library_name}/tests` using the
|
|
||||||
[googletest](https://google.github.io/googletest/) and
|
[googletest]: https://google.github.io/googletest/
|
||||||
[rapidcheck](https://github.com/emil-e/rapidcheck) frameworks.
|
[rapidcheck]: https://github.com/emil-e/rapidcheck
|
||||||
|
|
||||||
|
### Source and header layout
|
||||||
|
|
||||||
|
> An example of some files, demonstrating much of what is described below
|
||||||
|
>
|
||||||
|
> ```
|
||||||
|
> src
|
||||||
|
> ├── libexpr
|
||||||
|
> │ ├── value/context.hh
|
||||||
|
> │ ├── value/context.cc
|
||||||
|
> │ │
|
||||||
|
> │ …
|
||||||
|
> └── tests
|
||||||
|
> │ ├── value/context.hh
|
||||||
|
> │ ├── value/context.cc
|
||||||
|
> │ │
|
||||||
|
> │ …
|
||||||
|
> │
|
||||||
|
> ├── unit-test-data
|
||||||
|
> │ ├── libstore
|
||||||
|
> │ │ ├── worker-protocol/content-address.bin
|
||||||
|
> │ │ …
|
||||||
|
> │ …
|
||||||
|
> …
|
||||||
|
> ```
|
||||||
|
|
||||||
|
The unit tests for each Nix library (`libnixexpr`, `libnixstore`, etc..) live inside a directory `src/${library_shortname}/tests` within the directory for the library (`src/${library_shortname}`).
|
||||||
|
|
||||||
|
The data is in `unit-test-data`, with one subdir per library, with the same name as where the code goes.
|
||||||
|
For example, `libnixstore` code is in `src/libstore`, and its test data is in `unit-test-data/libstore`.
|
||||||
|
The path to the `unit-test-data` directory is passed to the unit test executable with the environment variable `_NIX_TEST_UNIT_DATA`.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> Due to the way googletest works, downstream unit test executables will actually include and re-run upstream library tests.
|
||||||
|
> Therefore it is important that the same value for `_NIX_TEST_UNIT_DATA` be used with the tests for each library.
|
||||||
|
> That is why we have the test data nested within a single `unit-test-data` directory.
|
||||||
|
|
||||||
|
### Running tests
|
||||||
|
|
||||||
You can run the whole testsuite with `make check`, or the tests for a specific component with `make libfoo-tests_RUN`.
|
You can run the whole testsuite with `make check`, or the tests for a specific component with `make libfoo-tests_RUN`.
|
||||||
Finer-grained filtering is also possible using the [--gtest_filter](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) command-line option, or the `GTEST_FILTER` environment variable.
|
Finer-grained filtering is also possible using the [--gtest_filter](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) command-line option, or the `GTEST_FILTER` environment variable.
|
||||||
|
|
||||||
|
### Characterization testing
|
||||||
|
|
||||||
|
See [below](#characterization-testing-1) for a broader discussion of characterization testing.
|
||||||
|
|
||||||
|
Like with the functional characterization, `_NIX_TEST_ACCEPT=1` is also used.
|
||||||
|
For example:
|
||||||
|
```shell-session
|
||||||
|
$ _NIX_TEST_ACCEPT=1 make libstore-tests-exe_RUN
|
||||||
|
...
|
||||||
|
[ SKIPPED ] WorkerProtoTest.string_read
|
||||||
|
[ SKIPPED ] WorkerProtoTest.string_write
|
||||||
|
[ SKIPPED ] WorkerProtoTest.storePath_read
|
||||||
|
[ SKIPPED ] WorkerProtoTest.storePath_write
|
||||||
|
...
|
||||||
|
```
|
||||||
|
will regenerate the "golden master" expected result for the `libnixstore` characterization tests.
|
||||||
|
The characterization tests will mark themselves "skipped" since they regenerated the expected result instead of actually testing anything.
|
||||||
|
|
||||||
## Functional tests
|
## Functional tests
|
||||||
|
|
||||||
The functional tests reside under the `tests` directory and are listed in `tests/local.mk`.
|
The functional tests reside under the `tests` directory and are listed in `tests/local.mk`.
|
||||||
|
@ -124,9 +180,12 @@ This technique is to include the exact output/behavior of a former version of Ni
|
||||||
For example, this technique is used for the language tests, to check both the printed final value if evaluation was successful, and any errors and warnings encountered.
|
For example, this technique is used for the language tests, to check both the printed final value if evaluation was successful, and any errors and warnings encountered.
|
||||||
|
|
||||||
It is frequently useful to regenerate the expected output.
|
It is frequently useful to regenerate the expected output.
|
||||||
To do that, rerun the failed test with `_NIX_TEST_ACCEPT=1`.
|
To do that, rerun the failed test(s) with `_NIX_TEST_ACCEPT=1`.
|
||||||
(At least, this is the convention we've used for `tests/lang.sh`.
|
For example:
|
||||||
If we add more characterization testing we should always strive to be consistent.)
|
```bash
|
||||||
|
_NIX_TEST_ACCEPT=1 make tests/lang.sh.test
|
||||||
|
```
|
||||||
|
This convention is shared with the [characterization unit tests](#characterization-testing-1) too.
|
||||||
|
|
||||||
An interesting situation to document is the case when these tests are "overfitted".
|
An interesting situation to document is the case when these tests are "overfitted".
|
||||||
The language tests are, again, an example of this.
|
The language tests are, again, an example of this.
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
./precompiled-headers.h
|
./precompiled-headers.h
|
||||||
./src
|
./src
|
||||||
./tests
|
./tests
|
||||||
|
./unit-test-data
|
||||||
./COPYING
|
./COPYING
|
||||||
./scripts/local.mk
|
./scripts/local.mk
|
||||||
(fileset.fileFilter (f: lib.strings.hasPrefix "nix-profile" f.name) ./scripts)
|
(fileset.fileFilter (f: lib.strings.hasPrefix "nix-profile" f.name) ./scripts)
|
||||||
|
|
|
@ -87,6 +87,6 @@ define build-program
|
||||||
# Phony target to run this program (typically as a dependency of 'check').
|
# Phony target to run this program (typically as a dependency of 'check').
|
||||||
.PHONY: $(1)_RUN
|
.PHONY: $(1)_RUN
|
||||||
$(1)_RUN: $$($(1)_PATH)
|
$(1)_RUN: $$($(1)_PATH)
|
||||||
$(trace-test) $$($(1)_PATH)
|
$(trace-test) $$(UNIT_TEST_ENV) $$($(1)_PATH)
|
||||||
|
|
||||||
endef
|
endef
|
||||||
|
|
139
src/libstore/tests/worker-protocol.cc
Normal file
139
src/libstore/tests/worker-protocol.cc
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "worker-protocol.hh"
|
||||||
|
#include "worker-protocol-impl.hh"
|
||||||
|
#include "derived-path.hh"
|
||||||
|
#include "tests/libstore.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
class WorkerProtoTest : public LibStoreTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Path unitTestData = getEnv("_NIX_TEST_UNIT_DATA").value() + "/libstore/worker-protocol";
|
||||||
|
|
||||||
|
bool testAccept() {
|
||||||
|
return getEnv("_NIX_TEST_ACCEPT") == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
Path goldenMaster(std::string_view testStem) {
|
||||||
|
return unitTestData + "/" + testStem + ".bin";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Golden test for `T` reading
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
void readTest(PathView testStem, T value)
|
||||||
|
{
|
||||||
|
if (testAccept())
|
||||||
|
{
|
||||||
|
GTEST_SKIP() << "Cannot read golden master because another test is also updating it";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto expected = readFile(goldenMaster(testStem));
|
||||||
|
|
||||||
|
T got = ({
|
||||||
|
StringSource from { expected };
|
||||||
|
WorkerProto::Serialise<T>::read(
|
||||||
|
*store,
|
||||||
|
WorkerProto::ReadConn { .from = from });
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_EQ(got, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Golden test for `T` write
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
void writeTest(PathView testStem, const T & value)
|
||||||
|
{
|
||||||
|
auto file = goldenMaster(testStem);
|
||||||
|
|
||||||
|
StringSink to;
|
||||||
|
WorkerProto::write(
|
||||||
|
*store,
|
||||||
|
WorkerProto::WriteConn { .to = to },
|
||||||
|
value);
|
||||||
|
|
||||||
|
if (testAccept())
|
||||||
|
{
|
||||||
|
createDirs(dirOf(file));
|
||||||
|
writeFile(file, to.s);
|
||||||
|
GTEST_SKIP() << "Updating golden master";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto expected = readFile(file);
|
||||||
|
ASSERT_EQ(to.s, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CHARACTERIZATION_TEST(NAME, STEM, VALUE) \
|
||||||
|
TEST_F(WorkerProtoTest, NAME ## _read) { \
|
||||||
|
readTest(STEM, VALUE); \
|
||||||
|
} \
|
||||||
|
TEST_F(WorkerProtoTest, NAME ## _write) { \
|
||||||
|
writeTest(STEM, VALUE); \
|
||||||
|
}
|
||||||
|
|
||||||
|
CHARACTERIZATION_TEST(
|
||||||
|
string,
|
||||||
|
"string",
|
||||||
|
(std::tuple<std::string, std::string, std::string, std::string, std::string> {
|
||||||
|
"",
|
||||||
|
"hi",
|
||||||
|
"white rabbit",
|
||||||
|
"大白兔",
|
||||||
|
"oh no \0\0\0 what was that!",
|
||||||
|
}))
|
||||||
|
|
||||||
|
CHARACTERIZATION_TEST(
|
||||||
|
storePath,
|
||||||
|
"store-path",
|
||||||
|
(std::tuple<StorePath, StorePath> {
|
||||||
|
StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo" },
|
||||||
|
StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo-bar" },
|
||||||
|
}))
|
||||||
|
|
||||||
|
CHARACTERIZATION_TEST(
|
||||||
|
contentAddress,
|
||||||
|
"content-address",
|
||||||
|
(std::tuple<ContentAddress, ContentAddress, ContentAddress> {
|
||||||
|
ContentAddress {
|
||||||
|
.method = TextIngestionMethod {},
|
||||||
|
.hash = hashString(HashType::htSHA256, "Derive(...)"),
|
||||||
|
},
|
||||||
|
ContentAddress {
|
||||||
|
.method = FileIngestionMethod::Flat,
|
||||||
|
.hash = hashString(HashType::htSHA1, "blob blob..."),
|
||||||
|
},
|
||||||
|
ContentAddress {
|
||||||
|
.method = FileIngestionMethod::Recursive,
|
||||||
|
.hash = hashString(HashType::htSHA256, "(...)"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
CHARACTERIZATION_TEST(
|
||||||
|
derivedPath,
|
||||||
|
"derived-path",
|
||||||
|
(std::tuple<DerivedPath, DerivedPath> {
|
||||||
|
DerivedPath::Opaque {
|
||||||
|
.path = StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo" },
|
||||||
|
},
|
||||||
|
DerivedPath::Built {
|
||||||
|
.drvPath = makeConstantStorePathRef(StorePath {
|
||||||
|
"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv",
|
||||||
|
}),
|
||||||
|
.outputs = OutputsSpec::Names { "x", "y" },
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
}
|
|
@ -75,4 +75,20 @@ void WorkerProto::Serialise<std::map<K, V>>::write(const Store & store, WorkerPr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename... Ts>
|
||||||
|
std::tuple<Ts...> WorkerProto::Serialise<std::tuple<Ts...>>::read(const Store & store, WorkerProto::ReadConn conn)
|
||||||
|
{
|
||||||
|
return std::tuple<Ts...> {
|
||||||
|
WorkerProto::Serialise<Ts>::read(store, conn)...,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Ts>
|
||||||
|
void WorkerProto::Serialise<std::tuple<Ts...>>::write(const Store & store, WorkerProto::WriteConn conn, const std::tuple<Ts...> & res)
|
||||||
|
{
|
||||||
|
std::apply([&]<typename... Us>(const Us &... args) {
|
||||||
|
(WorkerProto::Serialise<Us>::write(store, conn, args), ...);
|
||||||
|
}, res);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ class Store;
|
||||||
struct Source;
|
struct Source;
|
||||||
|
|
||||||
// items being serialised
|
// items being serialised
|
||||||
|
class StorePath;
|
||||||
|
struct ContentAddress;
|
||||||
struct DerivedPath;
|
struct DerivedPath;
|
||||||
struct DrvOutput;
|
struct DrvOutput;
|
||||||
struct Realisation;
|
struct Realisation;
|
||||||
|
@ -220,6 +222,8 @@ template<typename T>
|
||||||
MAKE_WORKER_PROTO(std::vector<T>);
|
MAKE_WORKER_PROTO(std::vector<T>);
|
||||||
template<typename T>
|
template<typename T>
|
||||||
MAKE_WORKER_PROTO(std::set<T>);
|
MAKE_WORKER_PROTO(std::set<T>);
|
||||||
|
template<typename... Ts>
|
||||||
|
MAKE_WORKER_PROTO(std::tuple<Ts...>);
|
||||||
|
|
||||||
template<typename K, typename V>
|
template<typename K, typename V>
|
||||||
#define X_ std::map<K, V>
|
#define X_ std::map<K, V>
|
||||||
|
|
BIN
unit-test-data/libstore/worker-protocol/content-address.bin
Normal file
BIN
unit-test-data/libstore/worker-protocol/content-address.bin
Normal file
Binary file not shown.
BIN
unit-test-data/libstore/worker-protocol/derived-path.bin
Normal file
BIN
unit-test-data/libstore/worker-protocol/derived-path.bin
Normal file
Binary file not shown.
BIN
unit-test-data/libstore/worker-protocol/store-path.bin
Normal file
BIN
unit-test-data/libstore/worker-protocol/store-path.bin
Normal file
Binary file not shown.
BIN
unit-test-data/libstore/worker-protocol/string.bin
Normal file
BIN
unit-test-data/libstore/worker-protocol/string.bin
Normal file
Binary file not shown.
Loading…
Reference in a new issue