about summary refs log tree commit diff
path: root/tvix/store/src/pathinfoservice
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/store/src/pathinfoservice')
-rw-r--r--tvix/store/src/pathinfoservice/mod.rs3
-rw-r--r--tvix/store/src/pathinfoservice/tests/mod.rs112
-rw-r--r--tvix/store/src/pathinfoservice/tests/utils.rs60
3 files changed, 175 insertions, 0 deletions
diff --git a/tvix/store/src/pathinfoservice/mod.rs b/tvix/store/src/pathinfoservice/mod.rs
index 1f52d20caed7..c334c8dc5631 100644
--- a/tvix/store/src/pathinfoservice/mod.rs
+++ b/tvix/store/src/pathinfoservice/mod.rs
@@ -7,6 +7,9 @@ mod sled;
 #[cfg(any(feature = "fuse", feature = "virtiofs"))]
 mod fs;
 
+#[cfg(test)]
+mod tests;
+
 use futures::stream::BoxStream;
 use tonic::async_trait;
 use tvix_castore::proto as castorepb;
diff --git a/tvix/store/src/pathinfoservice/tests/mod.rs b/tvix/store/src/pathinfoservice/tests/mod.rs
new file mode 100644
index 000000000000..a3035d094d1e
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/tests/mod.rs
@@ -0,0 +1,112 @@
+//! This contains test scenarios that a given [PathInfoService] needs to pass.
+//! We use [rstest] and [rstest_reuse] to provide all services we want to test
+//! against, and then apply this template to all test functions.
+
+use rstest::*;
+use rstest_reuse::{self, *};
+use std::sync::Arc;
+use tvix_castore::proto as castorepb;
+use tvix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
+
+use super::PathInfoService;
+use crate::proto::PathInfo;
+use crate::tests::fixtures::DUMMY_OUTPUT_HASH;
+
+mod utils;
+use self::utils::make_grpc_path_info_service_client;
+
+/// Convenience type alias batching all three servives together.
+#[allow(clippy::upper_case_acronyms)]
+type BSDSPS = (
+    Arc<dyn BlobService>,
+    Arc<dyn DirectoryService>,
+    Box<dyn PathInfoService>,
+);
+
+/// Creates a PathInfoService using a new Memory{Blob,Directory}Service.
+/// We return a 3-tuple containing all of them, as some tests want to interact
+/// with all three.
+pub async fn make_path_info_service(uri: &str) -> BSDSPS {
+    let blob_service: Arc<dyn BlobService> = tvix_castore::blobservice::from_addr("memory://")
+        .await
+        .unwrap()
+        .into();
+    let directory_service: Arc<dyn DirectoryService> =
+        tvix_castore::directoryservice::from_addr("memory://")
+            .await
+            .unwrap()
+            .into();
+
+    (
+        blob_service.clone(),
+        directory_service.clone(),
+        crate::pathinfoservice::from_addr(uri, blob_service, directory_service)
+            .await
+            .unwrap(),
+    )
+}
+
+#[template]
+#[rstest]
+#[case::memory(make_path_info_service("memory://").await)]
+#[case::grpc(make_grpc_path_info_service_client().await)]
+#[case::sled(make_path_info_service("sled://").await)]
+pub fn path_info_services(
+    #[case] services: (
+        impl BlobService,
+        impl DirectoryService,
+        impl PathInfoService,
+    ),
+) {
+}
+
+// FUTUREWORK: add more tests rejecting invalid PathInfo messages.
+// A subset of them should also ensure references to other PathInfos, or
+// elements in {Blob,Directory}Service do exist.
+
+/// Trying to get a non-existent PathInfo should return Ok(None).
+#[apply(path_info_services)]
+#[tokio::test]
+async fn not_found(services: BSDSPS) {
+    let (_, _, path_info_service) = services;
+    assert!(path_info_service
+        .get(DUMMY_OUTPUT_HASH)
+        .await
+        .expect("must succeed")
+        .is_none());
+}
+
+/// Put a PathInfo into the store, get it back.
+#[apply(path_info_services)]
+#[tokio::test]
+async fn put_get(services: BSDSPS) {
+    let (_, _, path_info_service) = services;
+
+    let path_info = PathInfo {
+        node: Some(castorepb::Node {
+            node: Some(castorepb::node::Node::Symlink(castorepb::SymlinkNode {
+                name: "00000000000000000000000000000000-foo".into(),
+                target: "doesntmatter".into(),
+            })),
+        }),
+        ..Default::default()
+    };
+
+    // insert
+    let resp = path_info_service
+        .put(path_info.clone())
+        .await
+        .expect("must succeed");
+
+    // expect the returned PathInfo to be equal (for now)
+    // in the future, some stores might add additional fields/signatures.
+    assert_eq!(path_info, resp);
+
+    // get it back
+    let resp = path_info_service
+        .get(DUMMY_OUTPUT_HASH)
+        .await
+        .expect("must succeed");
+
+    assert_eq!(Some(path_info), resp);
+}
diff --git a/tvix/store/src/pathinfoservice/tests/utils.rs b/tvix/store/src/pathinfoservice/tests/utils.rs
new file mode 100644
index 000000000000..31ec57aade73
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/tests/utils.rs
@@ -0,0 +1,60 @@
+use std::sync::Arc;
+
+use tonic::transport::{Endpoint, Server, Uri};
+
+use crate::{
+    pathinfoservice::{GRPCPathInfoService, MemoryPathInfoService, PathInfoService},
+    proto::{
+        path_info_service_client::PathInfoServiceClient,
+        path_info_service_server::PathInfoServiceServer, GRPCPathInfoServiceWrapper,
+    },
+    tests::fixtures::{blob_service, directory_service},
+};
+
+/// Constructs and returns a gRPC PathInfoService.
+/// We also return memory-based {Blob,Directory}Service,
+/// as the consumer of this function accepts a 3-tuple.
+pub async fn make_grpc_path_info_service_client() -> super::BSDSPS {
+    let (left, right) = tokio::io::duplex(64);
+
+    let blob_service = blob_service();
+    let directory_service = directory_service();
+
+    // spin up a server, which will only connect once, to the left side.
+    tokio::spawn({
+        let blob_service = blob_service.clone();
+        let directory_service = directory_service.clone();
+        async move {
+            let path_info_service: Arc<dyn PathInfoService> =
+                Arc::from(MemoryPathInfoService::new(blob_service, directory_service));
+
+            // spin up a new DirectoryService
+            let mut server = Server::builder();
+            let router = server.add_service(PathInfoServiceServer::new(
+                GRPCPathInfoServiceWrapper::new(path_info_service),
+            ));
+
+            router
+                .serve_with_incoming(tokio_stream::once(Ok::<_, std::io::Error>(left)))
+                .await
+        }
+    });
+
+    // Create a client, connecting to the right side. The URI is unused.
+    let mut maybe_right = Some(right);
+
+    let path_info_service = Box::new(GRPCPathInfoService::from_client(
+        PathInfoServiceClient::new(
+            Endpoint::try_from("http://[::]:50051")
+                .unwrap()
+                .connect_with_connector(tower::service_fn(move |_: Uri| {
+                    let right = maybe_right.take().unwrap();
+                    async move { Ok::<_, std::io::Error>(right) }
+                }))
+                .await
+                .unwrap(),
+        ),
+    ));
+
+    (blob_service, directory_service, path_info_service)
+}