iceberg_test_utils/
lib.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! This crate contains common utilities for testing.
19//!
20//! It's not intended for use outside of `iceberg-rust`.
21
22#[cfg(feature = "tests")]
23pub use common::*;
24
25#[cfg(feature = "tests")]
26mod common {
27    use std::sync::Once;
28
29    use iceberg::{Catalog, NamespaceIdent};
30
31    static INIT: Once = Once::new();
32    pub fn set_up() {
33        INIT.call_once(tracing_subscriber::fmt::init);
34    }
35    pub fn normalize_test_name(s: impl ToString) -> String {
36        s.to_string().replace("::", "__").replace('.', "_")
37    }
38
39    // Environment variable names for service endpoints
40    pub const ENV_MINIO_ENDPOINT: &str = "ICEBERG_TEST_MINIO_ENDPOINT";
41    pub const ENV_REST_CATALOG_ENDPOINT: &str = "ICEBERG_TEST_REST_ENDPOINT";
42    pub const ENV_HMS_ENDPOINT: &str = "ICEBERG_TEST_HMS_ENDPOINT";
43    pub const ENV_GLUE_ENDPOINT: &str = "ICEBERG_TEST_GLUE_ENDPOINT";
44    pub const ENV_GCS_ENDPOINT: &str = "ICEBERG_TEST_GCS_ENDPOINT";
45
46    // Default ports matching dev/docker-compose.yaml
47    pub const DEFAULT_MINIO_PORT: u16 = 9000;
48    pub const DEFAULT_REST_CATALOG_PORT: u16 = 8181;
49    pub const DEFAULT_HMS_PORT: u16 = 9083;
50    pub const DEFAULT_GLUE_PORT: u16 = 5001;
51    pub const DEFAULT_GCS_PORT: u16 = 4443;
52
53    /// Returns the MinIO S3-compatible endpoint.
54    /// Checks ICEBERG_TEST_MINIO_ENDPOINT env var, otherwise returns localhost default.
55    pub fn get_minio_endpoint() -> String {
56        std::env::var(ENV_MINIO_ENDPOINT)
57            .unwrap_or_else(|_| format!("http://localhost:{DEFAULT_MINIO_PORT}"))
58    }
59
60    /// Returns the REST catalog endpoint.
61    /// Checks ICEBERG_TEST_REST_ENDPOINT env var, otherwise returns localhost default.
62    pub fn get_rest_catalog_endpoint() -> String {
63        std::env::var(ENV_REST_CATALOG_ENDPOINT)
64            .unwrap_or_else(|_| format!("http://localhost:{DEFAULT_REST_CATALOG_PORT}"))
65    }
66
67    /// Returns the HMS (Hive Metastore) endpoint.
68    /// Checks ICEBERG_TEST_HMS_ENDPOINT env var, otherwise returns localhost default.
69    pub fn get_hms_endpoint() -> String {
70        std::env::var(ENV_HMS_ENDPOINT).unwrap_or_else(|_| format!("localhost:{DEFAULT_HMS_PORT}"))
71    }
72
73    /// Returns the Glue (Moto mock) endpoint.
74    /// Checks ICEBERG_TEST_GLUE_ENDPOINT env var, otherwise returns localhost default.
75    pub fn get_glue_endpoint() -> String {
76        std::env::var(ENV_GLUE_ENDPOINT)
77            .unwrap_or_else(|_| format!("http://localhost:{DEFAULT_GLUE_PORT}"))
78    }
79
80    /// Returns the GCS (fake-gcs-server) endpoint.
81    /// Checks ICEBERG_TEST_GCS_ENDPOINT env var, otherwise returns localhost default.
82    pub fn get_gcs_endpoint() -> String {
83        std::env::var(ENV_GCS_ENDPOINT)
84            .unwrap_or_else(|_| format!("http://localhost:{DEFAULT_GCS_PORT}"))
85    }
86
87    /// Helper to clean up a namespace and its tables before a test runs.
88    /// This handles the case where previous test runs left data in the persistent database.
89    pub async fn cleanup_namespace<C: Catalog>(catalog: &C, ns: &NamespaceIdent) {
90        // Try to drop all tables in the namespace first
91        if let Ok(tables) = catalog.list_tables(ns).await {
92            for table in tables {
93                let _ = catalog.drop_table(&table).await;
94            }
95        }
96        // Then try to drop the namespace itself
97        let _ = catalog.drop_namespace(ns).await;
98    }
99
100    /// Macro to generate a normalized test name with module path prefix.
101    /// Takes one or more string parts and joins them with the module path.
102    ///
103    /// Example:
104    /// ```ignore
105    /// // Returns something like "rest_catalog_test__test_create_table"
106    /// let name = normalize_test_name_with_parts!("test_create_table");
107    ///
108    /// // Returns something like "rest_catalog_test__apple__ios"
109    /// let name = normalize_test_name_with_parts!("apple", "ios");
110    /// ```
111    #[macro_export]
112    macro_rules! normalize_test_name_with_parts {
113        ($($part:expr),+) => {
114            $crate::normalize_test_name([module_path!(), $($part),+].join("_"))
115        };
116    }
117}