1use std::collections::HashMap;
19use std::env;
20use std::sync::LazyLock;
21
22use opendal_core::Operator;
23use opendal_core::Result;
24use opendal_core::layers::CapabilityOverrideLayer;
25use opendal_layer_logging::LoggingLayer;
26use opendal_layer_retry::RetryLayer;
27use opendal_layer_timeout::TimeoutLayer;
28use sha2::Digest;
29use sha2::Sha256;
30
31const OPENDAL_TEST_CAPABILITY_OVERRIDES: &str = "OPENDAL_TEST_CAPABILITY_OVERRIDES";
32const OPENDAL_TEST_UNSET_VALUE: &str = "__OPENDAL_TEST_UNSET__";
33
34pub(crate) fn sha256_digest(data: impl AsRef<[u8]>) -> String {
35 use std::fmt::Write;
36
37 let digest = Sha256::digest(data);
38 let mut output = String::with_capacity(digest.len() * 2);
39 for byte in digest {
40 write!(&mut output, "{byte:02x}").expect("writing to String must succeed");
41 }
42 output
43}
44
45pub static TEST_RUNTIME: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
47 tokio::runtime::Builder::new_multi_thread()
48 .enable_all()
49 .build()
50 .unwrap()
51});
52
53fn collect_config(
54 prefix: &str,
55 vars: impl IntoIterator<Item = (String, String)>,
56) -> HashMap<String, String> {
57 vars.into_iter()
58 .filter_map(|(k, v)| {
59 if v == OPENDAL_TEST_UNSET_VALUE {
60 return None;
61 }
62 k.to_lowercase()
63 .strip_prefix(prefix)
64 .map(|k| (k.to_string(), v))
65 })
66 .collect()
67}
68
69pub fn init_test_service() -> Result<Option<Operator>> {
75 let _ = dotenvy::dotenv();
76
77 let scheme = if let Ok(v) = env::var("OPENDAL_TEST") {
78 v
79 } else {
80 return Ok(None);
81 };
82
83 let prefix = {
84 let scheme_key = scheme.replace('-', "_");
85 format!("opendal_{scheme_key}_")
86 };
87
88 let mut cfg = collect_config(&prefix, env::vars());
89
90 let disable_random_root = env::var("OPENDAL_DISABLE_RANDOM_ROOT").unwrap_or_default() == "true";
92 if !disable_random_root {
93 let root = format!(
94 "{}{}/",
95 cfg.get("root").cloned().unwrap_or_else(|| "/".to_string()),
96 uuid::Uuid::new_v4()
97 );
98 cfg.insert("root".to_string(), root);
99 }
100
101 let scheme = scheme.replace('_', "-");
103 let mut op = Operator::via_iter(scheme, cfg).expect("must succeed");
104
105 if let Ok(overrides) = env::var(OPENDAL_TEST_CAPABILITY_OVERRIDES)
106 && overrides != OPENDAL_TEST_UNSET_VALUE
107 {
108 op = op.layer(CapabilityOverrideLayer::from_overrides(&overrides)?);
109 }
110
111 let op = op
112 .layer(LoggingLayer::default())
113 .layer(TimeoutLayer::new())
114 .layer(RetryLayer::new().with_max_times(4));
115
116 Ok(Some(op))
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn test_collect_config_skips_unset_values() {
125 let cfg = collect_config(
126 "opendal_s3_",
127 [
128 (
129 "OPENDAL_S3_ENDPOINT".to_string(),
130 "http://localhost".to_string(),
131 ),
132 (
133 "OPENDAL_S3_ALLOW_ANONYMOUS".to_string(),
134 OPENDAL_TEST_UNSET_VALUE.to_string(),
135 ),
136 ("OPENDAL_S3_PASSWORD".to_string(), String::new()),
137 ("OPENDAL_GCS_BUCKET".to_string(), "test".to_string()),
138 ],
139 );
140
141 assert_eq!(
142 cfg.get("endpoint").map(String::as_str),
143 Some("http://localhost")
144 );
145 assert_eq!(cfg.get("password").map(String::as_str), Some(""));
146 assert!(!cfg.contains_key("allow_anonymous"));
147 assert!(!cfg.contains_key("bucket"));
148 }
149}