typedb_test/
lib.rs

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        // Download TypeDB using curl
103        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        // Extract TypeDB
122        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        // Give the server time to start
148        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        // binary path {extracted_dir}/typedb-all-{default_platform()}-{version}/typedb
172        // the version can be determined after extraction.
173        // it'll be the first directory in the extracted directory
174        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}