build_tests.rs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. use super::format_elapsed;
  2. use crate::docker::build_dir::{build_checksum, parse_versions_env, serialize_versions_env};
  3. use std::collections::HashMap;
  4. use std::fs;
  5. use std::path::PathBuf;
  6. use std::time::Duration;
  7. #[test]
  8. fn registry_refs_version_and_latest() {
  9. use crate::docker::push::registry_refs;
  10. let (v, l) = registry_refs("ghcr.io/kilork", "glm53-jovian-r35:local", "ead6e95a333d").unwrap();
  11. assert_eq!(v, "ghcr.io/kilork/glm53-jovian-r35:ead6e95a333d");
  12. assert_eq!(l, "ghcr.io/kilork/glm53-jovian-r35:latest");
  13. // slashed image name → last segment; base slash-trimmed
  14. let (v, _) = registry_refs("ghcr.io/kilork/", "foo/bar:1.2", "abc123").unwrap();
  15. assert_eq!(v, "ghcr.io/kilork/bar:abc123");
  16. // empty base/version → None
  17. assert_eq!(registry_refs("", "img:tag", "abc"), None);
  18. assert_eq!(registry_refs("ghcr.io/k", "img:tag", ""), None);
  19. }
  20. fn temp_test_dir(name: &str) -> PathBuf {
  21. let p = std::env::temp_dir().join(format!("sm121-build-test-{}-{}", std::process::id(), name));
  22. fs::remove_dir_all(&p).ok();
  23. fs::create_dir_all(&p).unwrap();
  24. p
  25. }
  26. #[test]
  27. fn format_elapsed_seconds_and_minutes() {
  28. assert_eq!(format_elapsed(Duration::from_secs(0)), "0s");
  29. assert_eq!(format_elapsed(Duration::from_secs(30)), "30s");
  30. assert_eq!(format_elapsed(Duration::from_secs(59)), "59s");
  31. assert_eq!(format_elapsed(Duration::from_secs(60)), "1m 00s");
  32. assert_eq!(format_elapsed(Duration::from_secs(90)), "1m 30s");
  33. assert_eq!(format_elapsed(Duration::from_secs(125)), "2m 05s");
  34. }
  35. #[test]
  36. fn parse_versions_env_skips_comments_and_blanks() {
  37. let raw = "# comment\n\nKEY=val\nOTHER=a=b\n SPACED = x \nnoeq\n";
  38. let pins = parse_versions_env(raw);
  39. assert_eq!(pins.get("KEY"), Some(&"val".to_string()));
  40. assert_eq!(pins.get("OTHER"), Some(&"a=b".to_string()));
  41. assert_eq!(pins.get("SPACED"), Some(&"x".to_string()));
  42. assert!(!pins.contains_key("noeq"));
  43. assert_eq!(pins.len(), 3);
  44. }
  45. #[test]
  46. fn parse_versions_env_quotes_are_normalized() {
  47. // quoted / unquoted / single-quoted forms of the same value must
  48. // resolve identically (stable checksums across edits); inner quotes
  49. // and non-matching outer quotes stay verbatim.
  50. let raw = "A=\"x:tag@sha256:abc\"\nB='y'\nC=z\nD=\"m@n\"'\"op\"'\nE=\"half\nF='weird\"\n";
  51. let pins = parse_versions_env(raw);
  52. assert_eq!(pins.get("A"), Some(&"x:tag@sha256:abc".to_string()));
  53. assert_eq!(pins.get("B"), Some(&"y".to_string()));
  54. assert_eq!(pins.get("C"), Some(&"z".to_string()));
  55. assert_eq!(pins.get("D"), Some(&"\"m@n\"'\"op\"'".to_string())); // mismatched outer quotes stay
  56. assert_eq!(pins.get("E"), Some(&"\"half".to_string()));
  57. assert_eq!(pins.get("F"), Some(&"'weird\"".to_string()));
  58. }
  59. #[test]
  60. fn serialize_versions_env_sorted_one_per_line() {
  61. let mut pins = std::collections::BTreeMap::new();
  62. pins.insert("B".into(), "2".into());
  63. pins.insert("A".into(), "1".into());
  64. let s = serialize_versions_env(&pins);
  65. assert_eq!(
  66. s,
  67. "# Merged versions.env \u{2014} generated by sm121 (profile + recipe [build.versions] applied)\nA=1\nB=2\n"
  68. );
  69. }
  70. #[test]
  71. fn build_checksum_no_profile_no_overrides_returns_none_and_dir_checksum() {
  72. let dir = temp_test_dir("no_overrides");
  73. fs::write(dir.join("versions.env"), "A=1\nB=2\n").unwrap();
  74. let (merged, ck) = build_checksum(&dir, None, &HashMap::new()).unwrap();
  75. assert!(merged.is_none());
  76. let (_, ck2) = build_checksum(&dir, None, &HashMap::new()).unwrap();
  77. assert_eq!(ck, ck2);
  78. fs::remove_dir_all(&dir).ok();
  79. }
  80. #[test]
  81. fn build_checksum_overrides_change_checksum_and_produce_merged() {
  82. let dir = temp_test_dir("overrides");
  83. fs::write(dir.join("versions.env"), "A=1\nB=2\n").unwrap();
  84. let no_over = build_checksum(&dir, None, &HashMap::new()).unwrap().1;
  85. let mut over = HashMap::new();
  86. over.insert("A".to_string(), "99".to_string());
  87. over.insert("C".to_string(), "3".to_string());
  88. let (merged, with_over) = build_checksum(&dir, None, &over).unwrap();
  89. assert_ne!(no_over, with_over);
  90. let m = merged.unwrap();
  91. assert!(m.contains("A=99"));
  92. assert!(m.contains("C=3"));
  93. assert!(m.contains("B=2"));
  94. assert!(!m.contains("\nA=1\n"));
  95. over.insert("A".to_string(), "100".to_string());
  96. let (_, ck_changed) = build_checksum(&dir, None, &over).unwrap();
  97. assert_ne!(with_over, ck_changed);
  98. fs::remove_dir_all(&dir).ok();
  99. }
  100. #[test]
  101. fn build_checksum_profile_merges_over_base_and_under_overrides() {
  102. let dir = temp_test_dir("profile");
  103. fs::write(dir.join("versions.env"), "A=1\nB=2\nC=3\n").unwrap();
  104. fs::create_dir_all(dir.join("profiles")).unwrap();
  105. fs::write(
  106. dir.join("profiles").join("gen.env"),
  107. "# profile pins (comment line ignored)\nA=10\nC=30\n",
  108. )
  109. .unwrap();
  110. // profile alone
  111. let (merged, with_profile) = build_checksum(&dir, Some("gen"), &HashMap::new()).unwrap();
  112. let m = merged.unwrap();
  113. assert!(m.contains("A=10"), "profile wins over base: {m}");
  114. assert!(m.contains("C=30"), "{m}");
  115. assert!(m.contains("B=2"), "base key untouched: {m}");
  116. let base = build_checksum(&dir, None, &HashMap::new()).unwrap().1;
  117. assert_ne!(base, with_profile, "profile changes the checksum");
  118. // recipe override still wins over the profile
  119. let mut over = HashMap::new();
  120. over.insert("A".to_string(), "99".to_string());
  121. let (merged, with_both) = build_checksum(&dir, Some("gen"), &over).unwrap();
  122. let m = merged.unwrap();
  123. assert!(m.contains("A=99"), "recipe wins over profile: {m}");
  124. assert!(m.contains("C=30"));
  125. assert_ne!(with_profile, with_both);
  126. // a profile edit changes the checksum (the file is in the build dir,
  127. // so the dir checksum moves too)
  128. fs::write(dir.join("profiles").join("gen.env"), "A=10\nC=31\n").unwrap();
  129. let (_, edited) = build_checksum(&dir, Some("gen"), &HashMap::new()).unwrap();
  130. assert_ne!(with_profile, edited);
  131. fs::remove_dir_all(&dir).ok();
  132. }
  133. #[test]
  134. fn build_checksum_missing_profile_lists_available() {
  135. let dir = temp_test_dir("profile_missing");
  136. fs::write(dir.join("versions.env"), "A=1\n").unwrap();
  137. fs::create_dir_all(dir.join("profiles")).unwrap();
  138. fs::write(dir.join("profiles").join("gen.env"), "A=10\n").unwrap();
  139. let err = build_checksum(&dir, Some("nope"), &HashMap::new())
  140. .unwrap_err()
  141. .to_string();
  142. assert!(err.contains("build profile 'nope' not found"), "{err}");
  143. assert!(
  144. err.contains("gen.env"),
  145. "error lists available profiles: {err}"
  146. );
  147. fs::remove_dir_all(&dir).ok();
  148. }