diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock index 80e31a8ec..73724b57a 100644 --- a/tvix/Cargo.lock +++ b/tvix/Cargo.lock @@ -1045,6 +1045,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "ed25519" version = "2.2.3" @@ -1248,6 +1254,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "fuse-backend-rs" version = "0.12.0" @@ -2174,6 +2186,32 @@ dependencies = [ "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]] name = "multimap" version = "0.10.0" @@ -2290,6 +2328,7 @@ dependencies = [ "glob", "hex-literal", "mimalloc", + "mockall", "nix-compat-derive", "nom", "num-traits", @@ -2792,6 +2831,32 @@ dependencies = [ "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]] name = "pretty_assertions" version = "1.4.1" @@ -3931,6 +3996,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "test-strategy" version = "0.2.1" diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix index 42f793d1d..5045d5149 100644 --- a/tvix/Cargo.nix +++ b/tvix/Cargo.nix @@ -3332,6 +3332,19 @@ rec { features = { }; resolvedDefaultFeatures = [ "default" ]; }; + "downcast" = rec { + crateName = "downcast"; + version = "0.11.0"; + edition = "2018"; + sha256 = "1wa78ahlc57wmqyq2ncr80l7plrkgz57xsg7kfzgpcnqac8gld8l"; + authors = [ + "Felix Köpge " + ]; + features = { + "default" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; "ed25519" = rec { crateName = "ed25519"; version = "2.2.3"; @@ -3930,6 +3943,18 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "default" "std" ]; }; + "fragile" = rec { + crateName = "fragile"; + version = "2.0.0"; + edition = "2018"; + sha256 = "1ajfdnwdn921bhjlzyvsqvdgci8ab40ln6w9ly422lf8svb428bc"; + authors = [ + "Armin Ronacher " + ]; + features = { + "slab" = [ "dep:slab" ]; + }; + }; "fuse-backend-rs" = rec { crateName = "fuse-backend-rs"; version = "0.12.0"; @@ -6794,6 +6819,77 @@ rec { }; resolvedDefaultFeatures = [ "net" "os-ext" "os-poll" ]; }; + "mockall" = rec { + crateName = "mockall"; + version = "0.13.1"; + edition = "2021"; + sha256 = "1lir70dd9cnsjlf20gi3i51ha9n7mlrkx74bx5gfszlcdk6bz9ir"; + authors = [ + "Alan Somers " + ]; + 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 " + ]; + 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 { crateName = "multimap"; version = "0.10.0"; @@ -7295,6 +7391,10 @@ rec { name = "mimalloc"; packageId = "mimalloc"; } + { + name = "mockall"; + packageId = "mockall"; + } { name = "pretty_assertions"; packageId = "pretty_assertions"; @@ -8891,6 +8991,64 @@ rec { }; resolvedDefaultFeatures = [ "simd" "std" ]; }; + "predicates" = rec { + crateName = "predicates"; + version = "3.1.2"; + edition = "2021"; + sha256 = "15rcyjax4ykflw5425wsyzcfkgl08c9zsa8sdlsrmhj0fv68d43y"; + authors = [ + "Nick Stevens " + ]; + 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 " + ]; + + }; + "predicates-tree" = rec { + crateName = "predicates-tree"; + version = "1.0.11"; + edition = "2021"; + sha256 = "04zv0i9pjfrldnvyxf4y07n243nvk3n4g03w2k6nccgdjp8l1ds1"; + libName = "predicates_tree"; + authors = [ + "Nick Stevens " + ]; + dependencies = [ + { + name = "predicates-core"; + packageId = "predicates-core"; + } + { + name = "termtree"; + packageId = "termtree"; + } + ]; + + }; "pretty_assertions" = rec { crateName = "pretty_assertions"; version = "1.4.1"; @@ -12654,6 +12812,13 @@ rec { } ]; + }; + "termtree" = rec { + crateName = "termtree"; + version = "0.4.1"; + edition = "2018"; + sha256 = "0xkal5l2r3r9p9j90x35qy4npbdwxz4gskvbijs6msymaangas9k"; + }; "test-strategy" = rec { crateName = "test-strategy"; diff --git a/tvix/nix-compat/Cargo.toml b/tvix/nix-compat/Cargo.toml index 160eb2c20..dc0943476 100644 --- a/tvix/nix-compat/Cargo.toml +++ b/tvix/nix-compat/Cargo.toml @@ -47,6 +47,7 @@ criterion = { workspace = true, features = ["html_reports"] } futures = { workspace = true } hex-literal = { workspace = true } mimalloc = { workspace = true } +mockall = "0.13.1" pretty_assertions = { workspace = true } proptest = { workspace = true, features = ["std", "alloc", "tempfile"] } rstest = { workspace = true } diff --git a/tvix/nix-compat/src/nix_daemon/handler.rs b/tvix/nix-compat/src/nix_daemon/handler.rs index 4f4361211..6fb45bdb7 100644 --- a/tvix/nix-compat/src/nix_daemon/handler.rs +++ b/tvix/nix-compat/src/nix_daemon/handler.rs @@ -254,45 +254,17 @@ where #[cfg(test)] mod tests { use super::*; - use std::{io::Result, sync::Arc}; + use std::{io::ErrorKind, sync::Arc}; + use mockall::predicate; use tokio::io::AsyncWriteExt; use crate::{ - nix_daemon::types::UnkeyedValidPathInfo, + nix_daemon::MockNixDaemonIO, wire::ProtocolVersion, 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, - ) -> Result> { - Ok(None) - } - - async fn query_path_from_hash_part( - &self, - _hash: &[u8], - ) -> Result> { - Ok(None) - } - - async fn add_to_store_nar( - &self, - _request: crate::nix_daemon::types::AddToStoreNarRequest, - _reader: &mut R, - ) -> Result<()> - where - R: tokio::io::AsyncRead + Send + Unpin, - { - Ok(()) - } - } - #[tokio::test] async fn test_daemon_initialization() { let mut builder = tokio_test::io::Builder::new(); @@ -332,10 +304,125 @@ mod tests { .write(&[115, 116, 108, 97, 0, 0, 0, 0]) .build(); - let daemon = NixDaemon::initialize(Arc::new(MockDaemonIO {}), test_conn) + let mock = MockNixDaemonIO::new(); + let daemon = NixDaemon::initialize(Arc::new(mock), test_conn) .await .unwrap(); assert_eq!(daemon.client_settings, ClientSettings::default()); assert_eq!(daemon.protocol_version, ProtocolVersion::from_parts(1, 35)); } + + async fn serialize(req: &T, protocol_version: ProtocolVersion) -> Vec + where + T: NixSerialize + Send, + { + let mut result: Vec = 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( + resp: &Result, + protocol_version: ProtocolVersion, + ) -> Vec + where + T: NixSerialize + Send, + { + let mut result: Vec = 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 = StorePath::::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::::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 = StorePath::::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::::into(Operation::IsValidPath).to_le_bytes()); + handle.read(&serialize(&path, version).await); + handle.write(&respond::(&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() + ); + } } diff --git a/tvix/nix-compat/src/nix_daemon/mod.rs b/tvix/nix-compat/src/nix_daemon/mod.rs index b1fd15c04..11d236a6a 100644 --- a/tvix/nix-compat/src/nix_daemon/mod.rs +++ b/tvix/nix-compat/src/nix_daemon/mod.rs @@ -13,7 +13,11 @@ pub mod framing; pub mod handler; pub mod types; +#[cfg(test)] +use mockall::automock; + /// Represents all possible operations over the nix-daemon protocol. +#[cfg_attr(test, automock)] pub trait NixDaemonIO: Sync { fn is_valid_path( &self, @@ -62,6 +66,7 @@ pub trait NixDaemonIO: Sync { } } + #[cfg_attr(test, mockall::concretize)] fn add_to_store_nar( &self, request: AddToStoreNarRequest,