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