Skip to content

Commit fea6ff2

Browse files
committed
test(materialize): expose mode drift
1 parent e282a7e commit fea6ff2

1 file changed

Lines changed: 262 additions & 0 deletions

File tree

tests/materialize.rs

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use std::fs;
2+
use std::os::unix::fs::PermissionsExt;
3+
use std::os::unix::process::CommandExt as _;
24
use std::path::Path;
35
use std::process::Command;
46

@@ -550,6 +552,266 @@ fn byte_identical_tracked_target_is_allowed_without_modification() {
550552
assert_eq!(before, after, "identical target was needlessly rewritten");
551553
}
552554

555+
#[test]
556+
fn executable_copy_repairs_a_byte_identical_destination_mode() {
557+
let tmp = tempfile::tempdir().unwrap();
558+
let catalog = tmp.path().join("catalog");
559+
let workspace = tmp.path().join("workspace");
560+
fs::create_dir_all(&workspace).unwrap();
561+
write(&catalog.join("_templates/wrapper"), "#!/bin/sh\nexit 0\n");
562+
let destination = workspace.join("bin/wrapper");
563+
write(&destination, "#!/bin/sh\nexit 0\n");
564+
fs::set_permissions(&destination, fs::Permissions::from_mode(0o644)).unwrap();
565+
write(
566+
&catalog.join("agents/Silber/cos/agent.kdl"),
567+
agent_kdl(
568+
&workspace,
569+
r#" copy "_templates/wrapper" "bin/wrapper" executable=#true"#,
570+
),
571+
);
572+
573+
let found = discover(&catalog);
574+
assert!(found.errors.is_empty(), "{:?}", found.errors);
575+
let report = materialize_catalog(&catalog, &found.specs, "Silber");
576+
assert!(report.is_clean(), "{:?}", report.errors);
577+
assert_eq!(
578+
fs::metadata(destination).unwrap().permissions().mode() & 0o777,
579+
0o755
580+
);
581+
}
582+
583+
#[test]
584+
fn regular_copy_repairs_a_byte_identical_executable_mode() {
585+
let tmp = tempfile::tempdir().unwrap();
586+
let catalog = tmp.path().join("catalog");
587+
let workspace = tmp.path().join("workspace");
588+
fs::create_dir_all(&workspace).unwrap();
589+
write(&catalog.join("_templates/plain"), "same\n");
590+
let destination = workspace.join("plain");
591+
write(&destination, "same\n");
592+
fs::set_permissions(&destination, fs::Permissions::from_mode(0o777)).unwrap();
593+
write(
594+
&catalog.join("agents/Silber/cos/agent.kdl"),
595+
agent_kdl(&workspace, r#" copy "_templates/plain" "plain""#),
596+
);
597+
598+
let found = discover(&catalog);
599+
let report = materialize_catalog(&catalog, &found.specs, "Silber");
600+
601+
assert!(report.is_clean(), "{:?}", report.errors);
602+
assert_eq!(
603+
fs::metadata(destination).unwrap().permissions().mode() & 0o777,
604+
0o644
605+
);
606+
}
607+
608+
#[test]
609+
fn content_directives_create_exact_modes_under_a_restrictive_umask() {
610+
let tmp = tempfile::tempdir().unwrap();
611+
let catalog = tmp.path().join("catalog");
612+
let workspace = tmp.path().join("workspace");
613+
fs::create_dir_all(&workspace).unwrap();
614+
let template = catalog.join("_templates/tool");
615+
write(&template, "#!/bin/sh\nexit 0\n");
616+
fs::set_permissions(&template, fs::Permissions::from_mode(0o755)).unwrap();
617+
write(
618+
&catalog.join("agents/Silber/cos/agent.kdl"),
619+
agent_kdl(
620+
&workspace,
621+
r##" copy "_templates/tool" "bin/copied" executable=#true
622+
copy "_templates/tool" "plain-copy"
623+
file "bin/written" "#!/bin/sh\nexit 0\n" executable=#true
624+
json-upsert "bin/data.json" #"{"value":true}"# executable=#true
625+
ensure-line "bin/lines" "line" executable=#true
626+
file "plain" "plain"
627+
"##,
628+
),
629+
);
630+
631+
let mut process = Command::new(env!("CARGO_BIN_EXE_st2"));
632+
process.args(["up", "--catalog"]).arg(&catalog).args([
633+
"--host",
634+
"Silber",
635+
"--materialize-only",
636+
]);
637+
unsafe {
638+
process.pre_exec(|| {
639+
libc::umask(0o077);
640+
Ok(())
641+
});
642+
}
643+
let output = process.output().unwrap();
644+
assert!(
645+
output.status.success(),
646+
"stdout:\n{}\nstderr:\n{}",
647+
String::from_utf8_lossy(&output.stdout),
648+
String::from_utf8_lossy(&output.stderr)
649+
);
650+
for path in ["bin/copied", "bin/written", "bin/data.json", "bin/lines"] {
651+
assert_eq!(
652+
fs::metadata(workspace.join(path))
653+
.unwrap()
654+
.permissions()
655+
.mode()
656+
& 0o777,
657+
0o755,
658+
"{path}"
659+
);
660+
}
661+
for path in ["plain", "plain-copy"] {
662+
assert_eq!(
663+
fs::metadata(workspace.join(path))
664+
.unwrap()
665+
.permissions()
666+
.mode()
667+
& 0o777,
668+
0o644,
669+
"{path}"
670+
);
671+
}
672+
assert!(
673+
Command::new(workspace.join("bin/copied"))
674+
.status()
675+
.unwrap()
676+
.success()
677+
);
678+
}
679+
680+
#[test]
681+
fn inline_executable_content_is_exact_runnable_and_idempotent() {
682+
let tmp = tempfile::tempdir().unwrap();
683+
let catalog = tmp.path().join("catalog");
684+
let workspace = tmp.path().join("workspace");
685+
fs::create_dir_all(&workspace).unwrap();
686+
write(
687+
&catalog.join("agents/Silber/cos/agent.kdl"),
688+
agent_kdl(
689+
&workspace,
690+
r#####" file "bin/probe" executable=#true {
691+
content #"""
692+
#!/bin/sh
693+
printf 'hello:%s\n' "${ST_AGENT:-unknown}"
694+
695+
"""#
696+
}
697+
file "empty" ""
698+
file "quote-sequence" {
699+
content ##"""
700+
before
701+
"""# remains content
702+
703+
"""##
704+
}
705+
"#####,
706+
),
707+
);
708+
let expected_probe = b"#!/bin/sh\nprintf 'hello:%s\\n' \"${ST_AGENT:-unknown}\"\n".as_slice();
709+
let expected_quote = b"before\n\"\"\"# remains content\n".as_slice();
710+
711+
let found = discover(&catalog);
712+
let first = materialize_catalog(&catalog, &found.specs, "Silber");
713+
assert!(first.is_clean(), "{:?}", first.errors);
714+
assert_eq!(first.materialized.len(), 3, "{:?}", first.materialized);
715+
716+
let probe = workspace.join("bin/probe");
717+
assert_eq!(fs::read(&probe).unwrap(), expected_probe);
718+
assert_eq!(fs::read(workspace.join("empty")).unwrap(), b"");
719+
assert_eq!(
720+
fs::read(workspace.join("quote-sequence")).unwrap(),
721+
expected_quote
722+
);
723+
assert_eq!(
724+
fs::metadata(&probe).unwrap().permissions().mode() & 0o777,
725+
0o755
726+
);
727+
let executed = Command::new(&probe)
728+
.env("ST_AGENT", "Silber.inline-proof")
729+
.output()
730+
.unwrap();
731+
assert!(
732+
executed.status.success(),
733+
"{}",
734+
String::from_utf8_lossy(&executed.stderr)
735+
);
736+
assert_eq!(executed.stdout, b"hello:Silber.inline-proof\n");
737+
738+
let second = materialize_catalog(&catalog, &found.specs, "Silber");
739+
assert!(second.is_clean(), "{:?}", second.errors);
740+
assert!(
741+
second.materialized.is_empty(),
742+
"unchanged operations were reported as materialized: {:?}",
743+
second.materialized
744+
);
745+
assert_eq!(fs::read(probe).unwrap(), expected_probe);
746+
}
747+
748+
#[test]
749+
fn one_inline_target_repairs_mode_toggles_in_both_directions() {
750+
let tmp = tempfile::tempdir().unwrap();
751+
let catalog = tmp.path().join("catalog");
752+
let workspace = tmp.path().join("workspace");
753+
fs::create_dir_all(&workspace).unwrap();
754+
let declaration = |executable: bool| {
755+
agent_kdl(
756+
&workspace,
757+
&format!(
758+
" file \"target\" \"same\" executable=#{}",
759+
if executable { "true" } else { "false" }
760+
),
761+
)
762+
};
763+
764+
for (executable, expected_mode) in [(true, 0o755), (false, 0o644), (true, 0o755)] {
765+
write(
766+
&catalog.join("agents/Silber/cos/agent.kdl"),
767+
declaration(executable),
768+
);
769+
let found = discover(&catalog);
770+
let report = materialize_catalog(&catalog, &found.specs, "Silber");
771+
assert!(report.is_clean(), "{:?}", report.errors);
772+
assert_eq!(report.materialized.len(), 1, "{:?}", report.materialized);
773+
assert_eq!(
774+
fs::metadata(workspace.join("target"))
775+
.unwrap()
776+
.permissions()
777+
.mode()
778+
& 0o777,
779+
expected_mode
780+
);
781+
}
782+
}
783+
784+
#[test]
785+
fn invalid_render_mode_properties_fail_validation() {
786+
for (directive, expected) in [
787+
(
788+
r#"copy "_templates/source" "target" executable="yes""#,
789+
"property 'executable' must be a boolean",
790+
),
791+
(
792+
r#"file "target" "content" mode=#true"#,
793+
"unknown property 'mode'",
794+
),
795+
(
796+
r#"git-exclude "target" executable=#true"#,
797+
"unknown property 'executable'",
798+
),
799+
] {
800+
let tmp = tempfile::tempdir().unwrap();
801+
let catalog = tmp.path().join("catalog");
802+
let workspace = tmp.path().join("workspace");
803+
fs::create_dir_all(&workspace).unwrap();
804+
write(&catalog.join("_templates/source"), "source\n");
805+
write(
806+
&catalog.join("agents/Silber/cos/agent.kdl"),
807+
agent_kdl(&workspace, &format!(" {directive}")),
808+
);
809+
let found = discover(&catalog);
810+
let error = parse_plan(&found.specs[0]).unwrap_err().to_string();
811+
assert!(error.contains(expected), "{error}");
812+
}
813+
}
814+
553815
#[test]
554816
fn untracked_and_non_git_targets_remain_materializable() {
555817
for git in [true, false] {

0 commit comments

Comments
 (0)