chore(tvix/nix-compat): basic daemon handler tests

This change adds tests for basic request/response handling as per nix
daemon STDERR_* protocol.

Change-Id: Ia6a1904e14955551b11f776b6ccb595fa8984513
Reviewed-on: https://cl.tvl.fyi/c/depot/+/12852
Tested-by: BuildkiteCI
Reviewed-by: flokli <flokli@flokli.de>
Autosubmit: Vladimir Kryachko <v.kryachko@gmail.com>
This commit is contained in:
Vova Kryachko 2024-11-29 10:31:47 -05:00 committed by clbot
parent 8d4a0ac008
commit 88d51c9c16
5 changed files with 361 additions and 32 deletions

71
tvix/Cargo.lock generated
View file

@ -1045,6 +1045,12 @@ dependencies = [
"litrs", "litrs",
] ]
[[package]]
name = "downcast"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
[[package]] [[package]]
name = "ed25519" name = "ed25519"
version = "2.2.3" version = "2.2.3"
@ -1248,6 +1254,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fragile"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
[[package]] [[package]]
name = "fuse-backend-rs" name = "fuse-backend-rs"
version = "0.12.0" version = "0.12.0"
@ -2174,6 +2186,32 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "mockall"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2"
dependencies = [
"cfg-if",
"downcast",
"fragile",
"mockall_derive",
"predicates",
"predicates-tree",
]
[[package]]
name = "mockall_derive"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn 2.0.79",
]
[[package]] [[package]]
name = "multimap" name = "multimap"
version = "0.10.0" version = "0.10.0"
@ -2290,6 +2328,7 @@ dependencies = [
"glob", "glob",
"hex-literal", "hex-literal",
"mimalloc", "mimalloc",
"mockall",
"nix-compat-derive", "nix-compat-derive",
"nom", "nom",
"num-traits", "num-traits",
@ -2792,6 +2831,32 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "predicates"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97"
dependencies = [
"anstyle",
"predicates-core",
]
[[package]]
name = "predicates-core"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931"
[[package]]
name = "predicates-tree"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13"
dependencies = [
"predicates-core",
"termtree",
]
[[package]] [[package]]
name = "pretty_assertions" name = "pretty_assertions"
version = "1.4.1" version = "1.4.1"
@ -3931,6 +3996,12 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "termtree"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
[[package]] [[package]]
name = "test-strategy" name = "test-strategy"
version = "0.2.1" version = "0.2.1"

View file

@ -3332,6 +3332,19 @@ rec {
features = { }; features = { };
resolvedDefaultFeatures = [ "default" ]; resolvedDefaultFeatures = [ "default" ];
}; };
"downcast" = rec {
crateName = "downcast";
version = "0.11.0";
edition = "2018";
sha256 = "1wa78ahlc57wmqyq2ncr80l7plrkgz57xsg7kfzgpcnqac8gld8l";
authors = [
"Felix Köpge <fkoep@mailbox.org>"
];
features = {
"default" = [ "std" ];
};
resolvedDefaultFeatures = [ "default" "std" ];
};
"ed25519" = rec { "ed25519" = rec {
crateName = "ed25519"; crateName = "ed25519";
version = "2.2.3"; version = "2.2.3";
@ -3930,6 +3943,18 @@ rec {
}; };
resolvedDefaultFeatures = [ "alloc" "default" "std" ]; resolvedDefaultFeatures = [ "alloc" "default" "std" ];
}; };
"fragile" = rec {
crateName = "fragile";
version = "2.0.0";
edition = "2018";
sha256 = "1ajfdnwdn921bhjlzyvsqvdgci8ab40ln6w9ly422lf8svb428bc";
authors = [
"Armin Ronacher <armin.ronacher@active-4.com>"
];
features = {
"slab" = [ "dep:slab" ];
};
};
"fuse-backend-rs" = rec { "fuse-backend-rs" = rec {
crateName = "fuse-backend-rs"; crateName = "fuse-backend-rs";
version = "0.12.0"; version = "0.12.0";
@ -6794,6 +6819,77 @@ rec {
}; };
resolvedDefaultFeatures = [ "net" "os-ext" "os-poll" ]; resolvedDefaultFeatures = [ "net" "os-ext" "os-poll" ];
}; };
"mockall" = rec {
crateName = "mockall";
version = "0.13.1";
edition = "2021";
sha256 = "1lir70dd9cnsjlf20gi3i51ha9n7mlrkx74bx5gfszlcdk6bz9ir";
authors = [
"Alan Somers <asomers@gmail.com>"
];
dependencies = [
{
name = "cfg-if";
packageId = "cfg-if";
}
{
name = "downcast";
packageId = "downcast";
}
{
name = "fragile";
packageId = "fragile";
}
{
name = "mockall_derive";
packageId = "mockall_derive";
}
{
name = "predicates";
packageId = "predicates";
usesDefaultFeatures = false;
}
{
name = "predicates-tree";
packageId = "predicates-tree";
}
];
features = {
"nightly" = [ "mockall_derive/nightly_derive" "downcast/nightly" ];
};
};
"mockall_derive" = rec {
crateName = "mockall_derive";
version = "0.13.1";
edition = "2021";
sha256 = "1608qajqrz23xbvv81alc6wm4l24as1bsqg4shdh3sggq8231ji5";
procMacro = true;
authors = [
"Alan Somers <asomers@gmail.com>"
];
dependencies = [
{
name = "cfg-if";
packageId = "cfg-if";
}
{
name = "proc-macro2";
packageId = "proc-macro2";
}
{
name = "quote";
packageId = "quote";
}
{
name = "syn";
packageId = "syn 2.0.79";
features = [ "extra-traits" "full" ];
}
];
features = {
"nightly_derive" = [ "proc-macro2/nightly" ];
};
};
"multimap" = rec { "multimap" = rec {
crateName = "multimap"; crateName = "multimap";
version = "0.10.0"; version = "0.10.0";
@ -7295,6 +7391,10 @@ rec {
name = "mimalloc"; name = "mimalloc";
packageId = "mimalloc"; packageId = "mimalloc";
} }
{
name = "mockall";
packageId = "mockall";
}
{ {
name = "pretty_assertions"; name = "pretty_assertions";
packageId = "pretty_assertions"; packageId = "pretty_assertions";
@ -8891,6 +8991,64 @@ rec {
}; };
resolvedDefaultFeatures = [ "simd" "std" ]; resolvedDefaultFeatures = [ "simd" "std" ];
}; };
"predicates" = rec {
crateName = "predicates";
version = "3.1.2";
edition = "2021";
sha256 = "15rcyjax4ykflw5425wsyzcfkgl08c9zsa8sdlsrmhj0fv68d43y";
authors = [
"Nick Stevens <nick@bitcurry.com>"
];
dependencies = [
{
name = "anstyle";
packageId = "anstyle";
}
{
name = "predicates-core";
packageId = "predicates-core";
}
];
features = {
"default" = [ "diff" "regex" "float-cmp" "normalize-line-endings" "color" ];
"diff" = [ "dep:difflib" ];
"float-cmp" = [ "dep:float-cmp" ];
"normalize-line-endings" = [ "dep:normalize-line-endings" ];
"regex" = [ "dep:regex" ];
};
};
"predicates-core" = rec {
crateName = "predicates-core";
version = "1.0.8";
edition = "2021";
sha256 = "0c8rl6d7qkcl773fw539h61fhlgdg7v9yswwb536hpg7x2z7g0df";
libName = "predicates_core";
authors = [
"Nick Stevens <nick@bitcurry.com>"
];
};
"predicates-tree" = rec {
crateName = "predicates-tree";
version = "1.0.11";
edition = "2021";
sha256 = "04zv0i9pjfrldnvyxf4y07n243nvk3n4g03w2k6nccgdjp8l1ds1";
libName = "predicates_tree";
authors = [
"Nick Stevens <nick@bitcurry.com>"
];
dependencies = [
{
name = "predicates-core";
packageId = "predicates-core";
}
{
name = "termtree";
packageId = "termtree";
}
];
};
"pretty_assertions" = rec { "pretty_assertions" = rec {
crateName = "pretty_assertions"; crateName = "pretty_assertions";
version = "1.4.1"; version = "1.4.1";
@ -12654,6 +12812,13 @@ rec {
} }
]; ];
};
"termtree" = rec {
crateName = "termtree";
version = "0.4.1";
edition = "2018";
sha256 = "0xkal5l2r3r9p9j90x35qy4npbdwxz4gskvbijs6msymaangas9k";
}; };
"test-strategy" = rec { "test-strategy" = rec {
crateName = "test-strategy"; crateName = "test-strategy";

View file

@ -47,6 +47,7 @@ criterion = { workspace = true, features = ["html_reports"] }
futures = { workspace = true } futures = { workspace = true }
hex-literal = { workspace = true } hex-literal = { workspace = true }
mimalloc = { workspace = true } mimalloc = { workspace = true }
mockall = "0.13.1"
pretty_assertions = { workspace = true } pretty_assertions = { workspace = true }
proptest = { workspace = true, features = ["std", "alloc", "tempfile"] } proptest = { workspace = true, features = ["std", "alloc", "tempfile"] }
rstest = { workspace = true } rstest = { workspace = true }

View file

@ -254,45 +254,17 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::{io::Result, sync::Arc}; use std::{io::ErrorKind, sync::Arc};
use mockall::predicate;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use crate::{ use crate::{
nix_daemon::types::UnkeyedValidPathInfo, nix_daemon::MockNixDaemonIO,
wire::ProtocolVersion, wire::ProtocolVersion,
worker_protocol::{ClientSettings, WORKER_MAGIC_1, WORKER_MAGIC_2}, worker_protocol::{ClientSettings, WORKER_MAGIC_1, WORKER_MAGIC_2},
}; };
struct MockDaemonIO {}
impl NixDaemonIO for MockDaemonIO {
async fn query_path_info(
&self,
_path: &crate::store_path::StorePath<String>,
) -> Result<Option<UnkeyedValidPathInfo>> {
Ok(None)
}
async fn query_path_from_hash_part(
&self,
_hash: &[u8],
) -> Result<Option<UnkeyedValidPathInfo>> {
Ok(None)
}
async fn add_to_store_nar<R>(
&self,
_request: crate::nix_daemon::types::AddToStoreNarRequest,
_reader: &mut R,
) -> Result<()>
where
R: tokio::io::AsyncRead + Send + Unpin,
{
Ok(())
}
}
#[tokio::test] #[tokio::test]
async fn test_daemon_initialization() { async fn test_daemon_initialization() {
let mut builder = tokio_test::io::Builder::new(); let mut builder = tokio_test::io::Builder::new();
@ -332,10 +304,125 @@ mod tests {
.write(&[115, 116, 108, 97, 0, 0, 0, 0]) .write(&[115, 116, 108, 97, 0, 0, 0, 0])
.build(); .build();
let daemon = NixDaemon::initialize(Arc::new(MockDaemonIO {}), test_conn) let mock = MockNixDaemonIO::new();
let daemon = NixDaemon::initialize(Arc::new(mock), test_conn)
.await .await
.unwrap(); .unwrap();
assert_eq!(daemon.client_settings, ClientSettings::default()); assert_eq!(daemon.client_settings, ClientSettings::default());
assert_eq!(daemon.protocol_version, ProtocolVersion::from_parts(1, 35)); assert_eq!(daemon.protocol_version, ProtocolVersion::from_parts(1, 35));
} }
async fn serialize<T>(req: &T, protocol_version: ProtocolVersion) -> Vec<u8>
where
T: NixSerialize + Send,
{
let mut result: Vec<u8> = Vec::new();
let mut w = NixWriter::builder()
.set_version(protocol_version)
.build(&mut result);
w.write_value(req).await.unwrap();
w.flush().await.unwrap();
result
}
async fn respond<T>(
resp: &Result<T, std::io::Error>,
protocol_version: ProtocolVersion,
) -> Vec<u8>
where
T: NixSerialize + Send,
{
let mut result: Vec<u8> = Vec::new();
let mut w = NixWriter::builder()
.set_version(protocol_version)
.build(&mut result);
match resp {
Ok(value) => {
w.write_value(&STDERR_LAST).await.unwrap();
w.write_value(value).await.unwrap();
}
Err(e) => {
w.write_value(&STDERR_ERROR).await.unwrap();
w.write_value(&NixError::new(format!("{:?}", e)))
.await
.unwrap();
}
}
w.flush().await.unwrap();
result
}
#[tokio::test]
async fn test_handle_is_valid_path_ok() {
let version = ProtocolVersion::from_parts(1, 37);
let (io, mut handle) = tokio_test::io::Builder::new().build_with_handle();
let mut mock = MockNixDaemonIO::new();
let (reader, writer) = split(io);
let path: StorePath<String> = StorePath::<String>::from_absolute_path(
"/nix/store/33l4p0pn0mybmqzaxfkpppyh7vx1c74p-hello-2.12.1".as_bytes(),
)
.unwrap();
mock.expect_is_valid_path()
.with(predicate::eq(path.clone()))
.times(1)
.returning(|_| Box::pin(async { Ok(true) }));
handle.read(&Into::<u64>::into(Operation::IsValidPath).to_le_bytes());
handle.read(&serialize(&path, version).await);
handle.write(&respond(&Ok(true), version).await);
drop(handle);
let mut daemon = NixDaemon::new(
Arc::new(mock),
version,
ClientSettings::default(),
NixReader::new(reader),
NixWriter::new(writer),
);
assert_eq!(
ErrorKind::UnexpectedEof,
daemon
.handle_client()
.await
.expect_err("Expecting eof")
.kind()
);
}
#[tokio::test]
async fn test_handle_is_valid_path_err() {
let version = ProtocolVersion::from_parts(1, 37);
let (io, mut handle) = tokio_test::io::Builder::new().build_with_handle();
let mut mock = MockNixDaemonIO::new();
let (reader, writer) = split(io);
let path: StorePath<String> = StorePath::<String>::from_absolute_path(
"/nix/store/33l4p0pn0mybmqzaxfkpppyh7vx1c74p-hello-2.12.1".as_bytes(),
)
.unwrap();
mock.expect_is_valid_path()
.with(predicate::eq(path.clone()))
.times(1)
.returning(|_| Box::pin(async { Err(std::io::Error::other("hello")) }));
handle.read(&Into::<u64>::into(Operation::IsValidPath).to_le_bytes());
handle.read(&serialize(&path, version).await);
handle.write(&respond::<bool>(&Err(std::io::Error::other("hello")), version).await);
drop(handle);
let mut daemon = NixDaemon::new(
Arc::new(mock),
version,
ClientSettings::default(),
NixReader::new(reader),
NixWriter::new(writer),
);
assert_eq!(
ErrorKind::UnexpectedEof,
daemon
.handle_client()
.await
.expect_err("Expecting eof")
.kind()
);
}
} }

View file

@ -13,7 +13,11 @@ pub mod framing;
pub mod handler; pub mod handler;
pub mod types; pub mod types;
#[cfg(test)]
use mockall::automock;
/// Represents all possible operations over the nix-daemon protocol. /// Represents all possible operations over the nix-daemon protocol.
#[cfg_attr(test, automock)]
pub trait NixDaemonIO: Sync { pub trait NixDaemonIO: Sync {
fn is_valid_path( fn is_valid_path(
&self, &self,
@ -62,6 +66,7 @@ pub trait NixDaemonIO: Sync {
} }
} }
#[cfg_attr(test, mockall::concretize)]
fn add_to_store_nar<R>( fn add_to_store_nar<R>(
&self, &self,
request: AddToStoreNarRequest, request: AddToStoreNarRequest,