use std::fs;
use crate::support::fixtures::*;
use crate::support::shepherd::*;
const ROLLED_BACK_EXIT: u8 = 12;
#[test]
fn a_real_deploy_swaps_reloads_and_verifies() {
let shepherd = Shepherd::new();
let origin = origin_with_app(shepherd.home(), "v1", Readiness::Probe, "");
let first = build_tree(shepherd.home(), "web", origin.path());
write_state(shepherd.home(), "web", origin.path(), &first, "probed");
register_web(&shepherd, Readiness::Probe, "");
wait_until("the first release to come online", || {
described_pid(&shepherd, "web").is_some()
});
let out_log = shepherd.home().join("logs/web-0-out.log");
wait_until("v1 to have run at least once", || {
fs::read_to_string(&out_log)
.unwrap_or_default()
.contains("v1")
});
write_run_script(origin.path(), "v2");
git(origin.path(), &["add", "."]);
git(origin.path(), &["commit", "-q", "-m", "v2"]);
let second = head_of(origin.path());
let output = shepherd.deploy("web");
assert!(
output.status.success(),
"deploy failed: {}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains(&second),
"the deploy should report the new sha: {stdout}"
);
let current = fs::read_link(shepherd.home().join("deploy/web/current")).expect("current");
assert_eq!(
current,
shepherd.home().join("deploy/web/releases").join(&second)
);
let state = fs::read_to_string(shepherd.home().join("deploy/web/deploy.toml")).expect("state");
assert!(state.contains(&second), "{state}");
wait_until("v2 to have run", || {
fs::read_to_string(&out_log)
.unwrap_or_default()
.contains("v2")
});
}
#[test]
fn a_failing_build_leaves_the_previous_release_serving() {
let shepherd = Shepherd::new();
let origin = origin_with_app(shepherd.home(), "v1", Readiness::Probe, "");
let first = build_tree(shepherd.home(), "web", origin.path());
write_state(shepherd.home(), "web", origin.path(), &first, "probed");
register_web(&shepherd, Readiness::Probe, "");
wait_until("the first release to come online", || {
described_pid(&shepherd, "web").is_some()
});
let live_pid = described_pid(&shepherd, "web").expect("a pid");
write_committed_app(
origin.path(),
shepherd.home(),
Readiness::Probe,
"",
"\n[dog.deploy.build]\ncommand = 'exit 3'\n",
);
git(origin.path(), &["add", "."]);
git(origin.path(), &["commit", "-q", "-m", "broken"]);
let output = shepherd.deploy("web");
assert!(
!output.status.success(),
"a failing build must not be reported as a successful deploy"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("build exited with status 3"),
"the failure should name the build's own exit status: {stderr}"
);
let current = fs::read_link(shepherd.home().join("deploy/web/current")).expect("current");
assert_eq!(
current,
shepherd.home().join("deploy/web/releases").join(&first)
);
let state = fs::read_to_string(shepherd.home().join("deploy/web/deploy.toml")).expect("state");
assert!(state.contains(&first), "{state}");
let pid_after = described_pid(&shepherd, "web").expect("still running");
assert_eq!(
live_pid, pid_after,
"a failed build must never trigger a reload of the running sheep"
);
}
#[test]
fn a_release_that_cannot_come_up_is_rolled_back_and_the_old_release_serves() {
let shepherd = Shepherd::new();
let origin = origin_with_app(shepherd.home(), "v1", Readiness::Probe, "");
let first = build_tree(shepherd.home(), "web", origin.path());
write_state(shepherd.home(), "web", origin.path(), &first, "alive");
register_web(&shepherd, Readiness::Probe, "");
wait_until("the first release to come online", || {
described_pid(&shepherd, "web").is_some()
});
let out_log = shepherd.home().join("logs/web-0-out.log");
wait_until("v1 to have run", || {
last_line(&out_log).as_deref() == Some("v1")
});
write_run_script(origin.path(), "v2");
git(origin.path(), &["rm", "-q", "ready-marker"]);
git(origin.path(), &["add", "-A"]);
git(origin.path(), &["commit", "-q", "-m", "v2, never ready"]);
let second = head_of(origin.path());
let output = shepherd.deploy("web");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stdout.contains("rolled back"),
"a release that never came up must be reported as rolled back: {stdout}{stderr}"
);
assert!(
!stdout.contains(&format!("deployed {second}")),
"the broken release must never be reported deployed: {stdout}"
);
assert_eq!(
output.status.code(),
Some(i32::from(ROLLED_BACK_EXIT)),
"a rolled-back deploy must not report success: {stdout}{stderr}"
);
let current = fs::read_link(shepherd.home().join("deploy/web/current")).expect("current");
assert_eq!(
current,
shepherd.home().join("deploy/web/releases").join(&first),
"current must be back on the release that works"
);
let state = fs::read_to_string(shepherd.home().join("deploy/web/deploy.toml")).expect("state");
assert!(
state.contains(&format!("deployed = \"{first}\"")),
"{state}"
);
assert!(
!state.contains(&format!("deployed = \"{second}\"")),
"{state}"
);
assert!(state.contains(&format!("failed = \"{second}\"")), "{state}");
wait_until("the old release to be serving again", || {
last_line(&out_log).as_deref() == Some("v1")
});
assert!(
described_pid(&shepherd, "web").is_some(),
"the sheep must still be running after a rollback"
);
}