1#![doc = include_str!("../README.md")]
2#[allow(unused_imports)]
3#[macro_use]
4pub extern crate googletest;
5
6#[cfg(test)]
7mod tests;
8
9pub use googletest::gtest;
10use {
11 googletest::prelude::{Result, StaticFixture},
12 port_check::free_local_port_in_range,
13 std::{
14 fmt::Display,
15 ops::Deref,
16 panic::RefUnwindSafe,
17 path::{Path, PathBuf},
18 process::{Child, Command},
19 sync::{Arc, Mutex},
20 time::Duration,
21 },
22 typedb_driver::{Credentials, DriverOptions, TypeDBDriver},
23};
24
25const DEFAULT_DATABASE_NAME: &str = "test";
26
27#[derive(Debug, Clone)]
28pub struct TypeDBFixture {
29 driver: Arc<typedb_driver::TypeDBDriver>,
30 child: Arc<Mutex<Child>>,
31}
32
33impl Drop for TypeDBFixture {
34 fn drop(&mut self) {
35 self.child.lock().unwrap().kill().unwrap();
36 }
37}
38
39impl RefUnwindSafe for TypeDBFixture {}
40
41#[derive(Debug, Clone, Default)]
42enum OS {
43 #[cfg_attr(any(target_os = "linux"), default)]
44 #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
45 Linux,
46 #[cfg_attr(any(target_os = "macos"), default)]
47 #[cfg_attr(not(target_os = "macos"), allow(dead_code))]
48 Mac,
49 #[cfg_attr(any(target_os = "windows"), default)]
50 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
51 Windows,
52 #[cfg_attr(
53 not(any(target_os = "linux", target_os = "macos", target_os = "windows")),
54 default
55 )]
56 #[allow(dead_code)]
57 Unknown,
58}
59
60#[derive(Debug, Clone, Default)]
61enum Architecture {
62 #[cfg_attr(any(target_arch = "x86_64"), default)]
63 #[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
64 X86_64,
65 #[cfg_attr(any(target_arch = "aarch64"), default)]
66 #[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
67 Aarch64,
68 #[cfg_attr(not(any(target_arch = "x86_64", target_arch = "aarch64")), default)]
69 #[allow(dead_code)]
70 Unknown,
71}
72
73impl Display for OS {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 OS::Linux => write!(f, "linux"),
77 OS::Mac => write!(f, "mac"),
78 OS::Windows => write!(f, "windows"),
79 OS::Unknown => unimplemented!(),
80 }
81 }
82}
83
84impl Display for Architecture {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 match self {
87 Architecture::X86_64 => write!(f, "x86_64"),
88 Architecture::Aarch64 => write!(f, "arm64"),
89 Architecture::Unknown => unimplemented!(),
90 }
91 }
92}
93
94#[allow(dead_code)]
95fn default_platform() -> String {
96 format!("{}-{}", OS::default(), Architecture::default())
97}
98
99impl TypeDBFixture {
100 #[allow(dead_code)]
101 fn download_and_extract_typedb(archive_path: &Path, extract_dir: &Path) -> Result<()> {
102 let output = Command::new("curl")
104 .args([
105 "-L",
106 &format!(
107 "https://repo.typedb.com/public/public-release/raw/names/typedb-all-{}/versions/latest/download",
108 default_platform()
109 ),
110 "-o",
111 archive_path.to_str().unwrap(),
112 ])
113 .output()?;
114
115 if !output.status.success() {
116 panic!("Failed to extract TypeDB: {}", unsafe {
117 String::from_utf8_unchecked(output.stderr)
118 });
119 }
120
121 let output = Command::new("tar")
123 .args([
124 "-xzf",
125 archive_path.to_str().unwrap(),
126 "-C",
127 extract_dir.to_str().unwrap(),
128 ])
129 .output()?;
130
131 if !output.status.success() {
132 panic!("Failed to extract TypeDB: {}", unsafe {
133 String::from_utf8_unchecked(output.stderr)
134 });
135 }
136
137 Ok(())
138 }
139
140 async fn start_typedb_server(typedb_dir: &Path, port: u16) -> Result<Child> {
141 let typedb_binary = typedb_dir.join("typedb");
142
143 let child = Command::new(&typedb_binary)
144 .args(["server", "--server.address", &format!("localhost:{}", port)])
145 .spawn()?;
146
147 tokio::time::sleep(Duration::from_secs(5)).await;
149
150 Ok(child)
151 }
152}
153
154impl StaticFixture for TypeDBFixture {
155 fn set_up_once() -> googletest::Result<Self> {
156 let temp_dir = tempdir::TempDir::new("typedb_test")?;
157 let typedb_dir = temp_dir.into_path().to_path_buf();
158 let archive_path: PathBuf = typedb_dir.join("typedb.tar.gz");
159 let extracted_dir: PathBuf = typedb_dir;
160
161 Self::download_and_extract_typedb(&archive_path, &extracted_dir).unwrap();
162 let free_port_in_range = free_local_port_in_range(10000..=15000).unwrap();
163
164 let address = format!("localhost:{}", free_port_in_range);
165 let credentials = Credentials::new("admin", "password");
166 let options = DriverOptions::new(false, None).unwrap();
167
168 let mut driver: Option<TypeDBDriver> = None;
169 let mut child: Option<Child> = None;
170
171 let typedb_binary_path = extracted_dir.read_dir().unwrap();
175 let mut dir = None;
176 for entry in typedb_binary_path.flatten() {
177 if entry.file_type().unwrap().is_dir() {
178 dir = Some(entry.path());
179 break;
180 }
181 }
182 if dir.is_none() {
183 panic!("No directory found in extracted directory");
184 }
185 let dir = dir.unwrap();
186 let rt = tokio::runtime::Runtime::new().unwrap();
187 rt.block_on(async {
188 let server_child = Self::start_typedb_server(&dir, free_port_in_range)
189 .await
190 .unwrap();
191 child = Some(server_child);
192 let _driver = TypeDBDriver::new(address, credentials, options)
193 .await
194 .unwrap();
195 _driver
196 .databases()
197 .create(DEFAULT_DATABASE_NAME)
198 .await
199 .unwrap();
200 driver = Some(_driver);
201 });
202 Ok(TypeDBFixture {
203 driver: driver.take().unwrap().into(),
204 child: Arc::new(Mutex::new(child.take().unwrap())),
205 })
206 }
207}
208
209impl Deref for TypeDBFixture {
210 type Target = TypeDBDriver;
211
212 fn deref(&self) -> &Self::Target {
213 &self.driver
214 }
215}