about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVova Kryachko <v.kryachko@gmail.com>2024-11-29T15·31-0500
committerclbot <clbot@tvl.fyi>2024-12-01T17·58+0000
commit88d51c9c1647c583fd70a46fec4b3d6f7ac72797 (patch)
treee747c1b7c55274c8ed26ddc515e0d0aca5d179c5
parent8d4a0ac008f4e86e340242e394c74d08ca401f92 (diff)
chore(tvix/nix-compat): basic daemon handler tests r/8970
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>
-rw-r--r--tvix/Cargo.lock71
-rw-r--r--tvix/Cargo.nix165
-rw-r--r--tvix/nix-compat/Cargo.toml1
-rw-r--r--tvix/nix-compat/src/nix_daemon/handler.rs151
-rw-r--r--tvix/nix-compat/src/nix_daemon/mod.rs5
5 files changed, 361 insertions, 32 deletions
diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock
index 80e31a8ec9d4..73724b57a0e1 100644
--- a/tvix/Cargo.lock
+++ b/tvix/Cargo.lock
@@ -1046,6 +1046,12 @@ dependencies = [
 ]
 
 [[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1249,6 +1255,12 @@ dependencies = [
 ]
 
 [[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2175,6 +2187,32 @@ dependencies = [
 ]
 
 [[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2290,6 +2328,7 @@ dependencies = [
  "glob",
  "hex-literal",
  "mimalloc",
+ "mockall",
  "nix-compat-derive",
  "nom",
  "num-traits",
@@ -2793,6 +2832,32 @@ dependencies = [
 ]
 
 [[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3932,6 +3997,12 @@ dependencies = [
 ]
 
 [[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix
index 42f793d1d881..5045d5149ea8 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 <fkoep@mailbox.org>"
+        ];
+        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 <armin.ronacher@active-4.com>"
+        ];
+        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 <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 {
         crateName = "multimap";
         version = "0.10.0";
@@ -7296,6 +7392,10 @@ rec {
             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 <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 {
         crateName = "pretty_assertions";
         version = "1.4.1";
@@ -12655,6 +12813,13 @@ rec {
         ];
 
       };
+      "termtree" = rec {
+        crateName = "termtree";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "0xkal5l2r3r9p9j90x35qy4npbdwxz4gskvbijs6msymaangas9k";
+
+      };
       "test-strategy" = rec {
         crateName = "test-strategy";
         version = "0.2.1";
diff --git a/tvix/nix-compat/Cargo.toml b/tvix/nix-compat/Cargo.toml
index 160eb2c20c16..dc0943476437 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 4f43612114d8..6fb45bdb7e2d 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<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]
     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<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()
+        );
+    }
 }
diff --git a/tvix/nix-compat/src/nix_daemon/mod.rs b/tvix/nix-compat/src/nix_daemon/mod.rs
index b1fd15c04ed1..11d236a6a321 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<R>(
         &self,
         request: AddToStoreNarRequest,