From d1a711cb7e5d0cd836f43840afdae5309d8db2aa Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Wed, 8 Jan 2025 17:32:48 +0100 Subject: [PATCH] Updated test-r (#1233) * Migrated key_value_storage tests to the new test-r dependency matrix feature * Migrated indexed_storage tests to the new test-r dependency matrix feature * Migrated blob_storage tests to the new test-r dependency matrix feature * Updated dynamic test generators to the new api --- Cargo.lock | 29 +- Cargo.toml | 2 +- golem-cli/tests/api_definition.rs | 67 +- golem-cli/tests/api_deployment.rs | 27 +- golem-cli/tests/api_deployment_fileserver.rs | 17 +- golem-cli/tests/component.rs | 52 +- golem-cli/tests/profile.rs | 12 +- golem-cli/tests/text.rs | 87 +- golem-cli/tests/worker.rs | 92 +- golem-service-base/tests/blob_storage.rs | 1681 ++++++++-------- .../tests/indexed_storage.rs | 1686 +++++++++-------- .../tests/key_value_storage.rs | 1466 +++++++------- 12 files changed, 2824 insertions(+), 2394 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d3d246217..8c81d65b58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7249,7 +7249,7 @@ dependencies = [ "pin-project-lite", "poem-derive", "prometheus", - "quick-xml", + "quick-xml 0.36.2", "regex", "rfc7239", "serde 1.0.216", @@ -7299,7 +7299,7 @@ dependencies = [ "num-traits 0.2.19", "poem", "poem-openapi-derive", - "quick-xml", + "quick-xml 0.36.2", "regex", "serde 1.0.216", "serde_json", @@ -7853,6 +7853,15 @@ dependencies = [ "serde 1.0.216", ] +[[package]] +name = "quick-xml" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.37" @@ -9693,9 +9702,9 @@ dependencies = [ [[package]] name = "test-r" -version = "1.2.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "096af9a5318c22b4f7bcf483eeacac44d831ae3ac78f4fab065be61c25713a10" +checksum = "3d8cb299f722f145e6379e79c8c406f923340f28b33497c3957ef9cf9fa2b225" dependencies = [ "ctor", "test-r-core", @@ -9705,9 +9714,9 @@ dependencies = [ [[package]] name = "test-r-core" -version = "1.2.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be35981a41cf8814f5cf4c01cebdf1a32b5e3b2c77436db13dc6c6f6669485ab" +checksum = "065e701871bdcf15c796c9c12c7806b7f67b75603f54ddcf9531f927a54919bb" dependencies = [ "anstream", "anstyle", @@ -9719,7 +9728,7 @@ dependencies = [ "futures", "interprocess", "parking_lot", - "quick-xml", + "quick-xml 0.37.2", "rand", "tokio", "topological-sort", @@ -9728,11 +9737,11 @@ dependencies = [ [[package]] name = "test-r-macro" -version = "1.1.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "040d55dfc68c3a12628b74488faa4bf39487b32d506e0b03de0edeb468d152be" +checksum = "3356b06edde5f3c37f868e1113fb86347a0f4837814fe45c98d4dc778a2f2eb2" dependencies = [ - "heck 0.5.0", + "darling", "proc-macro2", "quote", "rand", diff --git a/Cargo.toml b/Cargo.toml index 56276df29d..39b48e9766 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -180,7 +180,7 @@ testcontainers-modules = { version = "0.11.4", features = [ "redis", "minio", ] } -test-r = { version = "1.2.0", default-features = true } +test-r = { version = "2.0.1", default-features = true } thiserror = "2.0.6" tokio = { version = "1.42", features = [ "macros", diff --git a/golem-cli/tests/api_definition.rs b/golem-cli/tests/api_definition.rs index 0de4b85494..9e271eb20b 100644 --- a/golem-cli/tests/api_definition.rs +++ b/golem-cli/tests/api_definition.rs @@ -35,7 +35,7 @@ use std::collections::HashMap; use std::fs; use std::path::PathBuf; use std::sync::Arc; -use test_r::core::{DynamicTestRegistration, TestType}; +use test_r::core::{DynamicTestRegistration, TestProperties, TestType}; use uuid::Uuid; inherit_test_dep!(EnvBasedTestDependencies); @@ -56,7 +56,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_json_import{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_import( (deps, name.to_string(), cli.with_args(short)), @@ -67,7 +70,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_yaml_import{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_import( (deps, name.to_string(), cli.with_args(short)), @@ -78,7 +84,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_yaml_add{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_add( (deps, name.to_string(), cli.with_args(short)), @@ -89,7 +98,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_yaml_add_with_security{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_add_with_security( (deps, name.to_string(), cli.with_args(short)), @@ -100,7 +112,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_json_add_with_security{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_add_with_security( (deps, name.to_string(), cli.with_args(short)), @@ -111,7 +126,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_json_add{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_add( (deps, name.to_string(), cli.with_args(short)), @@ -122,7 +140,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_yaml_update{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_update( (deps, name.to_string(), cli.with_args(short)), @@ -133,7 +154,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_json_update{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_update( (deps, name.to_string(), cli.with_args(short)), @@ -144,7 +168,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_update_immutable{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_update_immutable((deps, name.to_string(), cli.with_args(short))) } @@ -152,7 +179,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_list{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_list((deps, name.to_string(), cli.with_args(short))) } @@ -160,7 +190,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_list_versions{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_list_versions((deps, name.to_string(), cli.with_args(short))) } @@ -168,7 +201,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_get{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_get((deps, name.to_string(), cli.with_args(short))) } @@ -176,7 +212,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_definition_delete{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_definition_delete((deps, name.to_string(), cli.with_args(short))) } diff --git a/golem-cli/tests/api_deployment.rs b/golem-cli/tests/api_deployment.rs index 51d29255da..8dff708be7 100644 --- a/golem-cli/tests/api_deployment.rs +++ b/golem-cli/tests/api_deployment.rs @@ -23,7 +23,7 @@ use golem_cli::model::ApiSecurityScheme; use golem_client::model::{ApiDeployment, HttpApiDefinitionResponseData}; use golem_test_framework::config::{EnvBasedTestDependencies, TestDependencies}; use std::sync::Arc; -use test_r::core::{DynamicTestRegistration, TestType}; +use test_r::core::{DynamicTestRegistration, TestProperties, TestType}; use test_r::{add_test, inherit_test_dep, test_dep, test_gen}; inherit_test_dep!(EnvBasedTestDependencies); @@ -44,7 +44,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_deployment_deploy{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_deployment_deploy((deps, name.to_string(), cli.with_args(short))) } @@ -52,7 +55,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_deployment_deploy_with_security{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_deployment_deploy_with_security((deps, name.to_string(), cli.with_args(short))) } @@ -60,7 +66,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_deployment_get{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_deployment_get((deps, name.to_string(), cli.with_args(short))) } @@ -68,7 +77,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_deployment_list{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_deployment_list((deps, name.to_string(), cli.with_args(short))) } @@ -76,7 +88,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_deployment_delete{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_deployment_delete((deps, name.to_string(), cli.with_args(short))) } diff --git a/golem-cli/tests/api_deployment_fileserver.rs b/golem-cli/tests/api_deployment_fileserver.rs index e60fb5bb20..de41dae10a 100644 --- a/golem-cli/tests/api_deployment_fileserver.rs +++ b/golem-cli/tests/api_deployment_fileserver.rs @@ -22,7 +22,7 @@ use golem_common::uri::oss::urn::WorkerUrn; use golem_test_framework::config::{EnvBasedTestDependencies, TestDependencies}; use std::io::Write; use std::sync::Arc; -use test_r::core::{DynamicTestRegistration, TestType}; +use test_r::core::{DynamicTestRegistration, TestProperties, TestType}; use test_r::{add_test, inherit_test_dep, test_dep, test_gen}; inherit_test_dep!(EnvBasedTestDependencies); @@ -43,7 +43,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_deployment_fileserver_simple{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_deployment_file_server_simple((deps, name.to_string(), cli.with_args(short))) } @@ -51,7 +54,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_deployment_fileserver_complex{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_deployment_fileserver_complex((deps, name.to_string(), cli.with_args(short))) } @@ -59,7 +65,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("api_deployment_fileserver_stateful_worker{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { api_deployment_fileserver_stateful_worker(( deps, diff --git a/golem-cli/tests/component.rs b/golem-cli/tests/component.rs index 1ecaeb331f..a54e5725b0 100644 --- a/golem-cli/tests/component.rs +++ b/golem-cli/tests/component.rs @@ -20,7 +20,7 @@ use golem_common::uri::oss::url::ComponentUrl; use golem_test_framework::config::{EnvBasedTestDependencies, TestDependencies}; use itertools::Itertools; use std::sync::Arc; -use test_r::core::{DynamicTestRegistration, TestType}; +use test_r::core::{DynamicTestRegistration, TestProperties, TestType}; use test_r::{add_test, inherit_test_dep, test_dep, test_gen}; inherit_test_dep!(EnvBasedTestDependencies); @@ -41,7 +41,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("component_add_and_find_all{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { component_add_and_find_all((deps, name.to_string(), cli.with_args(short))) } @@ -49,7 +52,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("component_add_and_find_by_name{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { component_add_and_find_by_name((deps, name.to_string(), cli.with_args(short))) } @@ -57,7 +63,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("component_add_and_get{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { component_add_and_get((deps, name.to_string(), cli.with_args(short))) } @@ -65,7 +74,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("component_add_and_get_urn{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { component_add_and_get_urn((deps, name.to_string(), cli.with_args(short))) } @@ -73,7 +85,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("component_add_and_get_url{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { component_add_and_get_url((deps, name.to_string(), cli.with_args(short))) } @@ -81,7 +96,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("component_add_from_project_file{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { component_add_from_project_file((deps, name.to_string(), cli.with_args(short))) } @@ -89,7 +107,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("component_update{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { component_update((deps, name.to_string(), cli.with_args(short))) } @@ -97,7 +118,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("component_update_urn{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { component_update_urn((deps, name.to_string(), cli.with_args(short))) } @@ -105,7 +129,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("component_update_url{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { component_update_url((deps, name.to_string(), cli.with_args(short))) } @@ -113,7 +140,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("component_update_from_project_file{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { component_update_from_project_file((deps, name.to_string(), cli.with_args(short))) } diff --git a/golem-cli/tests/profile.rs b/golem-cli/tests/profile.rs index b4876e65ab..a9c3ab15aa 100644 --- a/golem-cli/tests/profile.rs +++ b/golem-cli/tests/profile.rs @@ -23,7 +23,7 @@ use golem_cli::model::Format; use golem_test_framework::config::EnvBasedTestDependencies; use std::fmt::{Display, Formatter}; use std::sync::Arc; -use test_r::core::{DynamicTestRegistration, TestType}; +use test_r::core::{DynamicTestRegistration, TestProperties, TestType}; use url::Url; inherit_test_dep!(EnvBasedTestDependencies); @@ -54,7 +54,10 @@ fn make(r: &mut DynamicTestRegistration, args_kind: ArgsKind) { add_test!( r, format!("profile_add_get_list_switch_delete_{args_kind}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, _tracing: &Tracing| { profile_add_get_list_switch_delete((deps, args_kind)) } @@ -62,7 +65,10 @@ fn make(r: &mut DynamicTestRegistration, args_kind: ArgsKind) { add_test!( r, format!("profile_config_{args_kind}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, _tracing: &Tracing| { profile_config((deps, args_kind)) } diff --git a/golem-cli/tests/text.rs b/golem-cli/tests/text.rs index 34cfeb47cc..9243030d0a 100644 --- a/golem-cli/tests/text.rs +++ b/golem-cli/tests/text.rs @@ -31,7 +31,7 @@ use golem_test_framework::config::{EnvBasedTestDependencies, TestDependencies}; use indoc::formatdoc; use regex::Regex; use std::sync::Arc; -use test_r::core::{DynamicTestRegistration, TestType}; +use test_r::core::{DynamicTestRegistration, TestProperties, TestType}; inherit_test_dep!(EnvBasedTestDependencies); inherit_test_dep!(Tracing); @@ -51,7 +51,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_component_add{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_component_add((deps, name.to_string(), cli.with_args(short))) } @@ -59,7 +62,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_component_update{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_component_update((deps, name.to_string(), cli.with_args(short))) } @@ -67,7 +73,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_component_get{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_component_get((deps, name.to_string(), cli.with_args(short))) } @@ -75,7 +84,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_component_list{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_component_list((deps, name.to_string(), cli.with_args(short))) } @@ -83,7 +95,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_worker_add{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_worker_add((deps, name.to_string(), cli.with_args(short))) } @@ -91,7 +106,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_worker_invoke_and_await{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_worker_invoke_and_await((deps, name.to_string(), cli.with_args(short))) } @@ -99,7 +117,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_worker_get{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_worker_get((deps, name.to_string(), cli.with_args(short))) } @@ -107,7 +128,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_worker_list{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_worker_list((deps, name.to_string(), cli.with_args(short))) } @@ -115,7 +139,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_example_list{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_example_list((deps, name.to_string(), cli.with_args(short))) } @@ -123,7 +150,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_api_definition_import{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_api_definition_import((deps, name.to_string(), cli.with_args(short))) } @@ -131,7 +161,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_api_definition_add{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_api_definition_add((deps, name.to_string(), cli.with_args(short))) } @@ -139,7 +172,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_api_definition_update{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_api_definition_update((deps, name.to_string(), cli.with_args(short))) } @@ -147,7 +183,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_api_definition_list{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_api_definition_list((deps, name.to_string(), cli.with_args(short))) } @@ -155,7 +194,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_api_definition_get{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_api_definition_get((deps, name.to_string(), cli.with_args(short))) } @@ -163,7 +205,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_api_deployment_deploy{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_api_deployment_deploy((deps, name.to_string(), cli.with_args(short))) } @@ -171,7 +216,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_api_deployment_get{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_api_deployment_get((deps, name.to_string(), cli.with_args(short))) } @@ -179,7 +227,10 @@ fn make(r: &mut DynamicTestRegistration, suffix: &'static str, name: &'static st add_test!( r, format!("text_api_deployment_list{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { text_api_deployment_list((deps, name.to_string(), cli.with_args(short))) } diff --git a/golem-cli/tests/worker.rs b/golem-cli/tests/worker.rs index f789432e33..dfc6a9be32 100644 --- a/golem-cli/tests/worker.rs +++ b/golem-cli/tests/worker.rs @@ -33,7 +33,7 @@ use std::io::{BufRead, BufReader}; use std::sync::Arc; use std::thread::sleep; use std::time::Duration; -use test_r::core::{DynamicTestRegistration, TestType}; +use test_r::core::{DynamicTestRegistration, TestProperties, TestType}; use tracing::debug; inherit_test_dep!(EnvBasedTestDependencies); @@ -64,7 +64,10 @@ fn make( add_test!( r, format!("worker_new_instance{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_new_instance((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -72,7 +75,10 @@ fn make( add_test!( r, format!("worker_invoke_and_await{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_invoke_and_await((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -80,7 +86,10 @@ fn make( add_test!( r, format!("worker_invoke_and_await_wave_params{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_invoke_and_await_wave_params(( deps, @@ -93,7 +102,10 @@ fn make( add_test!( r, format!("worker_invoke_no_params{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_invoke_no_params((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -101,7 +113,10 @@ fn make( add_test!( r, format!("worker_invoke_drop{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_invoke_drop((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -109,7 +124,10 @@ fn make( add_test!( r, format!("worker_invoke_json_params{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_invoke_json_params((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -117,7 +135,10 @@ fn make( add_test!( r, format!("worker_invoke_wave_params{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_invoke_wave_params((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -125,7 +146,10 @@ fn make( add_test!( r, format!("worker_connect{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_connect((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -133,7 +157,10 @@ fn make( add_test!( r, format!("worker_connect_failed{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_connect_failed((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -141,7 +168,10 @@ fn make( add_test!( r, format!("worker_interrupt{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_interrupt((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -149,7 +179,10 @@ fn make( add_test!( r, format!("worker_simulated_crash{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_simulated_crash((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -157,7 +190,10 @@ fn make( add_test!( r, format!("worker_list{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_list((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -165,7 +201,10 @@ fn make( add_test!( r, format!("worker_update{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_update((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -173,7 +212,10 @@ fn make( add_test!( r, format!("worker_invoke_indexed_resource{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_invoke_indexed_resource((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -181,7 +223,10 @@ fn make( add_test!( r, format!("worker_invoke_without_name{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_invoke_without_name((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -189,7 +234,10 @@ fn make( add_test!( r, format!("worker_invoke_without_name_ephemeral{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_invoke_without_name_ephemeral(( deps, @@ -202,7 +250,10 @@ fn make( add_test!( r, format!("worker_get_oplog{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_get_oplog((deps, name.to_string(), cli.with_args(short), ref_kind)) } @@ -210,7 +261,10 @@ fn make( add_test!( r, format!("worker_search_oplog{suffix}"), - TestType::IntegrationTest, + TestProperties { + test_type: TestType::IntegrationTest, + ..TestProperties::default() + }, move |deps: &EnvBasedTestDependencies, cli: &CliLive, _tracing: &Tracing| { worker_search_oplog((deps, name.to_string(), cli.with_args(short), ref_kind)) } diff --git a/golem-service-base/tests/blob_storage.rs b/golem-service-base/tests/blob_storage.rs index dcbf28472e..646629721b 100644 --- a/golem-service-base/tests/blob_storage.rs +++ b/golem-service-base/tests/blob_storage.rs @@ -22,729 +22,115 @@ use golem_service_base::storage::blob::sqlite::SqliteBlobStorage; use golem_service_base::storage::blob::{fs, memory, s3, BlobStorage, BlobStorageNamespace}; use golem_service_base::storage::sqlite::SqlitePool; use sqlx::sqlite::SqlitePoolOptions; +use std::fmt::Debug; use tempfile::{tempdir, TempDir}; use testcontainers::runners::AsyncRunner; use testcontainers::ContainerAsync; use testcontainers_modules::minio::MinIO; use uuid::Uuid; -macro_rules! test_blob_storage { - ( $name:ident, $init:expr, $ns:expr ) => { - mod $name { - use test_r::test; - - use assert2::check; - use bytes::{BufMut, Bytes, BytesMut}; - use futures::TryStreamExt; - use golem_service_base::storage::blob::*; - use std::path::Path; - - use crate::blob_storage::GetBlobStorage; - - #[test] - #[tracing::instrument] - async fn get_put_get_root() { - let test = $init().await; - let storage = test.get_blob_storage(); - let namespace = $ns(); - - let path = Path::new("test-path"); - let data = Bytes::from("test-data"); - - let result1 = storage - .get_raw("get_put_get_root", "get-raw", namespace.clone(), path) - .await - .unwrap(); - - storage - .put_raw( - "get_put_get_root", - "put-raw", - namespace.clone(), - path, - &data, - ) - .await - .unwrap(); - - let result2 = storage - .get_raw("get_put_get_root", "get-raw-2", namespace.clone(), path) - .await - .unwrap(); - - check!(result1 == None); - check!(result2 == Some(data)); - } - - #[test] - #[tracing::instrument] - async fn get_put_get_new_dir() { - let test = $init().await; - let storage = test.get_blob_storage(); - let namespace = $ns(); - - let path = Path::new("non-existing-dir/test-path"); - let data = Bytes::from("test-data"); - - let result1 = storage - .get_raw("get_put_get_new_dir", "get-raw", namespace.clone(), path) - .await - .unwrap(); - - storage - .put_raw( - "get_put_get_new_dir", - "put-raw", - namespace.clone(), - path, - &data, - ) - .await - .unwrap(); - - let result2 = storage - .get_raw("get_put_get_new_dir", "get-raw-2", namespace.clone(), path) - .await - .unwrap(); - - check!(result1 == None); - check!(result2 == Some(data)); - } - - #[test] - #[tracing::instrument] - async fn get_put_get_new_dir_streaming() { - let test = $init().await; - let storage = test.get_blob_storage(); - let namespace = $ns(); - - let path = Path::new("non-existing-dir/test-path"); - let mut data = BytesMut::new(); - for n in 1..(10 * 1024 * 1024) { - data.put_u8((n % 100) as u8); - } - let data = data.freeze(); - - let result1 = storage - .get_stream("get_put_get_new_dir", "get-raw", namespace.clone(), path) - .await - .unwrap(); - - storage - .put_stream( - "get_put_get_new_dir", - "put-raw", - namespace.clone(), - path, - &data, - ) - .await - .unwrap(); - - let result2 = storage - .get_stream("get_put_get_new_dir", "get-raw-2", namespace.clone(), path) - .await - .unwrap() - .unwrap() - .try_collect::>() - .await - .unwrap() - .concat(); - - check!(result1.is_none()); - check!(result2 == data.to_vec()); - } - - #[test] - #[tracing::instrument] - async fn create_delete_exists_dir() { - let test = $init().await; - let storage = test.get_blob_storage(); - let namespace = $ns(); - - let path = Path::new("test-dir"); - - let result1 = storage - .exists( - "create_delete_exists_dir", - "exists", - namespace.clone(), - path, - ) - .await - .unwrap(); - storage - .create_dir( - "create_delete_exists_dir", - "create-dir", - namespace.clone(), - path, - ) - .await - .unwrap(); - let result2 = storage - .exists( - "create_delete_exists_dir", - "exists-2", - namespace.clone(), - path, - ) - .await - .unwrap(); - let delete_result1 = storage - .delete_dir( - "create_delete_exists_dir", - "delete-dir", - namespace.clone(), - path, - ) - .await - .unwrap(); - let result3 = storage - .exists( - "create_delete_exists_dir", - "exists-3", - namespace.clone(), - path, - ) - .await - .unwrap(); - let delete_result2 = storage - .delete_dir( - "create_delete_exists_dir", - "delete-dir", - namespace.clone(), - path, - ) - .await - .unwrap(); - - check!(result1 == ExistsResult::DoesNotExist); - check!(result2 == ExistsResult::Directory); - check!(result3 == ExistsResult::DoesNotExist); - check!(delete_result1 == true); - check!(delete_result2 == false); - } - - #[test] - #[tracing::instrument] - async fn create_delete_exists_dir_and_file() { - let test = $init().await; - let storage = test.get_blob_storage(); - let namespace = $ns(); - - let path = Path::new("test-dir"); - - let result1 = storage - .exists( - "create_delete_exists_dir_and_file", - "exists", - namespace.clone(), - path, - ) - .await - .unwrap(); - storage - .create_dir( - "create_delete_exists_dir_and_file", - "create-dir", - namespace.clone(), - path, - ) - .await - .unwrap(); - storage - .put_raw( - "create_delete_exists_dir_and_file", - "put-raw", - namespace.clone(), - &path.join("test-file"), - &Bytes::from("test-data"), - ) - .await - .unwrap(); - let result2 = storage - .exists( - "create_delete_exists_dir_and_file", - "exists-2", - namespace.clone(), - path, - ) - .await - .unwrap(); - let result3 = storage - .exists( - "create_delete_exists_dir_and_file", - "exists-3", - namespace.clone(), - &path.join("test-file"), - ) - .await - .unwrap(); - storage - .delete_dir( - "create_delete_exists_dir_and_file", - "delete-dir", - namespace.clone(), - path, - ) - .await - .unwrap(); - let result4 = storage - .exists( - "create_delete_exists_dir_and_file", - "exists-4", - namespace.clone(), - path, - ) - .await - .unwrap(); - - check!(result1 == ExistsResult::DoesNotExist); - check!(result2 == ExistsResult::Directory); - check!(result3 == ExistsResult::File); - check!(result4 == ExistsResult::DoesNotExist); - } - - #[test] - #[tracing::instrument] - async fn list_dir() { - let test = $init().await; - let storage = test.get_blob_storage(); - let namespace = $ns(); - - let path = Path::new("test-dir"); - storage - .create_dir("list_dir", "create-dir", namespace.clone(), path) - .await - .unwrap(); - storage - .put_raw( - "list_dir", - "put-raw", - namespace.clone(), - &path.join("test-file1"), - &Bytes::from("test-data1"), - ) - .await - .unwrap(); - storage - .put_raw( - "list_dir", - "put-raw", - namespace.clone(), - &path.join("test-file2"), - &Bytes::from("test-data2"), - ) - .await - .unwrap(); - storage - .create_dir( - "list_dir", - "create-dir", - namespace.clone(), - &path.join("inner-dir"), - ) - .await - .unwrap(); - let mut entries = storage - .list_dir("list_dir", "entries", namespace.clone(), path) - .await - .unwrap(); - - entries.sort(); - - check!( - entries - == vec![ - Path::new("test-dir/inner-dir").to_path_buf(), - Path::new("test-dir/test-file1").to_path_buf(), - Path::new("test-dir/test-file2").to_path_buf(), - ] - ); - } - - #[test] - #[tracing::instrument] - async fn delete_many() { - let test = $init().await; - let storage = test.get_blob_storage(); - let namespace = $ns(); - - let path = Path::new("test-dir"); - storage - .create_dir("list_dir", "create-dir", namespace.clone(), path) - .await - .unwrap(); - storage - .put_raw( - "delete_many", - "put-raw", - namespace.clone(), - &path.join("test-file1"), - &Bytes::from("test-data1"), - ) - .await - .unwrap(); - storage - .put_raw( - "delete_many", - "put-raw", - namespace.clone(), - &path.join("test-file2"), - &Bytes::from("test-data2"), - ) - .await - .unwrap(); - storage - .put_raw( - "delete_many", - "put-raw", - namespace.clone(), - &path.join("test-file3"), - &Bytes::from("test-data3"), - ) - .await - .unwrap(); - storage - .create_dir( - "delete_many", - "create-dir", - namespace.clone(), - &path.join("inner-dir"), - ) - .await - .unwrap(); - storage - .delete_many( - "delete_many", - "delete-many", - namespace.clone(), - &[path.join("test-file1"), path.join("test-file3")], - ) - .await - .unwrap(); - - let mut entries = storage - .list_dir("delete_many", "entries", namespace.clone(), path) - .await - .unwrap(); - - entries.sort(); - - check!( - entries - == vec![ - Path::new("test-dir/inner-dir").to_path_buf(), - Path::new("test-dir/test-file2").to_path_buf(), - ] - ); - } - - #[test] - #[tracing::instrument] - async fn list_dir_root() { - let test = $init().await; - let storage = test.get_blob_storage(); - let namespace = $ns(); - - storage - .put_raw( - "list_dir_root", - "put-raw", - namespace.clone(), - Path::new("test-file1"), - &Bytes::from("test-data1"), - ) - .await - .unwrap(); - storage - .put_raw( - "list_dir_root", - "put-raw-2", - namespace.clone(), - Path::new("test-file2"), - &Bytes::from("test-data2"), - ) - .await - .unwrap(); - storage - .create_dir( - "list_dir_root", - "create-dir", - namespace.clone(), - Path::new("inner-dir"), - ) - .await - .unwrap(); - let mut entries = storage - .list_dir( - "list_dir_root", - "list-dir", - namespace.clone(), - Path::new(""), - ) - .await - .unwrap(); - - entries.sort(); - - check!( - entries - == vec![ - Path::new("inner-dir").to_path_buf(), - Path::new("test-file1").to_path_buf(), - Path::new("test-file2").to_path_buf(), - ] - ); - } - - #[test] - #[tracing::instrument] - async fn list_dir_root_only_subdirs() { - let test = $init().await; - let storage = test.get_blob_storage(); - let namespace = $ns(); - - storage - .create_dir( - "list_dir_root", - "create-dir", - namespace.clone(), - Path::new("inner-dir1"), - ) - .await - .unwrap(); - storage - .create_dir( - "list_dir_root", - "create-dir", - namespace.clone(), - Path::new("inner-dir2"), - ) - .await - .unwrap(); - - storage - .put_raw( - "list_dir_root", - "put-raw", - namespace.clone(), - Path::new("inner-dir1/test-file1"), - &Bytes::from("test-data1"), - ) - .await - .unwrap(); - storage - .put_raw( - "list_dir_root", - "put-raw-2", - namespace.clone(), - Path::new("inner-dir2/test-file2"), - &Bytes::from("test-data2"), - ) - .await - .unwrap(); - let mut entries = storage - .list_dir( - "list_dir_root", - "list-dir", - namespace.clone(), - Path::new(""), - ) - .await - .unwrap(); - - entries.sort(); - - check!( - entries - == vec![ - Path::new("inner-dir1").to_path_buf(), - Path::new("inner-dir2").to_path_buf(), - ] - ); - } - - #[test] - #[tracing::instrument] - async fn list_dir_same_prefix() { - let test = $init().await; - let storage = test.get_blob_storage(); - let namespace = $ns(); - - let path1 = Path::new("test-dir"); - let path2 = Path::new("test-dir2"); - let path3 = Path::new("test-dir3"); - storage - .create_dir("list_dir", "create-dir", namespace.clone(), path1) - .await - .unwrap(); - storage - .create_dir("list_dir", "create-dir-2", namespace.clone(), path2) - .await - .unwrap(); - storage - .create_dir("list_dir", "create-dir-3", namespace.clone(), path3) - .await - .unwrap(); - storage - .put_raw( - "list_dir_same_prefix", - "put-raw", - namespace.clone(), - &path1.join("test-file1"), - &Bytes::from("test-data1"), - ) - .await - .unwrap(); - storage - .put_raw( - "list_dir_same_prefix", - "put-raw", - namespace.clone(), - &path1.join("test-file2"), - &Bytes::from("test-data2"), - ) - .await - .unwrap(); - storage - .create_dir( - "list_dir_same_prefix", - "create-dir", - namespace.clone(), - &path1.join("inner-dir"), - ) - .await - .unwrap(); - let mut entries = storage - .list_dir("list_dir_same_prefix", "entries", namespace.clone(), path1) - .await - .unwrap(); - - entries.sort(); - - check!( - entries - == vec![ - Path::new("test-dir/inner-dir").to_path_buf(), - Path::new("test-dir/test-file1").to_path_buf(), - Path::new("test-dir/test-file2").to_path_buf(), - ] - ); - } - } - }; -} - -pub(crate) trait GetBlobStorage { - fn get_blob_storage(&self) -> &(dyn BlobStorage + Send + Sync); -} - -struct InMemoryTest { - storage: memory::InMemoryBlobStorage, +use test_r::{define_matrix_dimension, test, test_dep}; + +use assert2::check; +use async_trait::async_trait; +use bytes::{BufMut, Bytes, BytesMut}; +use futures::{Stream, TryStreamExt}; +use golem_service_base::storage::blob::*; +use std::path::{Path, PathBuf}; +use std::pin::Pin; +use std::sync::atomic::AtomicU32; +use std::sync::Arc; + +#[async_trait] +trait GetBlobStorage: Debug { + async fn get_blob_storage(&self) -> Arc; } -impl GetBlobStorage for InMemoryTest { - fn get_blob_storage(&self) -> &(dyn BlobStorage + Send + Sync) { - &self.storage - } -} +struct InMemoryTest; -struct FsTest { - _dir: TempDir, - storage: fs::FileSystemBlobStorage, -} - -impl GetBlobStorage for FsTest { - fn get_blob_storage(&self) -> &(dyn BlobStorage + Send + Sync) { - &self.storage +impl Debug for InMemoryTest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "InMemoryTest") } } -struct S3Test { - _container: ContainerAsync, - storage: s3::S3BlobStorage, +#[async_trait] +impl GetBlobStorage for InMemoryTest { + async fn get_blob_storage(&self) -> Arc { + Arc::new(memory::InMemoryBlobStorage::new()) + } } -impl GetBlobStorage for S3Test { - fn get_blob_storage(&self) -> &(dyn BlobStorage + Send + Sync) { - &self.storage - } +#[test_dep(tagged_as = "in_memory")] +fn in_memory() -> Arc { + Arc::new(InMemoryTest) } -struct SqliteTest { - storage: SqliteBlobStorage, +struct FsTest { + dir: TempDir, + counter: AtomicU32, } -impl GetBlobStorage for SqliteTest { - fn get_blob_storage(&self) -> &(dyn BlobStorage + Send + Sync) { - &self.storage +impl Debug for FsTest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "FsTest") } } -pub(crate) async fn in_memory() -> impl GetBlobStorage { - InMemoryTest { - storage: memory::InMemoryBlobStorage::new(), +#[async_trait] +impl GetBlobStorage for FsTest { + async fn get_blob_storage(&self) -> Arc { + let counter = self + .counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let path = self.dir.path().join(format!("test-{}", counter)); + Arc::new(fs::FileSystemBlobStorage::new(&path).await.unwrap()) } } -pub(crate) async fn fs() -> impl GetBlobStorage { +#[test_dep(tagged_as = "fs")] +async fn fs() -> Arc { let dir = tempdir().unwrap(); - let path = dir.path().to_path_buf(); - FsTest { - _dir: dir, - storage: fs::FileSystemBlobStorage::new(&path).await.unwrap(), - } + let counter = AtomicU32::new(0); + Arc::new(FsTest { dir, counter }) +} + +struct S3Test { + prefixed: Option, } -pub(crate) async fn s3() -> impl GetBlobStorage { - let container = MinIO::default() - .start() - .await - .expect("Failed to start MinIO"); - let host_port = container - .get_host_port_ipv4(9000) - .await - .expect("Failed to get host port"); - - let config = S3BlobStorageConfig { - retries: Default::default(), - region: "us-east-1".to_string(), - object_prefix: "".to_string(), - aws_endpoint_url: Some(format!("http://127.0.0.1:{host_port}")), - use_minio_credentials: true, - ..std::default::Default::default() - }; - create_buckets(host_port, &config).await; - S3Test { - _container: container, - storage: s3::S3BlobStorage::new(config.clone()).await, +impl Debug for S3Test { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "S3Test") } } -pub(crate) async fn s3_prefixed() -> impl GetBlobStorage { - let container = MinIO::default() - .start() - .await - .expect("Failed to start MinIO"); - let host_port = container - .get_host_port_ipv4(9000) - .await - .expect("Failed to get host port"); - - let config = S3BlobStorageConfig { - retries: Default::default(), - region: "us-east-1".to_string(), - object_prefix: "test-prefix".to_string(), - aws_endpoint_url: Some(format!("http://127.0.0.1:{host_port}")), - use_minio_credentials: true, - ..std::default::Default::default() - }; - create_buckets(host_port, &config).await; - S3Test { - _container: container, - storage: s3::S3BlobStorage::new(config.clone()).await, +#[async_trait] +impl GetBlobStorage for S3Test { + async fn get_blob_storage(&self) -> Arc { + let container = MinIO::default() + .start() + .await + .expect("Failed to start MinIO"); + let host_port = container + .get_host_port_ipv4(9000) + .await + .expect("Failed to get host port"); + + let config = S3BlobStorageConfig { + retries: Default::default(), + region: "us-east-1".to_string(), + object_prefix: self.prefixed.clone().unwrap_or_default(), + aws_endpoint_url: Some(format!("http://127.0.0.1:{host_port}")), + use_minio_credentials: true, + ..std::default::Default::default() + }; + create_buckets(host_port, &config).await; + let storage = s3::S3BlobStorage::new(config).await; + Arc::new(S3BlobStorageWithContainer { + storage, + _container: container, + }) } } @@ -783,11 +169,244 @@ async fn create_buckets(host_port: u16, config: &S3BlobStorageConfig) { } } -pub(crate) fn compilation_cache() -> BlobStorageNamespace { +struct S3BlobStorageWithContainer { + storage: s3::S3BlobStorage, + _container: ContainerAsync, +} + +impl Debug for S3BlobStorageWithContainer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "S3BlobStorageWithContainer") + } +} + +#[async_trait] +impl BlobStorage for S3BlobStorageWithContainer { + async fn get_raw( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + ) -> Result, String> { + self.storage + .get_raw(target_label, op_label, namespace, path) + .await + } + + async fn get_stream( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + ) -> Result> + Send>>>, String> { + self.storage + .get_stream(target_label, op_label, namespace, path) + .await + } + + async fn get_raw_slice( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + start: u64, + end: u64, + ) -> Result, String> { + self.storage + .get_raw_slice(target_label, op_label, namespace, path, start, end) + .await + } + + async fn get_metadata( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + ) -> Result, String> { + self.storage + .get_metadata(target_label, op_label, namespace, path) + .await + } + + async fn put_raw( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + data: &[u8], + ) -> Result<(), String> { + self.storage + .put_raw(target_label, op_label, namespace, path, data) + .await + } + + async fn put_stream( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + stream: &dyn ReplayableStream>, + ) -> Result<(), String> { + self.storage + .put_stream(target_label, op_label, namespace, path, stream) + .await + } + + async fn delete( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + ) -> Result<(), String> { + self.storage + .delete(target_label, op_label, namespace, path) + .await + } + + async fn delete_many( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + paths: &[PathBuf], + ) -> Result<(), String> { + self.storage + .delete_many(target_label, op_label, namespace, paths) + .await + } + + async fn create_dir( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + ) -> Result<(), String> { + self.storage + .create_dir(target_label, op_label, namespace, path) + .await + } + + async fn list_dir( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + ) -> Result, String> { + self.storage + .list_dir(target_label, op_label, namespace, path) + .await + } + + async fn delete_dir( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + ) -> Result { + self.storage + .delete_dir(target_label, op_label, namespace, path) + .await + } + + async fn exists( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + path: &Path, + ) -> Result { + self.storage + .exists(target_label, op_label, namespace, path) + .await + } + + async fn copy( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + from: &Path, + to: &Path, + ) -> Result<(), String> { + self.storage + .copy(target_label, op_label, namespace, from, to) + .await + } + + async fn r#move( + &self, + target_label: &'static str, + op_label: &'static str, + namespace: BlobStorageNamespace, + from: &Path, + to: &Path, + ) -> Result<(), String> { + self.storage + .r#move(target_label, op_label, namespace, from, to) + .await + } +} + +#[test_dep(tagged_as = "s3")] +async fn s3() -> Arc { + Arc::new(S3Test { prefixed: None }) +} + +#[test_dep(tagged_as = "s3_prefixed")] +async fn s3_prefixed() -> Arc { + Arc::new(S3Test { + prefixed: Some("random-prefix".to_string()), + }) +} + +struct SqliteTest; + +impl Debug for SqliteTest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "SqliteTest") + } +} + +#[async_trait] +impl GetBlobStorage for SqliteTest { + async fn get_blob_storage(&self) -> Arc { + let sqlx_pool_sqlite = SqlitePoolOptions::new() + .max_connections(10) + .connect("sqlite::memory:") + .await + .expect("Cannot create db options"); + + let pool = SqlitePool::new(sqlx_pool_sqlite) + .await + .expect("Cannot connect to sqlite db"); + + let sbs = SqliteBlobStorage::new(pool).await.unwrap(); + Arc::new(sbs) + } +} + +#[test_dep(tagged_as = "sqlite")] +async fn sqlite() -> Arc { + Arc::new(SqliteTest) +} + +#[test_dep(tagged_as = "cc")] +fn compilation_cache() -> BlobStorageNamespace { BlobStorageNamespace::CompilationCache } -pub(crate) fn compressed_oplog() -> BlobStorageNamespace { +#[test_dep(tagged_as = "co")] +fn compressed_oplog() -> BlobStorageNamespace { BlobStorageNamespace::CompressedOplog { account_id: AccountId { value: "test-account".to_string(), @@ -797,70 +416,608 @@ pub(crate) fn compressed_oplog() -> BlobStorageNamespace { } } -pub(crate) async fn sqlite() -> impl GetBlobStorage { - let sqlx_pool_sqlite = SqlitePoolOptions::new() - .max_connections(10) - .connect("sqlite::memory:") - .await - .expect("Cannot create db options"); - - let pool = SqlitePool::new(sqlx_pool_sqlite) - .await - .expect("Cannot connect to sqlite db"); - - let sbs = SqliteBlobStorage::new(pool).await.unwrap(); - - SqliteTest { storage: sbs } -} - -test_blob_storage!( - in_memory_cc, - crate::blob_storage::in_memory, - crate::blob_storage::compilation_cache -); -test_blob_storage!( - filesystem_cc, - crate::blob_storage::fs, - crate::blob_storage::compilation_cache -); -test_blob_storage!( - s3_no_prefix_cc, - crate::blob_storage::s3, - crate::blob_storage::compilation_cache -); -test_blob_storage!( - s3_prefixed_cc, - crate::blob_storage::s3_prefixed, - crate::blob_storage::compilation_cache -); -test_blob_storage!( - sqlite_cc, - crate::blob_storage::sqlite, - crate::blob_storage::compilation_cache -); - -test_blob_storage!( - in_memory_co, - crate::blob_storage::in_memory, - crate::blob_storage::compressed_oplog -); -test_blob_storage!( - filesystem_co, - crate::blob_storage::fs, - crate::blob_storage::compressed_oplog -); -test_blob_storage!( - s3_no_prefix_co, - crate::blob_storage::s3, - crate::blob_storage::compressed_oplog -); -test_blob_storage!( - s3_prefixed_co, - crate::blob_storage::s3_prefixed, - crate::blob_storage::compressed_oplog -); -test_blob_storage!( - sqlite_co, - crate::blob_storage::sqlite, - crate::blob_storage::compressed_oplog -); +define_matrix_dimension!(storage: Arc -> "in_memory", "fs", "s3", "s3_prefixed", "sqlite"); +define_matrix_dimension!(ns: BlobStorageNamespace -> "cc", "co"); + +#[test] +#[tracing::instrument] +async fn get_put_get_root( + #[dimension(storage)] test: &Arc, + #[dimension(ns)] namespace: &BlobStorageNamespace, +) { + let storage = test.get_blob_storage().await; + + let path = Path::new("test-path"); + let data = Bytes::from("test-data"); + + let result1 = storage + .get_raw("get_put_get_root", "get-raw", namespace.clone(), path) + .await + .unwrap(); + + storage + .put_raw( + "get_put_get_root", + "put-raw", + namespace.clone(), + path, + &data, + ) + .await + .unwrap(); + + let result2 = storage + .get_raw("get_put_get_root", "get-raw-2", namespace.clone(), path) + .await + .unwrap(); + + check!(result1 == None); + check!(result2 == Some(data)); +} + +#[test] +#[tracing::instrument] +async fn get_put_get_new_dir( + #[dimension(storage)] test: &Arc, + #[dimension(ns)] namespace: &BlobStorageNamespace, +) { + let storage = test.get_blob_storage().await; + + let path = Path::new("non-existing-dir/test-path"); + let data = Bytes::from("test-data"); + + let result1 = storage + .get_raw("get_put_get_new_dir", "get-raw", namespace.clone(), path) + .await + .unwrap(); + + storage + .put_raw( + "get_put_get_new_dir", + "put-raw", + namespace.clone(), + path, + &data, + ) + .await + .unwrap(); + + let result2 = storage + .get_raw("get_put_get_new_dir", "get-raw-2", namespace.clone(), path) + .await + .unwrap(); + + check!(result1 == None); + check!(result2 == Some(data)); +} + +#[test] +#[tracing::instrument] +async fn get_put_get_new_dir_streaming( + #[dimension(storage)] test: &Arc, + #[dimension(ns)] namespace: &BlobStorageNamespace, +) { + let storage = test.get_blob_storage().await; + + let path = Path::new("non-existing-dir/test-path"); + let mut data = BytesMut::new(); + for n in 1..(10 * 1024 * 1024) { + data.put_u8((n % 100) as u8); + } + let data = data.freeze(); + + let result1 = storage + .get_stream("get_put_get_new_dir", "get-raw", namespace.clone(), path) + .await + .unwrap(); + + storage + .put_stream( + "get_put_get_new_dir", + "put-raw", + namespace.clone(), + path, + &data, + ) + .await + .unwrap(); + + let result2 = storage + .get_stream("get_put_get_new_dir", "get-raw-2", namespace.clone(), path) + .await + .unwrap() + .unwrap() + .try_collect::>() + .await + .unwrap() + .concat(); + + check!(result1.is_none()); + check!(result2 == data.to_vec()); +} + +#[test] +#[tracing::instrument] +async fn create_delete_exists_dir( + #[dimension(storage)] test: &Arc, + #[dimension(ns)] namespace: &BlobStorageNamespace, +) { + let storage = test.get_blob_storage().await; + + let path = Path::new("test-dir"); + + let result1 = storage + .exists( + "create_delete_exists_dir", + "exists", + namespace.clone(), + path, + ) + .await + .unwrap(); + storage + .create_dir( + "create_delete_exists_dir", + "create-dir", + namespace.clone(), + path, + ) + .await + .unwrap(); + let result2 = storage + .exists( + "create_delete_exists_dir", + "exists-2", + namespace.clone(), + path, + ) + .await + .unwrap(); + let delete_result1 = storage + .delete_dir( + "create_delete_exists_dir", + "delete-dir", + namespace.clone(), + path, + ) + .await + .unwrap(); + let result3 = storage + .exists( + "create_delete_exists_dir", + "exists-3", + namespace.clone(), + path, + ) + .await + .unwrap(); + let delete_result2 = storage + .delete_dir( + "create_delete_exists_dir", + "delete-dir", + namespace.clone(), + path, + ) + .await + .unwrap(); + + check!(result1 == ExistsResult::DoesNotExist); + check!(result2 == ExistsResult::Directory); + check!(result3 == ExistsResult::DoesNotExist); + check!(delete_result1 == true); + check!(delete_result2 == false); +} + +#[test] +#[tracing::instrument] +async fn create_delete_exists_dir_and_file( + #[dimension(storage)] test: &Arc, + #[dimension(ns)] namespace: &BlobStorageNamespace, +) { + let storage = test.get_blob_storage().await; + + let path = Path::new("test-dir"); + + let result1 = storage + .exists( + "create_delete_exists_dir_and_file", + "exists", + namespace.clone(), + path, + ) + .await + .unwrap(); + storage + .create_dir( + "create_delete_exists_dir_and_file", + "create-dir", + namespace.clone(), + path, + ) + .await + .unwrap(); + storage + .put_raw( + "create_delete_exists_dir_and_file", + "put-raw", + namespace.clone(), + &path.join("test-file"), + &Bytes::from("test-data"), + ) + .await + .unwrap(); + let result2 = storage + .exists( + "create_delete_exists_dir_and_file", + "exists-2", + namespace.clone(), + path, + ) + .await + .unwrap(); + let result3 = storage + .exists( + "create_delete_exists_dir_and_file", + "exists-3", + namespace.clone(), + &path.join("test-file"), + ) + .await + .unwrap(); + storage + .delete_dir( + "create_delete_exists_dir_and_file", + "delete-dir", + namespace.clone(), + path, + ) + .await + .unwrap(); + let result4 = storage + .exists( + "create_delete_exists_dir_and_file", + "exists-4", + namespace.clone(), + path, + ) + .await + .unwrap(); + + check!(result1 == ExistsResult::DoesNotExist); + check!(result2 == ExistsResult::Directory); + check!(result3 == ExistsResult::File); + check!(result4 == ExistsResult::DoesNotExist); +} + +#[test] +#[tracing::instrument] +async fn list_dir( + #[dimension(storage)] test: &Arc, + #[dimension(ns)] namespace: &BlobStorageNamespace, +) { + let storage = test.get_blob_storage().await; + + let path = Path::new("test-dir"); + storage + .create_dir("list_dir", "create-dir", namespace.clone(), path) + .await + .unwrap(); + storage + .put_raw( + "list_dir", + "put-raw", + namespace.clone(), + &path.join("test-file1"), + &Bytes::from("test-data1"), + ) + .await + .unwrap(); + storage + .put_raw( + "list_dir", + "put-raw", + namespace.clone(), + &path.join("test-file2"), + &Bytes::from("test-data2"), + ) + .await + .unwrap(); + storage + .create_dir( + "list_dir", + "create-dir", + namespace.clone(), + &path.join("inner-dir"), + ) + .await + .unwrap(); + let mut entries = storage + .list_dir("list_dir", "entries", namespace.clone(), path) + .await + .unwrap(); + + entries.sort(); + + check!( + entries + == vec![ + Path::new("test-dir/inner-dir").to_path_buf(), + Path::new("test-dir/test-file1").to_path_buf(), + Path::new("test-dir/test-file2").to_path_buf(), + ] + ); +} + +#[test] +#[tracing::instrument] +async fn delete_many( + #[dimension(storage)] test: &Arc, + #[dimension(ns)] namespace: &BlobStorageNamespace, +) { + let storage = test.get_blob_storage().await; + + let path = Path::new("test-dir"); + storage + .create_dir("list_dir", "create-dir", namespace.clone(), path) + .await + .unwrap(); + storage + .put_raw( + "delete_many", + "put-raw", + namespace.clone(), + &path.join("test-file1"), + &Bytes::from("test-data1"), + ) + .await + .unwrap(); + storage + .put_raw( + "delete_many", + "put-raw", + namespace.clone(), + &path.join("test-file2"), + &Bytes::from("test-data2"), + ) + .await + .unwrap(); + storage + .put_raw( + "delete_many", + "put-raw", + namespace.clone(), + &path.join("test-file3"), + &Bytes::from("test-data3"), + ) + .await + .unwrap(); + storage + .create_dir( + "delete_many", + "create-dir", + namespace.clone(), + &path.join("inner-dir"), + ) + .await + .unwrap(); + storage + .delete_many( + "delete_many", + "delete-many", + namespace.clone(), + &[path.join("test-file1"), path.join("test-file3")], + ) + .await + .unwrap(); + + let mut entries = storage + .list_dir("delete_many", "entries", namespace.clone(), path) + .await + .unwrap(); + + entries.sort(); + + check!( + entries + == vec![ + Path::new("test-dir/inner-dir").to_path_buf(), + Path::new("test-dir/test-file2").to_path_buf(), + ] + ); +} + +#[test] +#[tracing::instrument] +async fn list_dir_root( + #[dimension(storage)] test: &Arc, + #[dimension(ns)] namespace: &BlobStorageNamespace, +) { + let storage = test.get_blob_storage().await; + + storage + .put_raw( + "list_dir_root", + "put-raw", + namespace.clone(), + Path::new("test-file1"), + &Bytes::from("test-data1"), + ) + .await + .unwrap(); + storage + .put_raw( + "list_dir_root", + "put-raw-2", + namespace.clone(), + Path::new("test-file2"), + &Bytes::from("test-data2"), + ) + .await + .unwrap(); + storage + .create_dir( + "list_dir_root", + "create-dir", + namespace.clone(), + Path::new("inner-dir"), + ) + .await + .unwrap(); + let mut entries = storage + .list_dir( + "list_dir_root", + "list-dir", + namespace.clone(), + Path::new(""), + ) + .await + .unwrap(); + + entries.sort(); + + check!( + entries + == vec![ + Path::new("inner-dir").to_path_buf(), + Path::new("test-file1").to_path_buf(), + Path::new("test-file2").to_path_buf(), + ] + ); +} + +#[test] +#[tracing::instrument] +async fn list_dir_root_only_subdirs( + #[dimension(storage)] test: &Arc, + #[dimension(ns)] namespace: &BlobStorageNamespace, +) { + let storage = test.get_blob_storage().await; + + storage + .create_dir( + "list_dir_root", + "create-dir", + namespace.clone(), + Path::new("inner-dir1"), + ) + .await + .unwrap(); + storage + .create_dir( + "list_dir_root", + "create-dir", + namespace.clone(), + Path::new("inner-dir2"), + ) + .await + .unwrap(); + + storage + .put_raw( + "list_dir_root", + "put-raw", + namespace.clone(), + Path::new("inner-dir1/test-file1"), + &Bytes::from("test-data1"), + ) + .await + .unwrap(); + storage + .put_raw( + "list_dir_root", + "put-raw-2", + namespace.clone(), + Path::new("inner-dir2/test-file2"), + &Bytes::from("test-data2"), + ) + .await + .unwrap(); + let mut entries = storage + .list_dir( + "list_dir_root", + "list-dir", + namespace.clone(), + Path::new(""), + ) + .await + .unwrap(); + + entries.sort(); + + check!( + entries + == vec![ + Path::new("inner-dir1").to_path_buf(), + Path::new("inner-dir2").to_path_buf(), + ] + ); +} + +#[test] +#[tracing::instrument] +async fn list_dir_same_prefix( + #[dimension(storage)] test: &Arc, + #[dimension(ns)] namespace: &BlobStorageNamespace, +) { + let storage = test.get_blob_storage().await; + + let path1 = Path::new("test-dir"); + let path2 = Path::new("test-dir2"); + let path3 = Path::new("test-dir3"); + storage + .create_dir("list_dir", "create-dir", namespace.clone(), path1) + .await + .unwrap(); + storage + .create_dir("list_dir", "create-dir-2", namespace.clone(), path2) + .await + .unwrap(); + storage + .create_dir("list_dir", "create-dir-3", namespace.clone(), path3) + .await + .unwrap(); + storage + .put_raw( + "list_dir_same_prefix", + "put-raw", + namespace.clone(), + &path1.join("test-file1"), + &Bytes::from("test-data1"), + ) + .await + .unwrap(); + storage + .put_raw( + "list_dir_same_prefix", + "put-raw", + namespace.clone(), + &path1.join("test-file2"), + &Bytes::from("test-data2"), + ) + .await + .unwrap(); + storage + .create_dir( + "list_dir_same_prefix", + "create-dir", + namespace.clone(), + &path1.join("inner-dir"), + ) + .await + .unwrap(); + let mut entries = storage + .list_dir("list_dir_same_prefix", "entries", namespace.clone(), path1) + .await + .unwrap(); + + entries.sort(); + + check!( + entries + == vec![ + Path::new("test-dir/inner-dir").to_path_buf(), + Path::new("test-dir/test-file1").to_path_buf(), + Path::new("test-dir/test-file2").to_path_buf(), + ] + ); +} diff --git a/golem-worker-executor-base/tests/indexed_storage.rs b/golem-worker-executor-base/tests/indexed_storage.rs index 1b5b2efcde..3e29f48922 100644 --- a/golem-worker-executor-base/tests/indexed_storage.rs +++ b/golem-worker-executor-base/tests/indexed_storage.rs @@ -13,889 +13,915 @@ // limitations under the License. use crate::WorkerExecutorTestDependencies; +use assert2::check; +use async_trait::async_trait; use golem_common::config::RedisConfig; use golem_common::redis::RedisPool; use golem_service_base::storage::sqlite::SqlitePool; use golem_test_framework::components::redis::Redis; -use golem_test_framework::components::redis_monitor::RedisMonitor; use golem_test_framework::config::TestDependencies; use golem_worker_executor_base::storage::indexed::memory::InMemoryIndexedStorage; use golem_worker_executor_base::storage::indexed::redis::RedisIndexedStorage; use golem_worker_executor_base::storage::indexed::sqlite::SqliteIndexedStorage; -use golem_worker_executor_base::storage::indexed::{IndexedStorage, IndexedStorageNamespace}; +use golem_worker_executor_base::storage::indexed::{ + IndexedStorage, IndexedStorageNamespace, ScanCursor, +}; use sqlx::sqlite::SqlitePoolOptions; +use std::fmt::Debug; use std::sync::Arc; -use test_r::inherit_test_dep; +use test_r::{define_matrix_dimension, inherit_test_dep, test, test_dep}; use uuid::Uuid; -pub(crate) trait GetIndexedStorage { - fn get_indexed_storage(&self) -> &dyn IndexedStorage; +#[async_trait] +trait GetIndexedStorage: Debug { + async fn get_indexed_storage(&self) -> Arc; } -struct InMemoryIndexedStorageWrapper { - kvs: InMemoryIndexedStorage, +struct InMemoryIndexedStorageWrapper; + +impl Debug for InMemoryIndexedStorageWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "InMemoryIndexedStorageWrapper") + } } +#[async_trait] impl GetIndexedStorage for InMemoryIndexedStorageWrapper { - fn get_indexed_storage(&self) -> &dyn IndexedStorage { - &self.kvs + async fn get_indexed_storage(&self) -> Arc { + let kvs = InMemoryIndexedStorage::new(); + Arc::new(kvs) } } -pub(crate) async fn in_memory_storage( +#[test_dep(tagged_as = "in_memory")] +async fn in_memory_storage( _deps: &WorkerExecutorTestDependencies, -) -> impl GetIndexedStorage { - let kvs = InMemoryIndexedStorage::new(); - InMemoryIndexedStorageWrapper { kvs } +) -> Arc { + Arc::new(InMemoryIndexedStorageWrapper) } struct RedisIndexedStorageWrapper { - kvs: RedisIndexedStorage, - _redis: Arc, - _monitor: Arc, + redis: Arc, +} + +impl Debug for RedisIndexedStorageWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "RedisIndexedStorageWrapper") + } } +#[async_trait] impl GetIndexedStorage for RedisIndexedStorageWrapper { - fn get_indexed_storage(&self) -> &dyn IndexedStorage { - &self.kvs + async fn get_indexed_storage(&self) -> Arc { + let random_prefix = Uuid::new_v4(); + let redis_pool = RedisPool::configured(&RedisConfig { + host: self.redis.public_host(), + port: self.redis.public_port(), + database: 0, + tracing: false, + pool_size: 1, + retries: Default::default(), + key_prefix: random_prefix.to_string(), + username: None, + password: None, + }) + .await + .unwrap(); + let kvs = RedisIndexedStorage::new(redis_pool); + Arc::new(kvs) } } -pub(crate) async fn redis_storage(deps: &WorkerExecutorTestDependencies) -> impl GetIndexedStorage { +#[test_dep(tagged_as = "redis")] +async fn redis_storage( + deps: &WorkerExecutorTestDependencies, +) -> Arc { let redis = deps.redis(); let redis_monitor = deps.redis_monitor(); redis.assert_valid(); redis_monitor.assert_valid(); - let random_prefix = Uuid::new_v4(); - let redis_pool = RedisPool::configured(&RedisConfig { - host: redis.public_host(), - port: redis.public_port(), - database: 0, - tracing: false, - pool_size: 1, - retries: Default::default(), - key_prefix: random_prefix.to_string(), - username: None, - password: None, - }) - .await - .unwrap(); - let kvs = RedisIndexedStorage::new(redis_pool); - RedisIndexedStorageWrapper { - kvs, - _redis: redis, - _monitor: redis_monitor, - } + Arc::new(RedisIndexedStorageWrapper { redis }) } -struct SqliteIndexedStorageWrapper { - sis: SqliteIndexedStorage, +struct SqliteIndexedStorageWrapper; + +impl Debug for SqliteIndexedStorageWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "SqliteIndexedStorageWrapper") + } } +#[async_trait] impl GetIndexedStorage for SqliteIndexedStorageWrapper { - fn get_indexed_storage(&self) -> &dyn IndexedStorage { - &self.sis + async fn get_indexed_storage(&self) -> Arc { + let sqlx_pool_sqlite = SqlitePoolOptions::new() + .max_connections(10) + .connect("sqlite::memory:") + .await + .expect("Cannot create db options"); + + let pool = SqlitePool::new(sqlx_pool_sqlite) + .await + .expect("Cannot connect to sqlite db"); + let sis = SqliteIndexedStorage::new(pool).await.unwrap(); + Arc::new(sis) } } -pub(crate) async fn sqlite_storage( +#[test_dep(tagged_as = "sqlite")] +async fn sqlite_storage( _deps: &WorkerExecutorTestDependencies, -) -> impl GetIndexedStorage { - let sqlx_pool_sqlite = SqlitePoolOptions::new() - .max_connections(10) - .connect("sqlite::memory:") - .await - .expect("Cannot create db options"); - - let pool = SqlitePool::new(sqlx_pool_sqlite) - .await - .expect("Cannot connect to sqlite db"); - let sis = SqliteIndexedStorage::new(pool).await.unwrap(); - SqliteIndexedStorageWrapper { sis } +) -> Arc { + Arc::new(SqliteIndexedStorageWrapper) } -pub fn ns() -> IndexedStorageNamespace { +#[test_dep(tagged_as = "ns1")] +fn ns() -> IndexedStorageNamespace { IndexedStorageNamespace::OpLog } -pub fn ns2() -> IndexedStorageNamespace { +#[test_dep(tagged_as = "ns2")] +fn ns2() -> IndexedStorageNamespace { IndexedStorageNamespace::CompressedOpLog { level: 1 } } inherit_test_dep!(WorkerExecutorTestDependencies); -macro_rules! test_indexed_storage { - ( $name:ident, $init:expr ) => { - mod $name { - use test_r::{inherit_test_dep, test}; - - use crate::indexed_storage::GetIndexedStorage; - use crate::WorkerExecutorTestDependencies; - use assert2::check; - use golem_worker_executor_base::storage::indexed::ScanCursor; - - inherit_test_dep!(WorkerExecutorTestDependencies); - - #[test] - #[tracing::instrument] - async fn exists_append(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - - let result1 = is.exists("svc", "api", ns.clone(), &key1).await.unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 1, value1) - .await - .unwrap(); - let result2 = is.exists("svc", "api", ns.clone(), &key1).await.unwrap(); - - check!(result1 == false); - check!(result2 == true); - } - - #[test] - #[tracing::instrument] - async fn namespaces_are_separate(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns1 = crate::indexed_storage::ns(); - let ns2 = crate::indexed_storage::ns2(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns1.clone(), &key1, 1, value1) - .await - .unwrap(); - let result = is.exists("svc", "api", ns2.clone(), &key1).await.unwrap(); - - check!(result == false); - } - - #[test] - #[tracing::instrument] - async fn can_append_and_get(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - let value3 = "value3".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 1, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 2, value2) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 3, value3) - .await - .unwrap(); - - let result = is - .read("svc", "api", "entity", ns.clone(), &key1, 1, 3) - .await - .unwrap(); - - check!(result == vec![(1, value1.into()), (2, value2.into()), (3, value3.into())]); - } - - #[test] - #[tracing::instrument] - async fn append_cannot_overwrite(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 1, value1) - .await - .unwrap(); - let result1 = is - .append("svc", "api", "entity", ns.clone(), &key1, 1, value2) - .await; - - check!(result1.is_err()); - } - - #[test] - #[tracing::instrument] - async fn append_can_skip(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 4, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 8, value2) - .await - .unwrap(); - - let result = is - .read("svc", "api", "entity", ns.clone(), &key1, 1, 10) - .await - .unwrap(); - - check!(result == vec![(4, value1.into()), (8, value2.into())]); - } - - #[test] - #[tracing::instrument] - async fn length(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let result1 = is.length("svc", "api", ns.clone(), &key1).await.unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 4, value1) - .await - .unwrap(); - let result2 = is.length("svc", "api", ns.clone(), &key1).await.unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 8, value2) - .await - .unwrap(); - let result3 = is.length("svc", "api", ns.clone(), &key1).await.unwrap(); - - check!(result1 == 0); - check!(result2 == 1); - check!(result3 == 2); - } - - #[test] - #[tracing::instrument] - async fn scan_empty(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let mut result: Vec = Vec::new(); - let mut cursor = ScanCursor::default(); - loop { - let (next, chunk) = is - .scan("svc", "api", ns.clone(), "*", cursor, 10) - .await - .unwrap(); - result.extend(chunk); - cursor = next; - if next == 0 { - break; - } - } - - check!(result == Vec::::new()); - } - - #[test] - #[tracing::instrument] - async fn scan_with_no_pattern_single_paged(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let key2 = "key2"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 1, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key2, 1, value2) - .await - .unwrap(); - - let mut result: Vec = Vec::new(); - let mut cursor = ScanCursor::default(); - loop { - let (next, chunk) = is - .scan("svc", "api", ns.clone(), "*", cursor, 10) - .await - .unwrap(); - result.extend(chunk); - cursor = next; - if next == 0 { - break; - } - } - - result.sort(); - check!(result == vec![key1.to_string(), key2.to_string()]); - } - - #[test] - #[tracing::instrument] - async fn scan_with_no_pattern_paginated(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let key2 = "key2"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 1, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 2, value2) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key2, 1, value2) - .await - .unwrap(); - - let mut r1: Vec = Vec::new(); - let mut cursor = ScanCursor::default(); - loop { - let (next, chunk) = is - .scan("svc", "api", ns.clone(), "*", cursor, 1) - .await - .unwrap(); - r1.extend(chunk); - cursor = next; - - if r1.len() == 1 || cursor == 0 { - break; - } - } - - let mut r2: Vec = Vec::new(); - loop { - let (next, chunk) = is - .scan("svc", "api", ns.clone(), "*", cursor, 1) - .await - .unwrap(); - r2.extend(chunk); - cursor = next; - - if cursor == 0 { - break; - } - } - - let mut all = Vec::new(); - all.extend(r1.clone()); - all.extend(r2.clone()); - all.sort(); - - check!(r1.len() == 1); - check!(r2.len() == 1); - check!(all == vec![key1.to_string(), key2.to_string()]); - } - - #[test] - #[tracing::instrument] - async fn scan_with_prefix_pattern_single_paged(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let key2 = "other2"; - let key3 = "key3"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - let value3 = "value3".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 1, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key2, 1, value2) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key3, 1, value3) - .await - .unwrap(); - - let mut result: Vec = Vec::new(); - let mut cursor = ScanCursor::default(); - loop { - let (next, chunk) = is - .scan("svc", "api", ns.clone(), "key*", cursor, 10) - .await - .unwrap(); - result.extend(chunk); - cursor = next; - if next == 0 { - break; - } - } - - result.sort(); - check!(result == vec![key1.to_string(), key3.to_string()]); - } - - #[test] - #[tracing::instrument] - async fn scan_with_prefix_pattern_paginated(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let key2 = "other2"; - let key3 = "key3"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - let value3 = "value3".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 1, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key2, 1, value2) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key3, 1, value3) - .await - .unwrap(); - - let mut r1: Vec = Vec::new(); - let mut cursor = ScanCursor::default(); - loop { - let (next, chunk) = is - .scan("svc", "api", ns.clone(), "key*", cursor, 1) - .await - .unwrap(); - r1.extend(chunk); - cursor = next; - - if r1.len() == 1 || cursor == 0 { - break; - } - } - - let mut r2: Vec = Vec::new(); - loop { - let (next, chunk) = is - .scan("svc", "api", ns.clone(), "key*", cursor, 1) - .await - .unwrap(); - r2.extend(chunk); - cursor = next; - - if cursor == 0 { - break; - } - } - - let mut all = Vec::new(); - all.extend(r1.clone()); - all.extend(r2.clone()); - all.sort(); - - check!(r1.len() == 1); - check!(r2.len() == 1); - check!(all == vec![key1.to_string(), key3.to_string()]); - } - - #[test] - #[tracing::instrument] - async fn exists_append_delete(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - - let result1 = is.exists("svc", "api", ns.clone(), &key1).await.unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 1, value1) - .await - .unwrap(); - let _ = is.delete("svc", "api", ns.clone(), &key1).await.unwrap(); - let result2 = is.exists("svc", "api", ns.clone(), &key1).await.unwrap(); - - check!(result1 == false); - check!(result2 == false); - } - - #[test] - #[tracing::instrument] - async fn delete_is_per_namespace(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns1 = crate::indexed_storage::ns(); - let ns2 = crate::indexed_storage::ns2(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns1.clone(), &key1, 1, value1) - .await - .unwrap(); - let _ = is.delete("svc", "api", ns2.clone(), &key1).await.unwrap(); - let result = is.exists("svc", "api", ns1.clone(), &key1).await.unwrap(); - - check!(result == true); - } - - #[test] - #[tracing::instrument] - async fn delete_non_existing(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - - let result = is.delete("svc", "api", ns.clone(), &key1).await; - - check!(result.is_ok()); - } - - #[test] - #[tracing::instrument] - async fn first(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let result1 = is - .first("svc", "api", "entity", ns.clone(), &key1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 5, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 7, value2) - .await - .unwrap(); - let result2 = is - .first("svc", "api", "entity", ns.clone(), &key1) - .await - .unwrap(); - - check!(result1 == None); - check!(result2 == Some((5, value1.into()))); - } - - #[test] - #[tracing::instrument] - async fn last(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let result1 = is - .last("svc", "api", "entity", ns.clone(), &key1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 5, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 7, value2) - .await - .unwrap(); - let result2 = is - .last("svc", "api", "entity", ns.clone(), &key1) - .await - .unwrap(); - - check!(result1 == None); - check!(result2 == Some((7, value2.into()))); - } - - #[test] - #[tracing::instrument] - async fn closest_low(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let result1 = is - .closest("svc", "api", "entity", ns.clone(), &key1, 3) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 5, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 7, value2) - .await - .unwrap(); - let result2 = is - .closest("svc", "api", "entity", ns.clone(), &key1, 3) - .await - .unwrap(); - - check!(result1 == None); - check!(result2 == Some((5, value1.into()))); - } - - #[test] - #[tracing::instrument] - async fn closest_match(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let result1 = is - .closest("svc", "api", "entity", ns.clone(), &key1, 5) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 5, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 7, value2) - .await - .unwrap(); - let result2 = is - .closest("svc", "api", "entity", ns.clone(), &key1, 5) - .await - .unwrap(); - - check!(result1 == None); - check!(result2 == Some((5, value1.into()))); - } - - #[test] - #[tracing::instrument] - async fn closest_mid(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let result1 = is - .closest("svc", "api", "entity", ns.clone(), &key1, 6) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 5, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 7, value2) - .await - .unwrap(); - let result2 = is - .closest("svc", "api", "entity", ns.clone(), &key1, 6) - .await - .unwrap(); - - check!(result1 == None); - check!(result2 == Some((7, value2.into()))); - } - - #[test] - #[tracing::instrument] - async fn closest_high(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let result1 = is - .closest("svc", "api", "entity", ns.clone(), &key1, 10) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 5, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 7, value2) - .await - .unwrap(); - let result2 = is - .closest("svc", "api", "entity", ns.clone(), &key1, 10) - .await - .unwrap(); - - check!(result1 == None); - check!(result2 == None); - } - - #[test] - #[tracing::instrument] - async fn drop_prefix_no_match(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - let value3 = "value3".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 10, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 11, value2) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 12, value3) - .await - .unwrap(); - - let _ = is - .drop_prefix("svc", "api", ns.clone(), &key1, 5) - .await - .unwrap(); - let result = is - .read("svc", "api", "entity", ns.clone(), &key1, 1, 100) - .await - .unwrap(); - - check!( - result - == vec![ - (10, value1.into()), - (11, value2.into()), - (12, value3.into()) - ] - ); - } - - #[test] - #[tracing::instrument] - async fn drop_prefix_partial(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - let value3 = "value3".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 10, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 11, value2) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 12, value3) - .await - .unwrap(); - - let _ = is - .drop_prefix("svc", "api", ns.clone(), &key1, 10) - .await - .unwrap(); - let result = is - .read("svc", "api", "entity", ns.clone(), &key1, 1, 100) - .await - .unwrap(); - - check!(result == vec![(11, value2.into()), (12, value3.into())]); - } - - #[test] - #[tracing::instrument] - async fn drop_prefix_full(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let is = test.get_indexed_storage(); - let ns = crate::indexed_storage::ns(); - - let key1 = "key1"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - let value3 = "value3".as_bytes(); - - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 10, value1) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 11, value2) - .await - .unwrap(); - let _ = is - .append("svc", "api", "entity", ns.clone(), &key1, 12, value3) - .await - .unwrap(); - - let _ = is - .drop_prefix("svc", "api", ns.clone(), &key1, 20) - .await - .unwrap(); - let result = is - .read("svc", "api", "entity", ns.clone(), &key1, 1, 100) - .await - .unwrap(); - - check!(result == vec![]); - } +define_matrix_dimension!(is: Arc -> "in_memory", "redis", "sqlite"); + +#[test] +#[tracing::instrument] +async fn exists_append( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + + let result1 = is.exists("svc", "api", ns.clone(), key1).await.unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 1, value1) + .await + .unwrap(); + let result2 = is.exists("svc", "api", ns.clone(), key1).await.unwrap(); + + check!(result1 == false); + check!(result2 == true); +} + +#[test] +#[tracing::instrument] +async fn namespaces_are_separate( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns1: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + + is.append("svc", "api", "entity", ns1.clone(), key1, 1, value1) + .await + .unwrap(); + let result = is.exists("svc", "api", ns2.clone(), key1).await.unwrap(); + + check!(result == false); +} + +#[test] +#[tracing::instrument] +async fn can_append_and_get( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + let value3 = "value3".as_bytes(); + + is.append("svc", "api", "entity", ns.clone(), key1, 1, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 2, value2) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 3, value3) + .await + .unwrap(); + + let result = is + .read("svc", "api", "entity", ns.clone(), key1, 1, 3) + .await + .unwrap(); + + check!(result == vec![(1, value1.into()), (2, value2.into()), (3, value3.into())]); +} + +#[test] +#[tracing::instrument] +async fn append_cannot_overwrite( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + is.append("svc", "api", "entity", ns.clone(), key1, 1, value1) + .await + .unwrap(); + let result1 = is + .append("svc", "api", "entity", ns.clone(), key1, 1, value2) + .await; + + check!(result1.is_err()); +} + +#[test] +#[tracing::instrument] +async fn append_can_skip( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + is.append("svc", "api", "entity", ns.clone(), key1, 4, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 8, value2) + .await + .unwrap(); + + let result = is + .read("svc", "api", "entity", ns.clone(), key1, 1, 10) + .await + .unwrap(); + + check!(result == vec![(4, value1.into()), (8, value2.into())]); +} + +#[test] +#[tracing::instrument] +async fn length( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + let result1 = is.length("svc", "api", ns.clone(), key1).await.unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 4, value1) + .await + .unwrap(); + let result2 = is.length("svc", "api", ns.clone(), key1).await.unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 8, value2) + .await + .unwrap(); + let result3 = is.length("svc", "api", ns.clone(), key1).await.unwrap(); + + check!(result1 == 0); + check!(result2 == 1); + check!(result3 == 2); +} + +#[test] +#[tracing::instrument] +async fn scan_empty( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let mut result: Vec = Vec::new(); + let mut cursor = ScanCursor::default(); + loop { + let (next, chunk) = is + .scan("svc", "api", ns.clone(), "*", cursor, 10) + .await + .unwrap(); + result.extend(chunk); + cursor = next; + if next == 0 { + break; + } + } + + check!(result == Vec::::new()); +} + +#[test] +#[tracing::instrument] +async fn scan_with_no_pattern_single_paged( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let key2 = "key2"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + is.append("svc", "api", "entity", ns.clone(), key1, 1, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key2, 1, value2) + .await + .unwrap(); + + let mut result: Vec = Vec::new(); + let mut cursor = ScanCursor::default(); + loop { + let (next, chunk) = is + .scan("svc", "api", ns.clone(), "*", cursor, 10) + .await + .unwrap(); + result.extend(chunk); + cursor = next; + if next == 0 { + break; + } + } + + result.sort(); + check!(result == vec![key1.to_string(), key2.to_string()]); +} + +#[test] +#[tracing::instrument] +async fn scan_with_no_pattern_paginated( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let key2 = "key2"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + is.append("svc", "api", "entity", ns.clone(), key1, 1, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 2, value2) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key2, 1, value2) + .await + .unwrap(); + + let mut r1: Vec = Vec::new(); + let mut cursor = ScanCursor::default(); + loop { + let (next, chunk) = is + .scan("svc", "api", ns.clone(), "*", cursor, 1) + .await + .unwrap(); + r1.extend(chunk); + cursor = next; + + if r1.len() == 1 || cursor == 0 { + break; + } + } + + let mut r2: Vec = Vec::new(); + loop { + let (next, chunk) = is + .scan("svc", "api", ns.clone(), "*", cursor, 1) + .await + .unwrap(); + r2.extend(chunk); + cursor = next; + + if cursor == 0 { + break; + } + } + + let mut all = Vec::new(); + all.extend(r1.clone()); + all.extend(r2.clone()); + all.sort(); + + check!(r1.len() == 1); + check!(r2.len() == 1); + check!(all == vec![key1.to_string(), key2.to_string()]); +} + +#[test] +#[tracing::instrument] +async fn scan_with_prefix_pattern_single_paged( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let key2 = "other2"; + let key3 = "key3"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + let value3 = "value3".as_bytes(); + + is.append("svc", "api", "entity", ns.clone(), key1, 1, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key2, 1, value2) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key3, 1, value3) + .await + .unwrap(); + + let mut result: Vec = Vec::new(); + let mut cursor = ScanCursor::default(); + loop { + let (next, chunk) = is + .scan("svc", "api", ns.clone(), "key*", cursor, 10) + .await + .unwrap(); + result.extend(chunk); + cursor = next; + if next == 0 { + break; + } + } + + result.sort(); + check!(result == vec![key1.to_string(), key3.to_string()]); +} + +#[test] +#[tracing::instrument] +async fn scan_with_prefix_pattern_paginated( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let key2 = "other2"; + let key3 = "key3"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + let value3 = "value3".as_bytes(); + + is.append("svc", "api", "entity", ns.clone(), key1, 1, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key2, 1, value2) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key3, 1, value3) + .await + .unwrap(); + + let mut r1: Vec = Vec::new(); + let mut cursor = ScanCursor::default(); + loop { + let (next, chunk) = is + .scan("svc", "api", ns.clone(), "key*", cursor, 1) + .await + .unwrap(); + r1.extend(chunk); + cursor = next; + + if r1.len() == 1 || cursor == 0 { + break; + } + } + + let mut r2: Vec = Vec::new(); + loop { + let (next, chunk) = is + .scan("svc", "api", ns.clone(), "key*", cursor, 1) + .await + .unwrap(); + r2.extend(chunk); + cursor = next; + + if cursor == 0 { + break; } - }; + } + + let mut all = Vec::new(); + all.extend(r1.clone()); + all.extend(r2.clone()); + all.sort(); + + check!(r1.len() == 1); + check!(r2.len() == 1); + check!(all == vec![key1.to_string(), key3.to_string()]); +} + +#[test] +#[tracing::instrument] +async fn exists_append_delete( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + + let result1 = is.exists("svc", "api", ns.clone(), key1).await.unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 1, value1) + .await + .unwrap(); + is.delete("svc", "api", ns.clone(), key1).await.unwrap(); + let result2 = is.exists("svc", "api", ns.clone(), key1).await.unwrap(); + + check!(result1 == false); + check!(result2 == false); +} + +#[test] +#[tracing::instrument] +async fn delete_is_per_namespace( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns1: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + + is.append("svc", "api", "entity", ns1.clone(), key1, 1, value1) + .await + .unwrap(); + is.delete("svc", "api", ns2.clone(), key1).await.unwrap(); + let result = is.exists("svc", "api", ns1.clone(), key1).await.unwrap(); + + check!(result == true); +} + +#[test] +#[tracing::instrument] +async fn delete_non_existing( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + + let result = is.delete("svc", "api", ns.clone(), key1).await; + + check!(result.is_ok()); +} + +#[test] +#[tracing::instrument] +async fn first( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + let result1 = is + .first("svc", "api", "entity", ns.clone(), key1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 5, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 7, value2) + .await + .unwrap(); + let result2 = is + .first("svc", "api", "entity", ns.clone(), key1) + .await + .unwrap(); + + check!(result1 == None); + check!(result2 == Some((5, value1.into()))); +} + +#[test] +#[tracing::instrument] +async fn last( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + let result1 = is + .last("svc", "api", "entity", ns.clone(), key1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 5, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 7, value2) + .await + .unwrap(); + let result2 = is + .last("svc", "api", "entity", ns.clone(), key1) + .await + .unwrap(); + + check!(result1 == None); + check!(result2 == Some((7, value2.into()))); +} + +#[test] +#[tracing::instrument] +async fn closest_low( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + let result1 = is + .closest("svc", "api", "entity", ns.clone(), key1, 3) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 5, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 7, value2) + .await + .unwrap(); + let result2 = is + .closest("svc", "api", "entity", ns.clone(), key1, 3) + .await + .unwrap(); + + check!(result1 == None); + check!(result2 == Some((5, value1.into()))); +} + +#[test] +#[tracing::instrument] +async fn closest_match( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + let result1 = is + .closest("svc", "api", "entity", ns.clone(), key1, 5) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 5, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 7, value2) + .await + .unwrap(); + let result2 = is + .closest("svc", "api", "entity", ns.clone(), key1, 5) + .await + .unwrap(); + + check!(result1 == None); + check!(result2 == Some((5, value1.into()))); +} + +#[test] +#[tracing::instrument] +async fn closest_mid( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + let result1 = is + .closest("svc", "api", "entity", ns.clone(), key1, 6) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 5, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 7, value2) + .await + .unwrap(); + let result2 = is + .closest("svc", "api", "entity", ns.clone(), key1, 6) + .await + .unwrap(); + + check!(result1 == None); + check!(result2 == Some((7, value2.into()))); } -test_indexed_storage!(in_memory, crate::indexed_storage::in_memory_storage); -test_indexed_storage!(redis, crate::indexed_storage::redis_storage); -test_indexed_storage!(sqlite, crate::indexed_storage::sqlite_storage); +#[test] +#[tracing::instrument] +async fn closest_high( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + let result1 = is + .closest("svc", "api", "entity", ns.clone(), key1, 10) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 5, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 7, value2) + .await + .unwrap(); + let result2 = is + .closest("svc", "api", "entity", ns.clone(), key1, 10) + .await + .unwrap(); + + check!(result1 == None); + check!(result2 == None); +} + +#[test] +#[tracing::instrument] +async fn drop_prefix_no_match( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + let value3 = "value3".as_bytes(); + + is.append("svc", "api", "entity", ns.clone(), key1, 10, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 11, value2) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 12, value3) + .await + .unwrap(); + + is.drop_prefix("svc", "api", ns.clone(), key1, 5) + .await + .unwrap(); + let result = is + .read("svc", "api", "entity", ns.clone(), key1, 1, 100) + .await + .unwrap(); + + check!( + result + == vec![ + (10, value1.into()), + (11, value2.into()), + (12, value3.into()) + ] + ); +} + +#[test] +#[tracing::instrument] +async fn drop_prefix_partial( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + let value3 = "value3".as_bytes(); + + is.append("svc", "api", "entity", ns.clone(), key1, 10, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 11, value2) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 12, value3) + .await + .unwrap(); + + is.drop_prefix("svc", "api", ns.clone(), key1, 10) + .await + .unwrap(); + let result = is + .read("svc", "api", "entity", ns.clone(), key1, 1, 100) + .await + .unwrap(); + + check!(result == vec![(11, value2.into()), (12, value3.into())]); +} + +#[test] +#[tracing::instrument] +async fn drop_prefix_full( + deps: &WorkerExecutorTestDependencies, + #[dimension(is)] is: &Arc, + #[tagged_as("ns1")] ns: &IndexedStorageNamespace, + #[tagged_as("ns2")] ns2: &IndexedStorageNamespace, +) { + let is = is.get_indexed_storage().await; + + let key1 = "key1"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + let value3 = "value3".as_bytes(); + + is.append("svc", "api", "entity", ns.clone(), key1, 10, value1) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 11, value2) + .await + .unwrap(); + is.append("svc", "api", "entity", ns.clone(), key1, 12, value3) + .await + .unwrap(); + + is.drop_prefix("svc", "api", ns.clone(), key1, 20) + .await + .unwrap(); + let result = is + .read("svc", "api", "entity", ns.clone(), key1, 1, 100) + .await + .unwrap(); + + check!(result == vec![]); +} diff --git a/golem-worker-executor-base/tests/key_value_storage.rs b/golem-worker-executor-base/tests/key_value_storage.rs index eb8ea9d1e4..0744b276cc 100644 --- a/golem-worker-executor-base/tests/key_value_storage.rs +++ b/golem-worker-executor-base/tests/key_value_storage.rs @@ -13,778 +13,812 @@ // limitations under the License. use crate::WorkerExecutorTestDependencies; +use async_trait::async_trait; use golem_common::config::RedisConfig; use golem_common::model::AccountId; use golem_common::redis::RedisPool; use golem_service_base::storage::sqlite::SqlitePool; use golem_test_framework::components::redis::Redis; -use golem_test_framework::components::redis_monitor::RedisMonitor; use golem_test_framework::config::TestDependencies; use golem_worker_executor_base::storage::keyvalue::memory::InMemoryKeyValueStorage; use golem_worker_executor_base::storage::keyvalue::redis::RedisKeyValueStorage; use golem_worker_executor_base::storage::keyvalue::sqlite::SqliteKeyValueStorage; use golem_worker_executor_base::storage::keyvalue::{KeyValueStorage, KeyValueStorageNamespace}; use sqlx::sqlite::SqlitePoolOptions; +use std::fmt::{Debug, Formatter}; use std::sync::Arc; -use test_r::inherit_test_dep; +use test_r::{define_matrix_dimension, inherit_test_dep, test, test_dep}; use uuid::Uuid; -pub(crate) trait GetKeyValueStorage { - fn get_key_value_storage(&self) -> &dyn KeyValueStorage; +#[async_trait] +trait GetKeyValueStorage: Debug { + async fn get_key_value_storage(&self) -> Arc; } -struct InMemoryKeyValueStorageWrapper { - kvs: InMemoryKeyValueStorage, +struct InMemoryKeyValueStorageWrapper; + +impl Debug for InMemoryKeyValueStorageWrapper { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("InMemoryKeyValueStorageWrapper") + } } +#[async_trait] impl GetKeyValueStorage for InMemoryKeyValueStorageWrapper { - fn get_key_value_storage(&self) -> &dyn KeyValueStorage { - &self.kvs + async fn get_key_value_storage(&self) -> Arc { + Arc::new(InMemoryKeyValueStorage::new()) } } -pub(crate) async fn in_memory_storage( +#[test_dep(tagged_as = "in_memory")] +async fn in_memory_storage( _deps: &WorkerExecutorTestDependencies, -) -> impl GetKeyValueStorage { - let kvs = InMemoryKeyValueStorage::new(); - InMemoryKeyValueStorageWrapper { kvs } +) -> Arc { + Arc::new(InMemoryKeyValueStorageWrapper) } struct RedisKeyValueStorageWrapper { - kvs: RedisKeyValueStorage, - _redis: Arc, - _monitor: Arc, + redis: Arc, +} + +impl Debug for RedisKeyValueStorageWrapper { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("RedisKeyValueStorageWrapper") + } } +#[async_trait] impl GetKeyValueStorage for RedisKeyValueStorageWrapper { - fn get_key_value_storage(&self) -> &dyn KeyValueStorage { - &self.kvs + async fn get_key_value_storage(&self) -> Arc { + let random_prefix = Uuid::new_v4(); + let redis_pool = RedisPool::configured(&RedisConfig { + host: self.redis.public_host(), + port: self.redis.public_port(), + database: 0, + tracing: false, + pool_size: 1, + retries: Default::default(), + key_prefix: random_prefix.to_string(), + username: None, + password: None, + }) + .await + .unwrap(); + let kvs = RedisKeyValueStorage::new(redis_pool); + Arc::new(kvs) } } -pub(crate) async fn redis_storage( +#[test_dep(tagged_as = "redis")] +async fn redis_storage( deps: &WorkerExecutorTestDependencies, -) -> impl GetKeyValueStorage { +) -> Arc { let redis = deps.redis(); let redis_monitor = deps.redis_monitor(); redis.assert_valid(); redis_monitor.assert_valid(); - let random_prefix = Uuid::new_v4(); - let redis_pool = RedisPool::configured(&RedisConfig { - host: redis.public_host(), - port: redis.public_port(), - database: 0, - tracing: false, - pool_size: 1, - retries: Default::default(), - key_prefix: random_prefix.to_string(), - username: None, - password: None, - }) - .await - .unwrap(); - let kvs = RedisKeyValueStorage::new(redis_pool); - RedisKeyValueStorageWrapper { - kvs, - _redis: redis, - _monitor: redis_monitor, - } + Arc::new(RedisKeyValueStorageWrapper { redis }) } -struct SqliteKeyValueStorageWrapper { - kvs: SqliteKeyValueStorage, +struct SqliteKeyValueStorageWrapper; + +impl Debug for SqliteKeyValueStorageWrapper { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("SqliteKeyValueStorageWrapper") + } } +#[async_trait] impl GetKeyValueStorage for SqliteKeyValueStorageWrapper { - fn get_key_value_storage(&self) -> &dyn KeyValueStorage { - &self.kvs + async fn get_key_value_storage(&self) -> Arc { + let sqlx_pool_sqlite = SqlitePoolOptions::new() + .max_connections(10) + .connect("sqlite::memory:") + .await + .expect("Cannot create db options"); + + let pool = SqlitePool::new(sqlx_pool_sqlite) + .await + .expect("Cannot connect to sqlite db"); + + let kvs = SqliteKeyValueStorage::new(pool).await.unwrap(); + Arc::new(kvs) + } +} + +#[test_dep(tagged_as = "sqlite")] +async fn sqlite_storage( + _deps: &WorkerExecutorTestDependencies, +) -> Arc { + Arc::new(SqliteKeyValueStorageWrapper) +} + +#[derive(Debug)] +struct Namespaces { + pub ns: KeyValueStorageNamespace, + pub ns2: KeyValueStorageNamespace, +} + +#[test_dep(tagged_as = "ns1")] +fn ns() -> Namespaces { + Namespaces { + ns: KeyValueStorageNamespace::Worker, + ns2: KeyValueStorageNamespace::UserDefined { + account_id: AccountId::generate(), + bucket: "test-bucket".to_string(), + }, + } +} + +#[test_dep(tagged_as = "ns2")] +fn ns2() -> Namespaces { + Namespaces { + ns: KeyValueStorageNamespace::UserDefined { + account_id: AccountId::generate(), + bucket: "test-bucket".to_string(), + }, + ns2: KeyValueStorageNamespace::Worker, } } -pub(crate) async fn sqlite_storage( +inherit_test_dep!(WorkerExecutorTestDependencies); + +define_matrix_dimension!(kvs: Arc -> "in_memory", "redis", "sqlite"); +define_matrix_dimension!(nss: Namespaces -> "ns1", "ns2"); + +#[test] +#[tracing::instrument] +async fn get_set_get( _deps: &WorkerExecutorTestDependencies, -) -> impl GetKeyValueStorage { - let sqlx_pool_sqlite = SqlitePoolOptions::new() - .max_connections(10) - .connect("sqlite::memory:") + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + + let key = "key"; + let value = "value".as_bytes(); + + let result1 = kvs + .get("test", "api", "entity", ns.clone(), key) .await - .expect("Cannot create db options"); + .unwrap(); + kvs.set("test", "api", "entity", ns.clone(), key, value) + .await + .unwrap(); + let result2 = kvs.get("test", "api", "entity", ns, key).await.unwrap(); + assert_eq!(result1, None); + assert_eq!(result2, Some(value.into())); +} - let pool = SqlitePool::new(sqlx_pool_sqlite) +#[test] +#[tracing::instrument] +async fn namespaces_are_separate( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns1 = nss.ns.clone(); + let ns2 = nss.ns2.clone(); + + let key = "key"; + let value = "value".as_bytes(); + let value2 = "value2".as_bytes(); + + let result11 = kvs + .get("test", "api", "entity", ns1.clone(), key) + .await + .unwrap(); + kvs.set("test", "api", "entity", ns1.clone(), key, value) + .await + .unwrap(); + let result12 = kvs + .get("test", "api", "entity", ns2.clone(), key) .await - .expect("Cannot connect to sqlite db"); + .unwrap(); + kvs.set("test", "api", "entity", ns2.clone(), key, value2) + .await + .unwrap(); + let result21 = kvs.get("test", "api", "entity", ns1, key).await.unwrap(); + let result22 = kvs.get("test", "api", "entity", ns2, key).await.unwrap(); + assert_eq!(result11, None); + assert_eq!(result12, None); + assert_eq!(result21, Some(value.into())); + assert_eq!(result22, Some(value2.into())); +} - let kvs = SqliteKeyValueStorage::new(pool).await.unwrap(); - SqliteKeyValueStorageWrapper { kvs } +#[test] +#[tracing::instrument] +async fn get_set_get_many( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + + let key1 = "key1"; + let key2 = "key2"; + let key3 = "key3"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + let result1 = kvs + .get_many( + "test", + "api", + "entity", + ns.clone(), + vec![key1.to_string(), key2.to_string(), key3.to_string()], + ) + .await + .unwrap(); + kvs.set_many( + "test", + "api", + "entity", + ns.clone(), + &[(key1, value1), (key2, value2)], + ) + .await + .unwrap(); + let result2 = kvs + .get_many( + "test", + "api", + "entity", + ns, + vec![key1.to_string(), key2.to_string(), key3.to_string()], + ) + .await + .unwrap(); + assert_eq!(result1, vec![None, None, None]); + assert_eq!( + result2, + vec![Some(value1.into()), Some(value2.into()), None] + ); } -pub fn ns() -> KeyValueStorageNamespace { - KeyValueStorageNamespace::Worker +#[test] +#[tracing::instrument] +async fn set_if_not_exists( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + + let key = "key"; + let value1 = "value".as_bytes(); + let value2 = "value2".as_bytes(); + + let result1 = kvs + .set_if_not_exists("test", "api", "entity", ns.clone(), key, value1) + .await + .unwrap(); + let result2 = kvs + .set_if_not_exists("test", "api", "entity", ns.clone(), key, value2) + .await + .unwrap(); + let result3 = kvs.get("test", "api", "entity", ns, key).await.unwrap(); + assert!(result1); + assert!(!result2); + assert_eq!(result3, Some(value1.into())); } -pub fn ns2() -> KeyValueStorageNamespace { - KeyValueStorageNamespace::UserDefined { - account_id: AccountId::generate(), - bucket: "test-bucket".to_string(), - } +#[test] +#[tracing::instrument] +async fn del( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + + let key = "key"; + let value = "value".as_bytes(); + + kvs.del("test", "api", ns.clone(), key).await.unwrap(); // deleting non-existing key must succeed + kvs.set("test", "api", "entity", ns.clone(), key, value) + .await + .unwrap(); + let result1 = kvs + .get("test", "api", "entity", ns.clone(), key) + .await + .unwrap(); + kvs.del("test", "api", ns.clone(), key).await.unwrap(); + let result2 = kvs.get("test", "api", "entity", ns, key).await.unwrap(); + + assert_eq!(result1, Some(value.into())); + assert_eq!(result2, None); } -inherit_test_dep!(WorkerExecutorTestDependencies); +#[test] +#[tracing::instrument] +async fn del_many( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + + let key1 = "key"; + let key2 = "key2"; + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + kvs.del_many( + "test", + "api", + ns.clone(), + vec![key1.to_string(), key2.to_string()], + ) + .await + .unwrap(); // deleting non-existing key must succeed + kvs.set("test", "api", "entity", ns.clone(), key1, value1) + .await + .unwrap(); + kvs.set("test", "api", "entity", ns.clone(), key2, value2) + .await + .unwrap(); + let result1 = kvs + .get("test", "api", "entity", ns.clone(), key1) + .await + .unwrap(); + let result2 = kvs + .get("test", "api", "entity", ns.clone(), key2) + .await + .unwrap(); + kvs.del_many( + "test", + "api", + ns.clone(), + vec![key1.to_string(), key2.to_string()], + ) + .await + .unwrap(); + let result3 = kvs + .get("test", "api", "entity", ns.clone(), key1) + .await + .unwrap(); + let result4 = kvs + .get("test", "api", "entity", ns.clone(), key2) + .await + .unwrap(); -macro_rules! test_kv_storage { - ( $name:ident, $init:expr, $ns:expr, $ns2:expr ) => { - mod $name { - use test_r::{inherit_test_dep, test}; - - use crate::key_value_storage::GetKeyValueStorage; - use crate::WorkerExecutorTestDependencies; - - inherit_test_dep!(WorkerExecutorTestDependencies); - - #[test] - #[tracing::instrument] - async fn get_set_get(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - - let key = "key"; - let value = "value".as_bytes(); - - let result1 = kvs - .get("test", "api", "entity", ns.clone(), key) - .await - .unwrap(); - kvs.set("test", "api", "entity", ns.clone(), key, value) - .await - .unwrap(); - let result2 = kvs.get("test", "api", "entity", ns, key).await.unwrap(); - assert_eq!(result1, None); - assert_eq!(result2, Some(value.into())); - } - - #[test] - #[tracing::instrument] - async fn namespaces_are_separate(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns1 = $ns(); - let ns2 = $ns2(); - - let key = "key"; - let value = "value".as_bytes(); - let value2 = "value2".as_bytes(); - - let result11 = kvs - .get("test", "api", "entity", ns1.clone(), key) - .await - .unwrap(); - kvs.set("test", "api", "entity", ns1.clone(), key, value) - .await - .unwrap(); - let result12 = kvs - .get("test", "api", "entity", ns2.clone(), key) - .await - .unwrap(); - kvs.set("test", "api", "entity", ns2.clone(), key, value2) - .await - .unwrap(); - let result21 = kvs.get("test", "api", "entity", ns1, key).await.unwrap(); - let result22 = kvs.get("test", "api", "entity", ns2, key).await.unwrap(); - assert_eq!(result11, None); - assert_eq!(result12, None); - assert_eq!(result21, Some(value.into())); - assert_eq!(result22, Some(value2.into())); - } - - #[test] - #[tracing::instrument] - async fn get_set_get_many(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - - let key1 = "key1"; - let key2 = "key2"; - let key3 = "key3"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let result1 = kvs - .get_many( - "test", - "api", - "entity", - ns.clone(), - vec![key1.to_string(), key2.to_string(), key3.to_string()], - ) - .await - .unwrap(); - kvs.set_many( - "test", - "api", - "entity", - ns.clone(), - &[(key1, value1), (key2, value2)], - ) - .await - .unwrap(); - let result2 = kvs - .get_many( - "test", - "api", - "entity", - ns, - vec![key1.to_string(), key2.to_string(), key3.to_string()], - ) - .await - .unwrap(); - assert_eq!(result1, vec![None, None, None]); - assert_eq!( - result2, - vec![Some(value1.into()), Some(value2.into()), None] - ); - } - - #[test] - #[tracing::instrument] - async fn set_if_not_exists(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - - let key = "key"; - let value1 = "value".as_bytes(); - let value2 = "value2".as_bytes(); - - let result1 = kvs - .set_if_not_exists("test", "api", "entity", ns.clone(), key, value1) - .await - .unwrap(); - let result2 = kvs - .set_if_not_exists("test", "api", "entity", ns.clone(), key, value2) - .await - .unwrap(); - let result3 = kvs.get("test", "api", "entity", ns, key).await.unwrap(); - assert_eq!(result1, true); - assert_eq!(result2, false); - assert_eq!(result3, Some(value1.into())); - } - - #[test] - #[tracing::instrument] - async fn del(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - - let key = "key"; - let value = "value".as_bytes(); - - let _ = kvs.del("test", "api", ns.clone(), key).await.unwrap(); // deleting non-existing key must succeed - kvs.set("test", "api", "entity", ns.clone(), key, value) - .await - .unwrap(); - let result1 = kvs - .get("test", "api", "entity", ns.clone(), key) - .await - .unwrap(); - let _ = kvs.del("test", "api", ns.clone(), key).await.unwrap(); - let result2 = kvs.get("test", "api", "entity", ns, key).await.unwrap(); - - assert_eq!(result1, Some(value.into())); - assert_eq!(result2, None); - } - - #[test] - #[tracing::instrument] - async fn del_many(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - - let key1 = "key"; - let key2 = "key2"; - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - let _ = kvs - .del_many( - "test", - "api", - ns.clone(), - vec![key1.to_string(), key2.to_string()], - ) - .await - .unwrap(); // deleting non-existing key must succeed - kvs.set("test", "api", "entity", ns.clone(), key1, value1) - .await - .unwrap(); - kvs.set("test", "api", "entity", ns.clone(), key2, value2) - .await - .unwrap(); - let result1 = kvs - .get("test", "api", "entity", ns.clone(), key1) - .await - .unwrap(); - let result2 = kvs - .get("test", "api", "entity", ns.clone(), key2) - .await - .unwrap(); - let _ = kvs - .del_many( - "test", - "api", - ns.clone(), - vec![key1.to_string(), key2.to_string()], - ) - .await - .unwrap(); - let result3 = kvs - .get("test", "api", "entity", ns.clone(), key1) - .await - .unwrap(); - let result4 = kvs - .get("test", "api", "entity", ns.clone(), key2) - .await - .unwrap(); - - assert_eq!(result1, Some(value1.into())); - assert_eq!(result2, Some(value2.into())); - assert_eq!(result3, None); - assert_eq!(result4, None); - } - - #[test] - #[tracing::instrument] - async fn exists_set_exists(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - - let key = "key"; - let value = "value".as_bytes(); - - let result1 = kvs.exists("test", "api", ns.clone(), key).await.unwrap(); - kvs.set("test", "api", "entity", ns.clone(), key, value) - .await - .unwrap(); - let result2 = kvs.exists("test", "api", ns, key).await.unwrap(); - assert_eq!(result1, false); - assert_eq!(result2, true); - } - - #[test] - #[tracing::instrument] - async fn exists_is_per_namespace(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - let ns2 = $ns2(); - - let key = "key"; - let value = "value".as_bytes(); - - let result1 = kvs.exists("test", "api", ns.clone(), key).await.unwrap(); - kvs.set("test", "api", "entity", ns.clone(), key, value) - .await - .unwrap(); - let result2 = kvs.exists("test", "api", ns, key).await.unwrap(); - let result3 = kvs.exists("test", "api", ns2, key).await.unwrap(); - assert_eq!(result1, false); - assert_eq!(result2, true); - assert_eq!(result3, false); - } - - #[test] - #[tracing::instrument] - async fn keys(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns2(); - - let key1 = "key1"; - let key2 = "key2"; - - let keys1 = kvs.keys("test", "api", ns.clone()).await.unwrap(); - kvs.set( - "test", - "api", - "entity", - ns.clone(), - key1, - "value1".as_bytes(), - ) - .await - .unwrap(); - kvs.set( - "test", - "api", - "entity", - ns.clone(), - key2, - "value2".as_bytes(), - ) - .await - .unwrap(); - let keys2 = kvs.keys("test", "api", ns.clone()).await.unwrap(); - kvs.del("test", "api", ns.clone(), key1).await.unwrap(); - let keys3 = kvs.keys("test", "api", ns).await.unwrap(); - - tracing::debug!("keys2: {keys2:?}"); - - assert_eq!(keys1, Vec::::new()); - assert_eq!(keys2.len(), 2); - assert_eq!(keys2.contains(&key1.to_string()), true); - assert_eq!(keys2.contains(&key2.to_string()), true); - assert_eq!(keys3, vec![key2.to_string()]); - } - - #[test] - #[tracing::instrument] - async fn sets(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - - let set1 = "set1"; - let set2 = "set2"; - - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - let value3 = "value3".as_bytes(); - - let s11 = kvs - .members_of_set("test", "api", "entity", ns.clone(), set1) - .await - .unwrap(); - let s21 = kvs - .members_of_set("test", "api", "entity", ns.clone(), set2) - .await - .unwrap(); - - kvs.add_to_set("test", "api", "entity", ns.clone(), set1, value1) - .await - .unwrap(); - kvs.add_to_set("test", "api", "entity", ns.clone(), set1, value2) - .await - .unwrap(); - kvs.add_to_set("test", "api", "entity", ns.clone(), set1, value2) - .await - .unwrap(); - - kvs.add_to_set("test", "api", "entity", ns.clone(), set2, value3) - .await - .unwrap(); - kvs.add_to_set("test", "api", "entity", ns.clone(), set2, value3) - .await - .unwrap(); - kvs.add_to_set("test", "api", "entity", ns.clone(), set2, value2) - .await - .unwrap(); - - let s12 = kvs - .members_of_set("test", "api", "entity", ns.clone(), set1) - .await - .unwrap(); - let s22 = kvs - .members_of_set("test", "api", "entity", ns.clone(), set2) - .await - .unwrap(); - - kvs.remove_from_set("test", "api", "entity", ns.clone(), set1, value2) - .await - .unwrap(); - kvs.remove_from_set("test", "api", "entity", ns.clone(), set2, value2) - .await - .unwrap(); - - let s13 = kvs - .members_of_set("test", "api", "entity", ns.clone(), set1) - .await - .unwrap(); - let s23 = kvs - .members_of_set("test", "api", "entity", ns.clone(), set2) - .await - .unwrap(); - - kvs.remove_from_set("test", "api", "entity", ns.clone(), set1, value2) - .await - .unwrap(); // can remove non-existing value - kvs.remove_from_set("test", "api", "entity", ns.clone(), set2, value2) - .await - .unwrap(); // can remove non-existing value - - let s14 = kvs - .members_of_set("test", "api", "entity", ns.clone(), set1) - .await - .unwrap(); - let s24 = kvs - .members_of_set("test", "api", "entity", ns.clone(), set2) - .await - .unwrap(); - - assert2::check!(s11 == Vec::>::new()); - assert2::check!(s21 == Vec::>::new()); - assert2::check!(s12.len() == 2); - assert2::check!(s12.contains(&value1.to_vec().into())); - assert2::check!(s12.contains(&value2.to_vec().into())); - assert2::check!(s22.len() == 2); - assert2::check!(s22.contains(&value2.to_vec().into())); - assert2::check!(s22.contains(&value3.to_vec().into())); - assert2::check!(s13.len() == 1); - assert2::check!(s13.contains(&value1.to_vec().into())); - assert2::check!(s23.len() == 1); - assert2::check!(s23.contains(&value3.to_vec().into())); - assert2::check!(s14.len() == 1); - assert2::check!(s14.contains(&value1.to_vec().into())); - assert2::check!(s24.len() == 1); - assert2::check!(s24.contains(&value3.to_vec().into())); - } - - #[test] - #[tracing::instrument] - async fn sorted_sets(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - - let set1 = "set1"; - let set2 = "set2"; - - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - let value3 = "value3".as_bytes(); - let value4 = "value4".as_bytes(); - - let s11 = kvs - .get_sorted_set("test", "api", "entity", ns.clone(), set1) - .await - .unwrap(); - let s21 = kvs - .get_sorted_set("test", "api", "entity", ns.clone(), set2) - .await - .unwrap(); - - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 4.0, value4) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 1.0, value1) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 2.0, value2) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 2.0, value2) - .await - .unwrap(); - - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set2, 4.0, value4) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set2, 3.0, value3) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set2, 3.0, value3) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set2, 2.0, value2) - .await - .unwrap(); - - let s12 = kvs - .get_sorted_set("test", "api", "entity", ns.clone(), set1) - .await - .unwrap(); - let s22 = kvs - .get_sorted_set("test", "api", "entity", ns.clone(), set2) - .await - .unwrap(); - - kvs.remove_from_sorted_set("test", "api", "entity", ns.clone(), set1, value2) - .await - .unwrap(); - kvs.remove_from_sorted_set("test", "api", "entity", ns.clone(), set2, value2) - .await - .unwrap(); - - let s13 = kvs - .get_sorted_set("test", "api", "entity", ns.clone(), set1) - .await - .unwrap(); - let s23 = kvs - .get_sorted_set("test", "api", "entity", ns.clone(), set2) - .await - .unwrap(); - - kvs.remove_from_sorted_set("test", "api", "entity", ns.clone(), set1, value2) - .await - .unwrap(); // can remove non-existing value - kvs.remove_from_sorted_set("test", "api", "entity", ns.clone(), set2, value2) - .await - .unwrap(); // can remove non-existing value - - let s14 = kvs - .get_sorted_set("test", "api", "entity", ns.clone(), set1) - .await - .unwrap(); - let s24 = kvs - .get_sorted_set("test", "api", "entity", ns.clone(), set2) - .await - .unwrap(); - - assert_eq!(s11, Vec::<(f64, bytes::Bytes)>::new()); - assert_eq!(s21, Vec::<(f64, bytes::Bytes)>::new()); - - assert_eq!( - s12, - vec![ - (1.0, value1.into()), - (2.0, value2.into()), - (4.0, value4.into()) - ] - ); - assert_eq!( - s22, - vec![ - (2.0, value2.into()), - (3.0, value3.into()), - (4.0, value4.into()) - ] - ); - - assert_eq!(s13, vec![(1.0, value1.into()), (4.0, value4.into())]); - assert_eq!(s23, vec![(3.0, value3.into()), (4.0, value4.into())]); - - assert_eq!(s14, vec![(1.0, value1.into()), (4.0, value4.into())]); - assert_eq!(s24, vec![(3.0, value3.into()), (4.0, value4.into())]); - } - - #[test] - #[tracing::instrument] - async fn add_to_sorted_set_updates_score(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - - let set1 = "set1"; - - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 1.0, value1) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 2.0, value2) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 3.0, value2) - .await - .unwrap(); - - let result = kvs - .get_sorted_set("test", "api", "entity", ns.clone(), set1) - .await - .unwrap(); - - assert_eq!(result, vec![(1.0, value1.into()), (3.0, value2.into())]); - } - - #[test] - #[tracing::instrument] - async fn query_sorted_set(deps: &WorkerExecutorTestDependencies) { - let test = $init(deps).await; - let kvs = test.get_key_value_storage(); - let ns = $ns(); - - let set1 = "set1"; - let set2 = "set2"; - - let value1 = "value1".as_bytes(); - let value2 = "value2".as_bytes(); - let value3 = "value3".as_bytes(); - let value4 = "value4".as_bytes(); - - let result1 = kvs - .query_sorted_set("test", "api", "entity", ns.clone(), set1, 0.0, 4.0) - .await - .unwrap(); - - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 1.0, value1) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 2.0, value2) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 3.0, value3) - .await - .unwrap(); - kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set2, 4.0, value4) - .await - .unwrap(); - - let result2 = kvs - .query_sorted_set("test", "api", "entity", ns.clone(), set1, 0.0, 4.0) - .await - .unwrap(); - let result3 = kvs - .query_sorted_set("test", "api", "entity", ns.clone(), set1, 1.0, 3.0) - .await - .unwrap(); - let result4 = kvs - .query_sorted_set("test", "api", "entity", ns.clone(), set1, 1.5, 3.2) - .await - .unwrap(); - let result5 = kvs - .query_sorted_set("test", "api", "entity", ns.clone(), set2, 4.0, 4.0) - .await - .unwrap(); - - assert_eq!(result1, Vec::<(f64, bytes::Bytes)>::new()); - assert_eq!( - result2, - vec![ - (1.0, value1.into()), - (2.0, value2.into()), - (3.0, value3.into()) - ] - ); - assert_eq!( - result3, - vec![ - (1.0, value1.into()), - (2.0, value2.into()), - (3.0, value3.into()) - ] - ); - assert_eq!(result4, vec![(2.0, value2.into()), (3.0, value3.into())]); - assert_eq!(result5, vec![(4.0, value4.into())]); - } - } - }; + assert_eq!(result1, Some(value1.into())); + assert_eq!(result2, Some(value2.into())); + assert_eq!(result3, None); + assert_eq!(result4, None); } -test_kv_storage!( - in_memory, - crate::key_value_storage::in_memory_storage, - crate::key_value_storage::ns, - crate::key_value_storage::ns2 -); -test_kv_storage!( - redis, - crate::key_value_storage::redis_storage, - crate::key_value_storage::ns, - crate::key_value_storage::ns2 -); -test_kv_storage!( - redis_hash, - crate::key_value_storage::redis_storage, - crate::key_value_storage::ns2, - crate::key_value_storage::ns -); -test_kv_storage!( - sqlite, - crate::key_value_storage::sqlite_storage, - crate::key_value_storage::ns2, - crate::key_value_storage::ns -); +#[test] +#[tracing::instrument] +async fn exists_set_exists( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + + let key = "key"; + let value = "value".as_bytes(); + + let result1 = kvs.exists("test", "api", ns.clone(), key).await.unwrap(); + kvs.set("test", "api", "entity", ns.clone(), key, value) + .await + .unwrap(); + let result2 = kvs.exists("test", "api", ns, key).await.unwrap(); + assert!(!result1); + assert!(result2); +} + +#[test] +#[tracing::instrument] +async fn exists_is_per_namespace( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + let ns2 = nss.ns2.clone(); + + let key = "key"; + let value = "value".as_bytes(); + + let result1 = kvs.exists("test", "api", ns.clone(), key).await.unwrap(); + kvs.set("test", "api", "entity", ns.clone(), key, value) + .await + .unwrap(); + let result2 = kvs.exists("test", "api", ns, key).await.unwrap(); + let result3 = kvs.exists("test", "api", ns2, key).await.unwrap(); + assert!(!result1); + assert!(result2); + assert!(!result3); +} + +#[test] +#[tracing::instrument] +async fn keys( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns2.clone(); + + let key1 = "key1"; + let key2 = "key2"; + + let keys1 = kvs.keys("test", "api", ns.clone()).await.unwrap(); + kvs.set( + "test", + "api", + "entity", + ns.clone(), + key1, + "value1".as_bytes(), + ) + .await + .unwrap(); + kvs.set( + "test", + "api", + "entity", + ns.clone(), + key2, + "value2".as_bytes(), + ) + .await + .unwrap(); + let keys2 = kvs.keys("test", "api", ns.clone()).await.unwrap(); + kvs.del("test", "api", ns.clone(), key1).await.unwrap(); + let keys3 = kvs.keys("test", "api", ns).await.unwrap(); + + tracing::debug!("keys2: {keys2:?}"); + + assert_eq!(keys1, Vec::::new()); + assert_eq!(keys2.len(), 2); + assert!(keys2.contains(&key1.to_string())); + assert!(keys2.contains(&key2.to_string())); + assert_eq!(keys3, vec![key2.to_string()]); +} + +#[test] +#[tracing::instrument] +async fn sets( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + + let set1 = "set1"; + let set2 = "set2"; + + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + let value3 = "value3".as_bytes(); + + let s11 = kvs + .members_of_set("test", "api", "entity", ns.clone(), set1) + .await + .unwrap(); + let s21 = kvs + .members_of_set("test", "api", "entity", ns.clone(), set2) + .await + .unwrap(); + + kvs.add_to_set("test", "api", "entity", ns.clone(), set1, value1) + .await + .unwrap(); + kvs.add_to_set("test", "api", "entity", ns.clone(), set1, value2) + .await + .unwrap(); + kvs.add_to_set("test", "api", "entity", ns.clone(), set1, value2) + .await + .unwrap(); + + kvs.add_to_set("test", "api", "entity", ns.clone(), set2, value3) + .await + .unwrap(); + kvs.add_to_set("test", "api", "entity", ns.clone(), set2, value3) + .await + .unwrap(); + kvs.add_to_set("test", "api", "entity", ns.clone(), set2, value2) + .await + .unwrap(); + + let s12 = kvs + .members_of_set("test", "api", "entity", ns.clone(), set1) + .await + .unwrap(); + let s22 = kvs + .members_of_set("test", "api", "entity", ns.clone(), set2) + .await + .unwrap(); + + kvs.remove_from_set("test", "api", "entity", ns.clone(), set1, value2) + .await + .unwrap(); + kvs.remove_from_set("test", "api", "entity", ns.clone(), set2, value2) + .await + .unwrap(); + + let s13 = kvs + .members_of_set("test", "api", "entity", ns.clone(), set1) + .await + .unwrap(); + let s23 = kvs + .members_of_set("test", "api", "entity", ns.clone(), set2) + .await + .unwrap(); + + kvs.remove_from_set("test", "api", "entity", ns.clone(), set1, value2) + .await + .unwrap(); // can remove non-existing value + kvs.remove_from_set("test", "api", "entity", ns.clone(), set2, value2) + .await + .unwrap(); // can remove non-existing value + + let s14 = kvs + .members_of_set("test", "api", "entity", ns.clone(), set1) + .await + .unwrap(); + let s24 = kvs + .members_of_set("test", "api", "entity", ns.clone(), set2) + .await + .unwrap(); + + assert2::check!(s11 == Vec::>::new()); + assert2::check!(s21 == Vec::>::new()); + assert2::check!(s12.len() == 2); + assert2::check!(s12.contains(&value1.to_vec().into())); + assert2::check!(s12.contains(&value2.to_vec().into())); + assert2::check!(s22.len() == 2); + assert2::check!(s22.contains(&value2.to_vec().into())); + assert2::check!(s22.contains(&value3.to_vec().into())); + assert2::check!(s13.len() == 1); + assert2::check!(s13.contains(&value1.to_vec().into())); + assert2::check!(s23.len() == 1); + assert2::check!(s23.contains(&value3.to_vec().into())); + assert2::check!(s14.len() == 1); + assert2::check!(s14.contains(&value1.to_vec().into())); + assert2::check!(s24.len() == 1); + assert2::check!(s24.contains(&value3.to_vec().into())); +} + +#[test] +#[tracing::instrument] +async fn sorted_sets( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + + let set1 = "set1"; + let set2 = "set2"; + + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + let value3 = "value3".as_bytes(); + let value4 = "value4".as_bytes(); + + let s11 = kvs + .get_sorted_set("test", "api", "entity", ns.clone(), set1) + .await + .unwrap(); + let s21 = kvs + .get_sorted_set("test", "api", "entity", ns.clone(), set2) + .await + .unwrap(); + + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 4.0, value4) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 1.0, value1) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 2.0, value2) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 2.0, value2) + .await + .unwrap(); + + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set2, 4.0, value4) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set2, 3.0, value3) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set2, 3.0, value3) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set2, 2.0, value2) + .await + .unwrap(); + + let s12 = kvs + .get_sorted_set("test", "api", "entity", ns.clone(), set1) + .await + .unwrap(); + let s22 = kvs + .get_sorted_set("test", "api", "entity", ns.clone(), set2) + .await + .unwrap(); + + kvs.remove_from_sorted_set("test", "api", "entity", ns.clone(), set1, value2) + .await + .unwrap(); + kvs.remove_from_sorted_set("test", "api", "entity", ns.clone(), set2, value2) + .await + .unwrap(); + + let s13 = kvs + .get_sorted_set("test", "api", "entity", ns.clone(), set1) + .await + .unwrap(); + let s23 = kvs + .get_sorted_set("test", "api", "entity", ns.clone(), set2) + .await + .unwrap(); + + kvs.remove_from_sorted_set("test", "api", "entity", ns.clone(), set1, value2) + .await + .unwrap(); // can remove non-existing value + kvs.remove_from_sorted_set("test", "api", "entity", ns.clone(), set2, value2) + .await + .unwrap(); // can remove non-existing value + + let s14 = kvs + .get_sorted_set("test", "api", "entity", ns.clone(), set1) + .await + .unwrap(); + let s24 = kvs + .get_sorted_set("test", "api", "entity", ns.clone(), set2) + .await + .unwrap(); + + assert_eq!(s11, Vec::<(f64, bytes::Bytes)>::new()); + assert_eq!(s21, Vec::<(f64, bytes::Bytes)>::new()); + + assert_eq!( + s12, + vec![ + (1.0, value1.into()), + (2.0, value2.into()), + (4.0, value4.into()) + ] + ); + assert_eq!( + s22, + vec![ + (2.0, value2.into()), + (3.0, value3.into()), + (4.0, value4.into()) + ] + ); + + assert_eq!(s13, vec![(1.0, value1.into()), (4.0, value4.into())]); + assert_eq!(s23, vec![(3.0, value3.into()), (4.0, value4.into())]); + + assert_eq!(s14, vec![(1.0, value1.into()), (4.0, value4.into())]); + assert_eq!(s24, vec![(3.0, value3.into()), (4.0, value4.into())]); +} + +#[test] +#[tracing::instrument] +async fn add_to_sorted_set_updates_score( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + + let set1 = "set1"; + + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 1.0, value1) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 2.0, value2) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 3.0, value2) + .await + .unwrap(); + + let result = kvs + .get_sorted_set("test", "api", "entity", ns.clone(), set1) + .await + .unwrap(); + + assert_eq!(result, vec![(1.0, value1.into()), (3.0, value2.into())]); +} + +#[test] +#[tracing::instrument] +async fn query_sorted_set( + _deps: &WorkerExecutorTestDependencies, + #[dimension(kvs)] kvs: &Arc, + #[dimension(nss)] nss: &Namespaces, +) { + let kvs = kvs.get_key_value_storage().await; + let ns = nss.ns.clone(); + + let set1 = "set1"; + let set2 = "set2"; + + let value1 = "value1".as_bytes(); + let value2 = "value2".as_bytes(); + let value3 = "value3".as_bytes(); + let value4 = "value4".as_bytes(); + + let result1 = kvs + .query_sorted_set("test", "api", "entity", ns.clone(), set1, 0.0, 4.0) + .await + .unwrap(); + + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 1.0, value1) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 2.0, value2) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set1, 3.0, value3) + .await + .unwrap(); + kvs.add_to_sorted_set("test", "api", "entity", ns.clone(), set2, 4.0, value4) + .await + .unwrap(); + + let result2 = kvs + .query_sorted_set("test", "api", "entity", ns.clone(), set1, 0.0, 4.0) + .await + .unwrap(); + let result3 = kvs + .query_sorted_set("test", "api", "entity", ns.clone(), set1, 1.0, 3.0) + .await + .unwrap(); + let result4 = kvs + .query_sorted_set("test", "api", "entity", ns.clone(), set1, 1.5, 3.2) + .await + .unwrap(); + let result5 = kvs + .query_sorted_set("test", "api", "entity", ns.clone(), set2, 4.0, 4.0) + .await + .unwrap(); + + assert_eq!(result1, Vec::<(f64, bytes::Bytes)>::new()); + assert_eq!( + result2, + vec![ + (1.0, value1.into()), + (2.0, value2.into()), + (3.0, value3.into()) + ] + ); + assert_eq!( + result3, + vec![ + (1.0, value1.into()), + (2.0, value2.into()), + (3.0, value3.into()) + ] + ); + assert_eq!(result4, vec![(2.0, value2.into()), (3.0, value3.into())]); + assert_eq!(result5, vec![(4.0, value4.into())]); +}