From 20dc5fb05929f336c64c8aeadaf0f172fd28a1ad Mon Sep 17 00:00:00 2001 From: Rim Rakhimov Date: Mon, 2 Dec 2024 22:01:07 +0400 Subject: [PATCH 1/7] feat(launcher): add test database initialization macros --- .../src/test_database.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/libs/blockscout-service-launcher/src/test_database.rs b/libs/blockscout-service-launcher/src/test_database.rs index 110bff4cb..c8c0f3f2c 100644 --- a/libs/blockscout-service-launcher/src/test_database.rs +++ b/libs/blockscout-service-launcher/src/test_database.rs @@ -100,3 +100,31 @@ impl Deref for TestDbGuard { &self.conn_with_db } } + +#[macro_export] +macro_rules! database_name { + () => { + format!("{}_{}_{}", file!(), line!(), column!()) + }; + ($custom_suffix:expr) => { + format!("{}_{}_{}_{}", file!(), line!(), column!(), $custom_suffix) + }; +} +pub use database_name; + +#[macro_export] +macro_rules! database { + ($migration_crate:ident) => {{ + $crate::test_database::TestDbGuard::new::<$migration_crate::Migrator>( + &$crate::test_database::database_name!(), + ) + .await + }}; + ($migration_crate:ident, $custom_suffix:expr) => {{ + $crate::test_database::TestDbGuard::new::<$migration_crate::Migrator>( + $crate::test_database::database_name!($custom_suffix), + ) + .await + }}; +} +pub use database; From d7d794fb3dd9fe73a6e723f3748d9ce79fc80264 Mon Sep 17 00:00:00 2001 From: Rim Rakhimov Date: Mon, 2 Dec 2024 22:01:34 +0400 Subject: [PATCH 2/7] fix(launcher): add missing serde dependency to 'database' feature --- libs/blockscout-service-launcher/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/blockscout-service-launcher/Cargo.toml b/libs/blockscout-service-launcher/Cargo.toml index 4faca6604..90d1438a3 100644 --- a/libs/blockscout-service-launcher/Cargo.toml +++ b/libs/blockscout-service-launcher/Cargo.toml @@ -89,6 +89,7 @@ tracing = [ database = [ "dep:anyhow", "dep:cfg-if", + "dep:serde", "dep:tracing", "dep:url", ] From 2cc51400115df05bbacc29e207c9b5703afc4e81 Mon Sep 17 00:00:00 2001 From: Rim Rakhimov Date: Wed, 4 Dec 2024 14:43:38 +0400 Subject: [PATCH 3/7] doc(launcher): add doc comments to database_name and database macros --- .../src/test_database.rs | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/libs/blockscout-service-launcher/src/test_database.rs b/libs/blockscout-service-launcher/src/test_database.rs index c8c0f3f2c..08cb0b3e0 100644 --- a/libs/blockscout-service-launcher/src/test_database.rs +++ b/libs/blockscout-service-launcher/src/test_database.rs @@ -101,17 +101,62 @@ impl Deref for TestDbGuard { } } +/// Generates a unique database name for use in tests. +/// +/// This macro creates a database name based on the file name, line number, and column number +/// of the macro invocation. Optionally, a custom prefix can be appended for added specificity, +/// which is useful in scenarios like parameterized tests. +/// +/// For more details on usage and examples, see the [`database!`](macro.database.html) macro. +/// +/// # Arguments +/// +/// - `custom_prefix` (optional): A custom string to append to the database name. #[macro_export] macro_rules! database_name { () => { format!("{}_{}_{}", file!(), line!(), column!()) }; - ($custom_suffix:expr) => { - format!("{}_{}_{}_{}", file!(), line!(), column!(), $custom_suffix) + ($custom_prefix:expr) => { + format!("{}_{}_{}_{}", $custom_prefix, file!(), line!(), column!()) }; } pub use database_name; +/// Initializes a test database for use in tests. +/// +/// This macro simplifies setting up a database by automatically generating a database name +/// based on the location where the function is defined. It eliminates the need to manually +/// specify the test case name for the database name. +/// +/// # Usage +/// +/// The macro can be used within a test as follows: +/// ```text +/// use blockscout_service_launcher::test_database::database; +/// +/// #[tokio::test] +/// async fn test() { +/// let db_guard = database!(migration_crate); +/// // Perform operations with the database... +/// } +/// ``` +/// +/// The `migration_crate` parameter refers to the migration crate associated with the database. +/// +/// # Parameterized Tests +/// +/// **Note:** When using this macro with [`rstest` parameterized test cases](https://docs.rs/rstest/latest/rstest/attr.rstest.html#test-parametrized-cases), +/// the same database name will be used for all test cases. To avoid conflicts, you need to provide +/// a meaningful prefix explicitly, as demonstrated below: +/// +/// ```text +/// #[tokio::test] +/// async fn test_with_prefix() { +/// let db_guard = database!(migration_crate, "custom_prefix"); +/// // Perform operations with the database... +/// } +/// ``` #[macro_export] macro_rules! database { ($migration_crate:ident) => {{ @@ -120,9 +165,9 @@ macro_rules! database { ) .await }}; - ($migration_crate:ident, $custom_suffix:expr) => {{ + ($migration_crate:ident, $custom_prefix:expr) => {{ $crate::test_database::TestDbGuard::new::<$migration_crate::Migrator>( - $crate::test_database::database_name!($custom_suffix), + $crate::test_database::database_name!($custom_prefix), ) .await }}; From d0868930b351101bcbf4f90d981b2586a1d9f50b Mon Sep 17 00:00:00 2001 From: Rim Rakhimov Date: Wed, 4 Dec 2024 16:15:34 +0400 Subject: [PATCH 4/7] feat(launcher): update database name initialization to use original names as prefixes --- .../src/test_database.rs | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/libs/blockscout-service-launcher/src/test_database.rs b/libs/blockscout-service-launcher/src/test_database.rs index 08cb0b3e0..c757d4446 100644 --- a/libs/blockscout-service-launcher/src/test_database.rs +++ b/libs/blockscout-service-launcher/src/test_database.rs @@ -3,6 +3,14 @@ use crate::database::{ }; use std::{ops::Deref, sync::Arc}; +/// Postgres supports maximum 63 symbols. +/// All exceeding symbols are truncated by the database. +const MAX_DATABASE_NAME_LEN: usize = 63; + +/// A length of the hex encoded hash of database name +/// when the original exceeds [`MAX_DATABASE_NAME_LEN`] +const HASH_SUFFIX_STRING_LEN: usize = 8; + #[derive(Clone, Debug)] pub struct TestDbGuard { conn_with_db: Arc, @@ -20,9 +28,7 @@ impl TestDbGuard { let conn_without_db = Database::connect(&base_db_url) .await .expect("Connection to postgres (without database) failed"); - // We use a hash, as the name itself may be quite long and be trimmed. - // Postgres DB name should be 63 symbols max. - let db_name = format!("_{:x}", keccak_hash::keccak(db_name))[..63].to_string(); + let db_name = Self::preprocess_database_name(db_name); let mut guard = TestDbGuard { conn_with_db: Arc::new(DatabaseConnection::Disconnected), conn_without_db: Arc::new(conn_without_db), @@ -35,6 +41,35 @@ impl TestDbGuard { guard } + /// Creates a new test database helper with a unique name. + /// + /// This function initializes a test database, where the database name is constructed + /// as a concatenation of the provided `prefix_name`, `file`, `line`, and `column` arguments. + /// It ensures that the generated database name is unique to the location in the code + /// where this function is called. + /// + /// # Arguments + /// + /// - `prefix_name`: A custom prefix for the database name. + /// - `file`: The file name where this function is invoked. Must be the result of the `file!` macro. + /// - `line`: The line number where this function is invoked. Must be the result of the `line!` macro. + /// - `column`: The column number where this function is invoked. Must be the result of the `column!` macro. + /// + /// # Example + /// + /// ```text + /// let db_guard = TestDbGuard::new_with_metadata::("test_db", file!(), line!(), column!()).await; + /// ``` + pub async fn new_with_metadata( + prefix_name: &str, + file: &str, + line: u32, + column: u32, + ) -> Self { + let db_name = format!("{prefix_name}_{file}_{line}_{column}"); + Self::new::(db_name.as_str()).await + } + pub fn client(&self) -> Arc { self.conn_with_db.clone() } @@ -92,6 +127,21 @@ impl TestDbGuard { .await .expect("Database migration failed"); } + + /// Strips given database name if the one is too long to be supported. + /// To differentiate the resultant name with other similar prefixes, + /// a 4-bytes hash of the original name is added at the end. + fn preprocess_database_name(name: &str) -> String { + if name.len() <= MAX_DATABASE_NAME_LEN { + return name.to_string(); + } + + let hash = &format!("{:x}", keccak_hash::keccak(name))[..HASH_SUFFIX_STRING_LEN]; + format!( + "{}-{hash}", + &name[..MAX_DATABASE_NAME_LEN - HASH_SUFFIX_STRING_LEN - 1] + ) + } } impl Deref for TestDbGuard { From 3f8a7919690404e52f3a9b82a9cb036d990e9a17 Mon Sep 17 00:00:00 2001 From: Rim Rakhimov Date: Wed, 4 Dec 2024 16:20:46 +0400 Subject: [PATCH 5/7] chore(launcher): bump to v0.15.0 --- libs/blockscout-service-launcher/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/blockscout-service-launcher/Cargo.toml b/libs/blockscout-service-launcher/Cargo.toml index 90d1438a3..578b5d83f 100644 --- a/libs/blockscout-service-launcher/Cargo.toml +++ b/libs/blockscout-service-launcher/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blockscout-service-launcher" -version = "0.14.0" +version = "0.15.0" description = "Allows to launch blazingly fast blockscout rust services" license = "MIT" repository = "https://github.com/blockscout/blockscout-rs" From d4a03735ae2c8f7d943d9bea582d98253672c770 Mon Sep 17 00:00:00 2001 From: Rim Rakhimov Date: Wed, 4 Dec 2024 16:33:45 +0400 Subject: [PATCH 6/7] fix(launcher): wrap database name in quotes to allow non-alpha symbols in the database names --- libs/blockscout-service-launcher/src/test_database.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/blockscout-service-launcher/src/test_database.rs b/libs/blockscout-service-launcher/src/test_database.rs index c757d4446..420fa133c 100644 --- a/libs/blockscout-service-launcher/src/test_database.rs +++ b/libs/blockscout-service-launcher/src/test_database.rs @@ -106,7 +106,7 @@ impl TestDbGuard { tracing::info!(name = db_name, "creating database"); db.execute(Statement::from_string( db.get_database_backend(), - format!("CREATE DATABASE {db_name}"), + format!("CREATE DATABASE \"{db_name}\""), )) .await?; Ok(()) @@ -116,7 +116,7 @@ impl TestDbGuard { tracing::info!(name = db_name, "dropping database"); db.execute(Statement::from_string( db.get_database_backend(), - format!("DROP DATABASE IF EXISTS {db_name} WITH (FORCE)"), + format!("DROP DATABASE IF EXISTS \"{db_name}\" WITH (FORCE)"), )) .await?; Ok(()) From 4aa2877a896a8e01c6637508bb7d7a0f849edff6 Mon Sep 17 00:00:00 2001 From: Rim Rakhimov Date: Wed, 4 Dec 2024 16:57:34 +0400 Subject: [PATCH 7/7] chore(metrics-tools): fix cargo clippy --- libs/metrics-tools/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/metrics-tools/src/lib.rs b/libs/metrics-tools/src/lib.rs index f7712f49f..43f5619e2 100644 --- a/libs/metrics-tools/src/lib.rs +++ b/libs/metrics-tools/src/lib.rs @@ -50,7 +50,7 @@ pub struct Interval<'a> { discarded: bool, } -impl<'a> Interval<'a> { +impl Interval<'_> { /// Get current time of the interval without recording. pub fn elapsed_from_start(&self) -> Duration { self.start_time.elapsed() @@ -62,7 +62,7 @@ impl<'a> Interval<'a> { } } -impl<'a> Drop for Interval<'a> { +impl Drop for Interval<'_> { fn drop(&mut self) { if !self.discarded { self.recorder.add_time(self.elapsed_from_start())