1#[macro_use] mod imports; use imports::*;
3
4x!{crate_pin_wildcard_deps}
5x!{fix_nested_tables}
6x!{get_version_of_local_dep}
7x!{insert_local_version_if_absent}
8x!{is_dependencies_key}
9x!{pick_highest_version}
10x!{pin_from_lock_or_warn}
11x!{pin_wildcard_dependencies_in_table}
12x!{workspace_pin_all_wildcard_deps}
13x!{pin_wildcard_inline_table_dependency}
14x!{pin_wildcard_string_dependency}
15x!{pin_wildcard_table_dependency}
16x!{pin_wildcards_in_doc}
17x!{replace_wildcard_version_with_local}
18x!{toml_pin_wildcard_deps}
19
20#[cfg(test)]
21mod test_end_to_end_scenario {
22 use super::*;
23 use std::{fs, path::PathBuf};
24 use tempfile::tempdir;
25 use tracing::{info, debug, trace};
26
27 #[traced_test]
51 async fn pins_local_and_wildcard_deps_from_snippet() {
52 info!("Starting test_end_to_end_scenario::pins_local_and_wildcard_deps_from_snippet");
53
54 let temp = tempdir().expect("failed to create tempdir");
56 let base_path = temp.path();
57
58 let crate_dir = base_path.join("mycrate");
60 fs::create_dir_all(&crate_dir).expect("failed to create main crate dir");
61
62 let local_crates_no_version = vec![
65 ("workspacer-config", "0.9.1"),
66 ("workspacer-errors", "0.9.2"),
67 ("workspacer-toml-interface", "0.9.3"),
68 ];
69 for (dir_name, ver) in &local_crates_no_version {
70 let dir = base_path.join(dir_name);
71 fs::create_dir_all(&dir).expect("failed to create local crate dir");
72 let local_cargo_toml = format!(
73 r#"[package]
74name = "{dir_name}"
75version = "{ver}"
76"#,
77 );
78 fs::write(dir.join("Cargo.toml"), local_cargo_toml)
79 .expect("failed to write local Cargo.toml");
80 }
81
82 let pinned_locals = vec![
84 ("workspacer-3p", "0.5.9"),
85 ("workspacer-interface", "0.5.8"),
86 ];
87 for (dir_name, ver) in &pinned_locals {
88 let dir = base_path.join(dir_name);
89 fs::create_dir_all(&dir).expect("failed to create pinned local crate dir");
90 let local_cargo_toml = format!(
91 r#"[package]
92name = "{dir_name}"
93version = "{ver}"
94"#,
95 );
96 fs::write(dir.join("Cargo.toml"), local_cargo_toml)
97 .expect("failed to write pinned local Cargo.toml");
98 }
99
100 let cargo_toml_contents = r#"
103[dependencies]
104derive_builder = "0.20.2"
105regex = "*"
106
107[dependencies.serde]
108features = [ "derive" ]
109version = "*"
110
111[dependencies.serde_derive]
112version = "*"
113
114[dependencies.serde_json]
115version = "*"
116
117[dependencies.workspacer-3p]
118path = "../workspacer-3p"
119version = "0.5.0"
120
121[dependencies.workspacer-config]
122path = "../workspacer-config"
123
124[dependencies.workspacer-errors]
125path = "../workspacer-errors"
126
127[dependencies.workspacer-interface]
128path = "../workspacer-interface"
129version = "0.5.0"
130
131[dependencies.workspacer-toml-interface]
132path = "../workspacer-toml-interface"
133
134[package]
135authors = [ "klebs tpk3.mx@gmail.com" ]
136description = "A utility crate for parsing, validating, and handling Cargo.toml files as part of the Workspacer ecosystem."
137edition = "2024"
138license = "MIT OR Apache-2.0"
139name = "workspacer-toml"
140repository = "https://github.com/klebs6/klebs-general"
141version = "0.5.0"
142"#;
143 let main_cargo_toml_path = crate_dir.join("Cargo.toml");
144 fs::write(&main_cargo_toml_path, cargo_toml_contents)
145 .expect("failed to write main Cargo.toml in mycrate/");
146
147 let cargo_lock_contents = r#"
149[[package]]
150name = "regex"
151version = "1.10.5"
152source = "registry+https://github.com/rust-lang/crates.io-index"
153
154[[package]]
155name = "serde"
156version = "1.0.153"
157source = "registry+https://github.com/rust-lang/crates.io-index"
158
159[[package]]
160name = "serde_derive"
161version = "1.0.153"
162source = "registry+https://github.com/rust-lang/crates.io-index"
163
164[[package]]
165name = "serde_json"
166version = "1.0.153"
167source = "registry+https://github.com/rust-lang/crates.io-index"
168"#;
169 fs::write(crate_dir.join("Cargo.lock"), cargo_lock_contents)
170 .expect("failed to write Cargo.lock in mycrate/");
171
172 let mut handle = match CrateHandle::new(&crate_dir).await {
174 Ok(ch) => ch,
175 Err(e) => panic!("Failed to create CrateHandle for test: {:?}", e),
176 };
177
178 let result = handle.pin_all_wildcard_dependencies().await;
180 assert!(result.is_ok(), "pin_all_wildcard_dependencies failed: {:?}", result);
181
182 let pinned_toml = CargoToml::new(&main_cargo_toml_path)
184 .await
185 .expect("failed to re-open pinned Cargo.toml");
186 let doc = pinned_toml.document_clone().await
187 .expect("failed to clone pinned doc");
188
189 let deps_item = doc.as_table().get("dependencies")
191 .expect("no [dependencies] in pinned doc");
192 let deps = deps_item.as_table().expect("[dependencies] not a table?");
193
194 let pinned_regex = deps.get("regex").and_then(|i| i.as_str())
196 .expect("missing pinned regex");
197 assert_eq!(pinned_regex, "1.10.5", "Expected regex pinned from lockfile");
198
199 let pinned_serde = deps.get("serde")
201 .and_then(|i| i.as_table())
202 .and_then(|t| t.get("version"))
203 .and_then(|v| v.as_value())
204 .and_then(|v| v.as_str())
205 .expect("missing pinned serde version");
206 assert_eq!(pinned_serde, "1.0.153");
207
208 let pinned_serde_derive = deps.get("serde_derive")
210 .and_then(|i| i.as_table())
211 .and_then(|t| t.get("version"))
212 .and_then(|v| v.as_value())
213 .and_then(|v| v.as_str())
214 .expect("missing pinned serde_derive version");
215 assert_eq!(pinned_serde_derive, "1.0.153");
216
217 let pinned_serde_json = deps.get("serde_json")
219 .and_then(|i| i.as_table())
220 .and_then(|t| t.get("version"))
221 .and_then(|v| v.as_value())
222 .and_then(|v| v.as_str())
223 .expect("missing pinned serde_json version");
224 assert_eq!(pinned_serde_json, "1.0.153");
225
226 let pinned_config = deps.get("workspacer-config")
229 .and_then(|i| i.as_table())
230 .and_then(|t| t.get("version"))
231 .and_then(|v| v.as_value())
232 .and_then(|v| v.as_str())
233 .expect("missing inserted version for workspacer-config");
234 assert_eq!(pinned_config, "0.9.1");
235
236 let pinned_errors = deps.get("workspacer-errors")
237 .and_then(|i| i.as_table())
238 .and_then(|t| t.get("version"))
239 .and_then(|v| v.as_value())
240 .and_then(|v| v.as_str())
241 .expect("missing inserted version for workspacer-errors");
242 assert_eq!(pinned_errors, "0.9.2");
243
244 let pinned_toml_iface = deps.get("workspacer-toml-interface")
245 .and_then(|i| i.as_table())
246 .and_then(|t| t.get("version"))
247 .and_then(|v| v.as_value())
248 .and_then(|v| v.as_str())
249 .expect("missing inserted version for workspacer-toml-interface");
250 assert_eq!(pinned_toml_iface, "0.9.3");
251
252 let pinned_3p = deps.get("workspacer-3p")
255 .and_then(|i| i.as_table())
256 .and_then(|t| t.get("version"))
257 .and_then(|v| v.as_value())
258 .and_then(|v| v.as_str())
259 .expect("missing pinned version for workspacer-3p");
260 assert_eq!(pinned_3p, "0.5.0");
261
262 let pinned_iface = deps.get("workspacer-interface")
263 .and_then(|i| i.as_table())
264 .and_then(|t| t.get("version"))
265 .and_then(|v| v.as_value())
266 .and_then(|v| v.as_str())
267 .expect("missing pinned version for workspacer-interface");
268 assert_eq!(pinned_iface, "0.5.0");
269
270 let pinned_derive_builder = deps.get("derive_builder")
272 .and_then(|v| v.as_str())
273 .unwrap_or("<missing>");
274 assert_eq!(pinned_derive_builder, "0.20.2");
275
276 debug!("test_end_to_end_scenario::pins_local_and_wildcard_deps_from_snippet passed");
277 }
278}
279
280#[cfg(test)]
281mod test_multi_version_same_crate {
282 use super::*;
283 use tempfile::tempdir;
284 use std::{fs, path::PathBuf};
285 use tracing::{info, debug, trace};
286
287 fn write_subcrate_cargo_toml(
306 dir: &PathBuf,
307 name: &str,
308 error_tree_spec: &str, ) {
310 let contents = format!(
311 r#"
312[package]
313name = "{name}"
314version = "0.1.0"
315edition = "2021"
316
317[dependencies]
318error-tree = "{error_tree_spec}"
319 "#
320 );
321 fs::write(dir.join("Cargo.toml"), contents)
322 .expect("failed to write subcrate Cargo.toml");
323 }
324
325 fn multi_version_lockfile_contents() -> &'static str {
328 r#"
329[[package]]
330name = "error-tree"
331version = "0.3.6"
332source = "registry+https://github.com/rust-lang/crates.io-index"
333
334[[package]]
335name = "error-tree"
336version = "1.0.0"
337source = "registry+https://github.com/rust-lang/crates.io-index"
338
339[[package]]
340name = "something-else"
341version = "2.5.0"
342source = "registry+https://github.com/rust-lang/crates.io-index"
343"#
344 }
345
346 fn workspace_cargo_toml() -> &'static str {
348 r#"
349[workspace]
350members = [
351 "batch-scribe",
352 "batch-executor",
353]
354"#
355 }
356
357 #[traced_test]
358 #[allow(clippy::too_many_lines)]
359 async fn pins_to_highest_when_star_given() {
360 info!("Starting test_multi_version_same_crate::pins_to_highest_when_star_given");
361 let temp = tempdir().expect("failed to create tempdir");
368 let base_dir = temp.path().to_path_buf();
369
370 fs::write(base_dir.join("Cargo.toml"), workspace_cargo_toml())
372 .expect("failed to write workspace Cargo.toml");
373
374 let scribe_dir = base_dir.join("batch-scribe");
376 fs::create_dir_all(&scribe_dir).expect("failed to create batch-scribe dir");
377 write_subcrate_cargo_toml(&scribe_dir, "batch-scribe", "*");
378
379 let executor_dir = base_dir.join("batch-executor");
380 fs::create_dir_all(&executor_dir).expect("failed to create batch-executor dir");
381 write_subcrate_cargo_toml(&executor_dir, "batch-executor", "*");
382
383 fs::write(base_dir.join("Cargo.lock"), multi_version_lockfile_contents())
385 .expect("failed to write multi-version Cargo.lock");
386
387 let mut workspace = match Workspace::<PathBuf, CrateHandle>::new(&base_dir).await {
389 Ok(ws) => ws,
390 Err(e) => panic!("Not a valid workspace? error: {:?}", e),
391 };
392
393 let result = workspace.pin_all_wildcard_dependencies().await;
395 assert!(result.is_ok(), "pin_all_wildcard_dependencies() failed: {:?}", result);
396
397 for crate_dir in &[&scribe_dir, &executor_dir] {
399 let pinned_toml = crate_dir.join("Cargo.toml");
400 let pinned = CargoToml::new(&pinned_toml)
401 .await
402 .expect("failed to open pinned subcrate Cargo.toml");
403 let doc = pinned.document_clone().await
404 .expect("failed to clone pinned doc");
405 let deps_item = doc.as_table().get("dependencies")
406 .expect("no [dependencies] in pinned doc");
407 let deps_tbl = deps_item.as_table().expect("[dependencies] not table?");
408
409 let pinned_error_tree = deps_tbl.get("error-tree")
411 .and_then(|i| i.as_str())
412 .unwrap_or("<missing>");
413 assert_eq!(
414 pinned_error_tree,
415 "1.0.0",
416 "Expected star to pin to highest lockfile version"
417 );
418 }
419
420 debug!("test_multi_version_same_crate::pins_to_highest_when_star_given passed");
421 }
422
423 #[traced_test]
424 #[allow(clippy::too_many_lines)]
425 async fn leaves_existing_pinned_version_intact() {
426 info!("Starting test_multi_version_same_crate::leaves_existing_pinned_version_intact");
427 let temp = tempdir().expect("failed to create tempdir");
433 let base_dir = temp.path().to_path_buf();
434
435 fs::write(base_dir.join("Cargo.toml"), workspace_cargo_toml())
437 .expect("failed to write workspace Cargo.toml");
438
439 let scribe_dir = base_dir.join("batch-scribe");
441 fs::create_dir_all(&scribe_dir).expect("failed to create batch-scribe dir");
442 write_subcrate_cargo_toml(&scribe_dir, "batch-scribe", "0.3.6");
444
445 let executor_dir = base_dir.join("batch-executor");
446 fs::create_dir_all(&executor_dir).expect("failed to create batch-executor dir");
447 write_subcrate_cargo_toml(&executor_dir, "batch-executor", "*");
449
450 fs::write(base_dir.join("Cargo.lock"), multi_version_lockfile_contents())
452 .expect("failed to write multi-version Cargo.lock");
453
454 let mut workspace = match Workspace::<PathBuf, CrateHandle>::new(&base_dir).await {
456 Ok(ws) => ws,
457 Err(e) => panic!("not a valid workspace? error: {:?}", e),
458 };
459
460 let result = workspace.pin_all_wildcard_dependencies().await;
462 assert!(result.is_ok(), "pin_all_wildcard_dependencies() failed: {:?}", result);
463
464 {
468 let pinned_toml = scribe_dir.join("Cargo.toml");
469 let pinned = CargoToml::new(&pinned_toml)
470 .await
471 .expect("failed scribe CargoToml");
472 let doc = pinned.document_clone().await
473 .expect("scribe doc clone fail");
474 let deps = doc["dependencies"].as_table().expect("not a table");
475 let pinned_errtree = deps.get("error-tree")
476 .and_then(|i| i.as_str())
477 .unwrap_or("<missing>");
478 assert_eq!(pinned_errtree, "0.3.6",
479 "Explicit pinned version should remain unchanged");
480 }
481 {
482 let pinned_toml = executor_dir.join("Cargo.toml");
483 let pinned = CargoToml::new(&pinned_toml)
484 .await
485 .expect("failed executor CargoToml");
486 let doc = pinned.document_clone().await
487 .expect("executor doc clone fail");
488 let deps = doc["dependencies"].as_table().expect("not a table");
489 let pinned_errtree = deps.get("error-tree")
490 .and_then(|i| i.as_str())
491 .unwrap_or("<missing>");
492 assert_eq!(pinned_errtree, "1.0.0",
493 "Wildcard should pick highest from lockfile");
494 }
495
496 debug!("test_multi_version_same_crate::leaves_existing_pinned_version_intact passed");
497 }
498
499 #[traced_test]
500 #[allow(clippy::too_many_lines)]
501 async fn warns_when_multiple_versions_present() {
502 info!("Starting test_multi_version_same_crate::warns_when_multiple_versions_present");
503 let temp = tempdir().expect("failed to create tempdir");
515 let base_dir = temp.path().to_path_buf();
516
517 let cargo_toml_contents = r#"
519[package]
520name = "single-test"
521version = "0.1.0"
522edition = "2021"
523
524[dependencies]
525error-tree = "*"
526"#;
527 fs::write(base_dir.join("Cargo.toml"), cargo_toml_contents)
528 .expect("failed to write single crate Cargo.toml");
529
530 fs::write(base_dir.join("Cargo.lock"), multi_version_lockfile_contents())
532 .expect("failed to write multi-version Cargo.lock");
533
534 let mut handle = match CrateHandle::new(&base_dir).await {
536 Ok(ch) => ch,
537 Err(e) => panic!("Failed to create CrateHandle: {:?}", e),
538 };
539
540 let result = handle.pin_all_wildcard_dependencies().await;
542 assert!(result.is_ok());
543
544 let pinned = CargoToml::new(&base_dir.join("Cargo.toml")).await
546 .expect("failed to re-open pinned single crate Cargo.toml");
547 let doc = pinned.document_clone().await
548 .expect("doc clone failed");
549 let deps = doc["dependencies"].as_table().expect("not a table");
550 let pinned_errtree = deps.get("error-tree").and_then(|i| i.as_str()).unwrap();
551 assert_eq!(pinned_errtree, "1.0.0");
552
553 debug!("test_multi_version_same_crate::warns_when_multiple_versions_present passed");
559 }
560}