1use crate::Backend;
2use cargo_metadata::camino::Utf8PathBuf;
3use cargo_metadata::{Metadata, MetadataCommand};
4use std::process::Command;
5use std::process::Stdio;
6
7#[derive(clap::Args, Debug)]
8pub(crate) struct TestArgs {
9 #[clap(default_value_t = Backend::All)]
11 backend: Backend,
12 #[clap(long = "no-integration-tests")]
14 no_integration_tests: bool,
15 #[clap(long = "no-doc-tests")]
17 no_doc_tests: bool,
18 #[clap(long = "no-example-schema-check")]
20 no_example_schema_check: bool,
21 #[clap(long = "keep-going")]
24 keep_going: bool,
25 #[clap(long = "wasm")]
27 wasm: bool,
28 flags: Vec<String>,
35}
36
37impl TestArgs {
38 pub(crate) fn run(mut self) {
39 let metadata = MetadataCommand::default().exec().unwrap();
40 let success = if matches!(self.backend, Backend::All) {
41 let mut success = true;
42 for backend in Backend::ALL {
43 self.backend = *backend;
44 let result = self.run_tests(&metadata);
45 success = success && result;
46 if !result && !self.keep_going {
47 break;
48 }
49 }
50 success
51 } else {
52 self.run_tests(&metadata)
53 };
54 if !success {
55 std::process::exit(1);
56 }
57 }
58
59 fn run_tests(&self, metadata: &Metadata) -> bool {
60 let backend_name = self.backend.to_string();
61 println!("Running tests for {backend_name}");
62 let exclude = crate::utils::get_exclude_for_backend(&backend_name, metadata);
63 if std::env::var("DATABASE_URL").is_err() {
64 match self.backend {
65 Backend::Postgres => {
66 if std::env::var("PG_DATABASE_URL").is_err() {
67 println!(
68 "Remember to set `PG_DATABASE_URL` for running the postgres tests"
69 );
70 }
71 }
72 Backend::Sqlite => {
73 if std::env::var("SQLITE_DATABASE_URL").is_err() {
74 println!(
75 "Remember to set `SQLITE_DATABASE_URL` for running the sqlite tests"
76 );
77 }
78 }
79 Backend::Mysql => {
80 if std::env::var("MYSQL_DATABASE_URL").is_err()
81 || std::env::var("MYSQL_UNIT_TEST_DATABASE_URL").is_err()
82 {
83 println!("Remember to set `MYSQL_DATABASE_URL` and `MYSQL_UNIT_TEST_DATABASE_URL` for running the mysql tests");
84 }
85 }
86 Backend::All => unreachable!(),
87 }
88 }
89 let backend = &self.backend;
90 if self.wasm {
91 if matches!(backend, Backend::Sqlite) {
92 fn run_tests(path: Utf8PathBuf) -> bool {
93 let mut command = Command::new("cargo");
94 command
95 .args([
96 "test",
97 "--features",
98 "sqlite",
99 "--target",
100 "wasm32-unknown-unknown",
101 ])
102 .current_dir(path)
103 .env("WASM_BINDGEN_TEST_TIMEOUT", "120")
104 .env(
105 "CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER",
106 "wasm-bindgen-test-runner",
107 )
108 .env("RUSTFLAGS", "--cfg getrandom_backend=\"wasm_js\"")
109 .stderr(Stdio::inherit())
110 .stdout(Stdio::inherit())
111 .status()
112 .unwrap()
113 .success()
114 }
115 if !run_tests(metadata.workspace_root.join("diesel")) {
116 eprintln!("Failed to run wasm diesel unit tests");
117 return false;
118 }
119 if !run_tests(metadata.workspace_root.join("diesel_tests")) {
120 eprintln!("Failed to run wasm integration tests");
121 return false;
122 }
123 return true;
124 } else {
125 eprintln!(
126 "Only the sqlite backend supports wasm for now, the current backend is {backend}"
127 );
128 return true;
129 }
130 }
131 let url = match backend {
132 Backend::Postgres => std::env::var("PG_DATABASE_URL"),
133 Backend::Sqlite => std::env::var("SQLITE_DATABASE_URL"),
134 Backend::Mysql => std::env::var("MYSQL_DATABASE_URL"),
135 Backend::All => unreachable!(),
136 };
137 let url = url
138 .or_else(|_| std::env::var("DATABASE_URL"))
139 .expect("DATABASE_URL is set for tests");
140
141 let mut command = Command::new("cargo");
143 command
144 .args(["run", "-p", "diesel_cli", "--no-default-features", "-F"])
145 .arg(backend.to_string())
146 .args(["--", "migration", "run", "--migration-dir"])
147 .arg(
148 metadata
149 .workspace_root
150 .join("migrations")
151 .join(backend.to_string()),
152 )
153 .arg("--database-url")
154 .arg(&url);
155 println!("Run database migration via `{command:?}`");
156 let status = command
157 .stdout(Stdio::inherit())
158 .stderr(Stdio::inherit())
159 .status()
160 .unwrap();
161 if !status.success() {
162 eprintln!("Failed to run migrations");
163 return false;
164 }
165
166 if !self.no_integration_tests {
167 let mut command = Command::new("cargo");
169 command
170 .args(["nextest", "run", "--workspace", "--no-default-features"])
171 .current_dir(&metadata.workspace_root)
172 .args(exclude)
173 .arg("-F")
174 .arg(format!("diesel/{backend}"))
175 .args(["-F", "diesel/extras"])
176 .arg("-F")
177 .arg(format!("diesel_derives/{backend}"))
178 .arg("-F")
179 .arg(format!("diesel_cli/{backend}"))
180 .arg("-F")
181 .arg(format!("migrations_macros/{backend}"))
182 .arg("-F")
183 .arg(format!("diesel_migrations/{backend}"))
184 .arg("-F")
185 .arg(format!("diesel_tests/{backend}"))
186 .arg("-F")
187 .arg(format!("diesel-dynamic-schema/{backend}"))
188 .args(&self.flags);
189
190 if matches!(self.backend, Backend::Mysql) {
191 command.args(["-j", "1"]);
193 }
194 println!("Running tests via `{command:?}`: ");
195
196 let out = command
197 .stderr(Stdio::inherit())
198 .stdout(Stdio::inherit())
199 .status()
200 .unwrap();
201 if !out.success() {
202 eprintln!("Failed to run integration tests");
203 return false;
204 }
205 } else {
206 println!("Integration tests skipped because `--no-integration-tests` was passed");
207 }
208 if !self.no_doc_tests {
209 let mut command = Command::new("cargo");
210
211 command
212 .current_dir(&metadata.workspace_root)
213 .args([
214 "test",
215 "--doc",
216 "--no-default-features",
217 "-p",
218 "diesel",
219 "-p",
220 "diesel_derives",
221 "-p",
222 "diesel_migrations",
223 "-p",
224 "diesel-dynamic-schema",
225 "-p",
226 "dsl_auto_type",
227 "-p",
228 "diesel_table_macro_syntax",
229 "-F",
230 "diesel/extras",
231 ])
232 .arg("-F")
233 .arg(format!("diesel/{backend}"))
234 .arg("-F")
235 .arg(format!("diesel_derives/{backend}"))
236 .arg("-F")
237 .arg(format!("diesel-dynamic-schema/{backend}"));
238 if matches!(backend, Backend::Mysql) {
239 command.args(["-j", "1"]);
241 }
242 println!("Running tests via `{command:?}`: ");
243 let status = command
244 .stdout(Stdio::inherit())
245 .stderr(Stdio::inherit())
246 .status()
247 .unwrap();
248 if !status.success() {
249 eprintln!("Failed to run doc tests");
250 return false;
251 }
252 } else {
253 println!("Doc tests are skipped because `--no-doc-tests` was passed");
254 }
255
256 if !self.no_example_schema_check {
257 let examples = metadata
258 .workspace_root
259 .join("examples")
260 .join(backend.to_string());
261 let temp_dir = if matches!(backend, Backend::Sqlite) {
262 Some(tempfile::tempdir().unwrap())
263 } else {
264 None
265 };
266 let mut fail = false;
267 for p in metadata
268 .workspace_packages()
269 .into_iter()
270 .filter(|p| p.manifest_path.starts_with(&examples))
271 {
272 let example_root = p.manifest_path.parent().unwrap();
273 if example_root.join("migrations").exists() {
274 let db_url = if matches!(backend, Backend::Sqlite) {
275 temp_dir
276 .as_ref()
277 .unwrap()
278 .path()
279 .join(&p.name)
280 .display()
281 .to_string()
282 } else {
283 let (start, end) = url.rsplit_once('/').unwrap();
287 let query = end.split_once('?').map(|(_, q)| q);
288
289 let mut url = format!("{start}/{}", p.name);
290 if let Some(query) = query {
291 url.push('?');
292 url.push_str(query);
293 }
294 url
295 };
296
297 let mut command = Command::new("cargo");
298 command
299 .current_dir(example_root)
300 .args(["run", "-p", "diesel_cli", "--no-default-features", "-F"])
301 .arg(backend.to_string())
302 .args(["--", "database", "reset", "--locked-schema"])
303 .env("DATABASE_URL", db_url);
304 println!(
305 "Check schema for example `{}` ({example_root}) with command `{command:?}`",
306 p.name,
307 );
308 let status = command.status().unwrap();
309 if !status.success() {
310 fail = true;
311 eprintln!("Failed to check example schema for `{}`", p.name);
312 if !self.keep_going {
313 return false;
314 }
315 }
316 }
317 }
318 if fail {
319 return false;
320 }
321 } else {
322 println!(
323 "Example schema check is skipped because `--no-example-schema-check` was passed"
324 );
325 }
326
327 true
328 }
329}