| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- use super::format_elapsed;
- use crate::docker::build_dir::{build_checksum, parse_versions_env, serialize_versions_env};
- use std::collections::HashMap;
- use std::fs;
- use std::path::PathBuf;
- use std::time::Duration;
- #[test]
- fn registry_refs_version_and_latest() {
- use crate::docker::push::registry_refs;
- let (v, l) = registry_refs("ghcr.io/kilork", "glm53-jovian-r35:local", "ead6e95a333d").unwrap();
- assert_eq!(v, "ghcr.io/kilork/glm53-jovian-r35:ead6e95a333d");
- assert_eq!(l, "ghcr.io/kilork/glm53-jovian-r35:latest");
- // slashed image name → last segment; base slash-trimmed
- let (v, _) = registry_refs("ghcr.io/kilork/", "foo/bar:1.2", "abc123").unwrap();
- assert_eq!(v, "ghcr.io/kilork/bar:abc123");
- // empty base/version → None
- assert_eq!(registry_refs("", "img:tag", "abc"), None);
- assert_eq!(registry_refs("ghcr.io/k", "img:tag", ""), None);
- }
- fn temp_test_dir(name: &str) -> PathBuf {
- let p = std::env::temp_dir().join(format!("sm121-build-test-{}-{}", std::process::id(), name));
- fs::remove_dir_all(&p).ok();
- fs::create_dir_all(&p).unwrap();
- p
- }
- #[test]
- fn format_elapsed_seconds_and_minutes() {
- assert_eq!(format_elapsed(Duration::from_secs(0)), "0s");
- assert_eq!(format_elapsed(Duration::from_secs(30)), "30s");
- assert_eq!(format_elapsed(Duration::from_secs(59)), "59s");
- assert_eq!(format_elapsed(Duration::from_secs(60)), "1m 00s");
- assert_eq!(format_elapsed(Duration::from_secs(90)), "1m 30s");
- assert_eq!(format_elapsed(Duration::from_secs(125)), "2m 05s");
- }
- #[test]
- fn parse_versions_env_skips_comments_and_blanks() {
- let raw = "# comment\n\nKEY=val\nOTHER=a=b\n SPACED = x \nnoeq\n";
- let pins = parse_versions_env(raw);
- assert_eq!(pins.get("KEY"), Some(&"val".to_string()));
- assert_eq!(pins.get("OTHER"), Some(&"a=b".to_string()));
- assert_eq!(pins.get("SPACED"), Some(&"x".to_string()));
- assert!(!pins.contains_key("noeq"));
- assert_eq!(pins.len(), 3);
- }
- #[test]
- fn parse_versions_env_quotes_are_normalized() {
- // quoted / unquoted / single-quoted forms of the same value must
- // resolve identically (stable checksums across edits); inner quotes
- // and non-matching outer quotes stay verbatim.
- let raw = "A=\"x:tag@sha256:abc\"\nB='y'\nC=z\nD=\"m@n\"'\"op\"'\nE=\"half\nF='weird\"\n";
- let pins = parse_versions_env(raw);
- assert_eq!(pins.get("A"), Some(&"x:tag@sha256:abc".to_string()));
- assert_eq!(pins.get("B"), Some(&"y".to_string()));
- assert_eq!(pins.get("C"), Some(&"z".to_string()));
- assert_eq!(pins.get("D"), Some(&"\"m@n\"'\"op\"'".to_string())); // mismatched outer quotes stay
- assert_eq!(pins.get("E"), Some(&"\"half".to_string()));
- assert_eq!(pins.get("F"), Some(&"'weird\"".to_string()));
- }
- #[test]
- fn serialize_versions_env_sorted_one_per_line() {
- let mut pins = std::collections::BTreeMap::new();
- pins.insert("B".into(), "2".into());
- pins.insert("A".into(), "1".into());
- let s = serialize_versions_env(&pins);
- assert_eq!(
- s,
- "# Merged versions.env \u{2014} generated by sm121 (profile + recipe [build.versions] applied)\nA=1\nB=2\n"
- );
- }
- #[test]
- fn build_checksum_no_profile_no_overrides_returns_none_and_dir_checksum() {
- let dir = temp_test_dir("no_overrides");
- fs::write(dir.join("versions.env"), "A=1\nB=2\n").unwrap();
- let (merged, ck) = build_checksum(&dir, None, &HashMap::new()).unwrap();
- assert!(merged.is_none());
- let (_, ck2) = build_checksum(&dir, None, &HashMap::new()).unwrap();
- assert_eq!(ck, ck2);
- fs::remove_dir_all(&dir).ok();
- }
- #[test]
- fn build_checksum_overrides_change_checksum_and_produce_merged() {
- let dir = temp_test_dir("overrides");
- fs::write(dir.join("versions.env"), "A=1\nB=2\n").unwrap();
- let no_over = build_checksum(&dir, None, &HashMap::new()).unwrap().1;
- let mut over = HashMap::new();
- over.insert("A".to_string(), "99".to_string());
- over.insert("C".to_string(), "3".to_string());
- let (merged, with_over) = build_checksum(&dir, None, &over).unwrap();
- assert_ne!(no_over, with_over);
- let m = merged.unwrap();
- assert!(m.contains("A=99"));
- assert!(m.contains("C=3"));
- assert!(m.contains("B=2"));
- assert!(!m.contains("\nA=1\n"));
- over.insert("A".to_string(), "100".to_string());
- let (_, ck_changed) = build_checksum(&dir, None, &over).unwrap();
- assert_ne!(with_over, ck_changed);
- fs::remove_dir_all(&dir).ok();
- }
- #[test]
- fn build_checksum_profile_merges_over_base_and_under_overrides() {
- let dir = temp_test_dir("profile");
- fs::write(dir.join("versions.env"), "A=1\nB=2\nC=3\n").unwrap();
- fs::create_dir_all(dir.join("profiles")).unwrap();
- fs::write(
- dir.join("profiles").join("gen.env"),
- "# profile pins (comment line ignored)\nA=10\nC=30\n",
- )
- .unwrap();
- // profile alone
- let (merged, with_profile) = build_checksum(&dir, Some("gen"), &HashMap::new()).unwrap();
- let m = merged.unwrap();
- assert!(m.contains("A=10"), "profile wins over base: {m}");
- assert!(m.contains("C=30"), "{m}");
- assert!(m.contains("B=2"), "base key untouched: {m}");
- let base = build_checksum(&dir, None, &HashMap::new()).unwrap().1;
- assert_ne!(base, with_profile, "profile changes the checksum");
- // recipe override still wins over the profile
- let mut over = HashMap::new();
- over.insert("A".to_string(), "99".to_string());
- let (merged, with_both) = build_checksum(&dir, Some("gen"), &over).unwrap();
- let m = merged.unwrap();
- assert!(m.contains("A=99"), "recipe wins over profile: {m}");
- assert!(m.contains("C=30"));
- assert_ne!(with_profile, with_both);
- // a profile edit changes the checksum (the file is in the build dir,
- // so the dir checksum moves too)
- fs::write(dir.join("profiles").join("gen.env"), "A=10\nC=31\n").unwrap();
- let (_, edited) = build_checksum(&dir, Some("gen"), &HashMap::new()).unwrap();
- assert_ne!(with_profile, edited);
- fs::remove_dir_all(&dir).ok();
- }
- #[test]
- fn build_checksum_missing_profile_lists_available() {
- let dir = temp_test_dir("profile_missing");
- fs::write(dir.join("versions.env"), "A=1\n").unwrap();
- fs::create_dir_all(dir.join("profiles")).unwrap();
- fs::write(dir.join("profiles").join("gen.env"), "A=10\n").unwrap();
- let err = build_checksum(&dir, Some("nope"), &HashMap::new())
- .unwrap_err()
- .to_string();
- assert!(err.contains("build profile 'nope' not found"), "{err}");
- assert!(
- err.contains("gen.env"),
- "error lists available profiles: {err}"
- );
- fs::remove_dir_all(&dir).ok();
- }
|