Skip to main content

tests_fuzz/
utils.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15pub mod cluster_info;
16pub mod config;
17pub mod crd;
18/// CSV dump writer utilities for fuzz tests.
19pub mod csv_dump_writer;
20pub mod health;
21pub mod migration;
22pub mod network_chaos;
23pub mod partition;
24pub mod pod_failure;
25pub mod procedure;
26#[cfg(feature = "unstable")]
27pub mod process;
28pub mod retry;
29/// SQL dump writer utilities for fuzz tests.
30pub mod sql_dump_writer;
31pub mod wait;
32
33use std::env;
34use std::str::FromStr;
35
36use common_base::readable_size::ReadableSize;
37use common_telemetry::info;
38use common_telemetry::tracing::log::LevelFilter;
39use paste::paste;
40use snafu::ResultExt;
41use sqlx::mysql::{MySqlConnectOptions, MySqlPoolOptions};
42use sqlx::{ConnectOptions, MySql, Pool};
43
44use crate::error::{self, Result};
45use crate::ir::Ident;
46
47/// Database connections
48pub struct Connections {
49    pub mysql: Option<Pool<MySql>>,
50}
51
52const GT_MYSQL_ADDR: &str = "GT_MYSQL_ADDR";
53
54/// Connects to GreptimeDB via env variables.
55pub async fn init_greptime_connections_via_env() -> Connections {
56    let _ = dotenv::dotenv();
57    let mysql = if let Ok(addr) = env::var(GT_MYSQL_ADDR) {
58        Some(addr)
59    } else {
60        info!("GT_MYSQL_ADDR is empty, ignores test");
61        None
62    };
63
64    init_greptime_connections(mysql).await
65}
66
67/// Connects to GreptimeDB.
68pub async fn init_greptime_connections(mysql: Option<String>) -> Connections {
69    let mysql = if let Some(addr) = mysql {
70        let opts = format!("mysql://{addr}/public")
71            .parse::<MySqlConnectOptions>()
72            .unwrap()
73            .log_statements(LevelFilter::Off);
74
75        Some(MySqlPoolOptions::new().connect_with(opts).await.unwrap())
76    } else {
77        None
78    };
79
80    Connections { mysql }
81}
82
83const GT_FUZZ_BINARY_PATH: &str = "GT_FUZZ_BINARY_PATH";
84const GT_FUZZ_INSTANCE_ROOT_DIR: &str = "GT_FUZZ_INSTANCE_ROOT_DIR";
85
86/// The variables for unstable test
87pub struct UnstableTestVariables {
88    pub binary_path: String,
89    pub root_dir: Option<String>,
90}
91
92/// Loads env variables for unstable test
93pub fn load_unstable_test_env_variables() -> UnstableTestVariables {
94    let _ = dotenv::dotenv();
95    let binary_path = env::var(GT_FUZZ_BINARY_PATH).expect("GT_FUZZ_BINARY_PATH not found");
96    let root_dir = env::var(GT_FUZZ_INSTANCE_ROOT_DIR).ok();
97
98    UnstableTestVariables {
99        binary_path,
100        root_dir,
101    }
102}
103
104pub const GT_FUZZ_CLUSTER_NAMESPACE: &str = "GT_FUZZ_CLUSTER_NAMESPACE";
105pub const GT_FUZZ_CLUSTER_NAME: &str = "GT_FUZZ_CLUSTER_NAME";
106
107/// Flushes memtable to SST file.
108pub async fn flush_memtable(e: &Pool<MySql>, table_name: &Ident) -> Result<()> {
109    let sql = format!("admin flush_table(\"{}\")", table_name);
110    let result = sqlx::query(&sql)
111        .execute(e)
112        .await
113        .context(error::ExecuteQuerySnafu { sql })?;
114    info!("Flush table: {}\n\nResult: {result:?}\n\n", table_name);
115
116    Ok(())
117}
118
119/// Triggers a compaction for table
120pub async fn compact_table(e: &Pool<MySql>, table_name: &Ident) -> Result<()> {
121    let sql = format!("admin compact_table(\"{}\")", table_name);
122    let result = sqlx::query(&sql)
123        .execute(e)
124        .await
125        .context(error::ExecuteQuerySnafu { sql })?;
126    info!("Compact table: {}\n\nResult: {result:?}\n\n", table_name);
127
128    Ok(())
129}
130
131pub const GT_FUZZ_INPUT_MAX_ROWS: &str = "GT_FUZZ_INPUT_MAX_ROWS";
132pub const GT_FUZZ_INPUT_MAX_TABLES: &str = "GT_FUZZ_INPUT_MAX_TABLES";
133pub const GT_FUZZ_INPUT_MAX_COLUMNS: &str = "GT_FUZZ_INPUT_MAX_COLUMNS";
134pub const GT_FUZZ_INPUT_MAX_ALTER_ACTIONS: &str = "GT_FUZZ_INPUT_MAX_ALTER_ACTIONS";
135pub const GT_FUZZ_INPUT_MAX_INSERT_ACTIONS: &str = "GT_FUZZ_INPUT_MAX_INSERT_ACTIONS";
136pub const FUZZ_OVERRIDE_PREFIX: &str = "GT_FUZZ_OVERRIDE_";
137/// Enables CSV dump generation for fuzz runs.
138pub const GT_FUZZ_DUMP_TABLE_CSV: &str = "GT_FUZZ_DUMP_TABLE_CSV";
139/// Base directory for CSV dump sessions.
140pub const GT_FUZZ_DUMP_DIR: &str = "GT_FUZZ_DUMP_DIR";
141/// Directory suffix used by one CSV dump session.
142pub const GT_FUZZ_DUMP_SUFFIX: &str = "GT_FUZZ_DUMP_SUFFIX";
143/// Max in-memory CSV buffer size before auto flush.
144pub const GT_FUZZ_DUMP_BUFFER_MAX_BYTES: &str = "GT_FUZZ_DUMP_BUFFER_MAX_BYTES";
145
146/// Reads an override value for a fuzz parameter from env `GT_FUZZ_OVERRIDE_<NAME>`.
147pub fn get_fuzz_override<T>(name: &str) -> Option<T>
148where
149    T: std::str::FromStr,
150{
151    let _ = dotenv::dotenv();
152    let key = format!("{}{}", FUZZ_OVERRIDE_PREFIX, name.to_ascii_uppercase());
153    env::var(&key).ok().and_then(|v| v.parse().ok())
154}
155
156/// Returns CSV dump base directory.
157pub fn get_gt_fuzz_dump_dir() -> String {
158    let _ = dotenv::dotenv();
159    env::var(GT_FUZZ_DUMP_DIR).unwrap_or_else(|_| "/tmp/greptime-fuzz-dumps".to_string())
160}
161
162/// Returns CSV dump directory suffix.
163pub fn get_gt_fuzz_dump_suffix() -> String {
164    let _ = dotenv::dotenv();
165    env::var(GT_FUZZ_DUMP_SUFFIX).unwrap_or_else(|_| ".repartition-metric-csv".to_string())
166}
167
168/// Returns max CSV in-memory buffer size.
169pub fn get_gt_fuzz_dump_buffer_max_bytes() -> usize {
170    let _ = dotenv::dotenv();
171    env::var(GT_FUZZ_DUMP_BUFFER_MAX_BYTES)
172        .ok()
173        .and_then(|value| {
174            value.parse::<usize>().ok().or_else(|| {
175                ReadableSize::from_str(&value)
176                    .ok()
177                    .map(|size| size.as_bytes() as usize)
178            })
179        })
180        .unwrap_or(8 * 1024 * 1024)
181}
182
183macro_rules! make_get_from_env_helper {
184    ($key:expr, $default: expr) => {
185        paste! {
186            #[doc = "Retrieves `" $key "` environment variable \
187                     or returns a default value (`" $default "`) if the environment variable is not set.
188            "]
189            pub fn [<get_ $key:lower>]() -> usize {
190                get_from_env_or_default_value($key, $default)
191            }
192        }
193    };
194}
195
196make_get_from_env_helper!(GT_FUZZ_INPUT_MAX_ALTER_ACTIONS, 256);
197make_get_from_env_helper!(GT_FUZZ_INPUT_MAX_INSERT_ACTIONS, 4);
198make_get_from_env_helper!(GT_FUZZ_INPUT_MAX_ROWS, 512);
199make_get_from_env_helper!(GT_FUZZ_INPUT_MAX_TABLES, 32);
200make_get_from_env_helper!(GT_FUZZ_INPUT_MAX_COLUMNS, 16);
201
202/// Retrieves a value from the environment variables
203/// or returns a default value if the environment variable is not set.
204fn get_from_env_or_default_value(key: &str, default_value: usize) -> usize {
205    let _ = dotenv::dotenv();
206    if let Ok(value) = env::var(key) {
207        value.parse().unwrap()
208    } else {
209        default_value
210    }
211}