Unify release selection across clients
ci / test (push) Canceled after 0s
ci / fuzz-smoke (push) Canceled after 0s
ci / macos-client (macos-aarch64, macos-14) (push) Canceled after 0s
ci / macos-client (macos-x86_64, macos-13) (push) Canceled after 0s
ci / windows-client (push) Canceled after 0s
ci / package-release (linux-x86_64, ubuntu-latest, , , ) (push) Canceled after 0s
ci / package-release (macos-aarch64, macos-14, , , ) (push) Canceled after 0s
ci / package-release (macos-x86_64, macos-13, , , ) (push) Canceled after 0s
ci / package-release (windows-aarch64, windows-latest, aarch64, windows, aarch64-pc-windows-msvc) (push) Canceled after 0s
ci / package-release (windows-x86_64, windows-latest, , , ) (push) Canceled after 0s
ci / remote-bench (push) Canceled after 0s
ci / publish-gitea-release (push) Canceled after 0s

This commit is contained in:
DuProcess
2026-07-17 18:58:58 -04:00
parent 89b81d73b1
commit 7f8d5711ea
7 changed files with 641 additions and 439 deletions
+265
View File
@@ -0,0 +1,265 @@
#![cfg(unix)]
use sha2::{Digest, Sha256};
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
const VERSION: &str = "9.8.7-rc3";
fn write_executable(path: &Path, body: &str) {
fs::write(path, body).unwrap();
let mut permissions = fs::metadata(path).unwrap().permissions();
permissions.set_mode(0o755);
fs::set_permissions(path, permissions).unwrap();
}
struct InstallerFixture {
root: TempDir,
fake_bin: PathBuf,
archive: PathBuf,
checksum: PathBuf,
curl_log: PathBuf,
git_log: PathBuf,
version: String,
}
impl InstallerFixture {
fn new(version: &str) -> Self {
let root = tempfile::tempdir().unwrap();
let fake_bin = root.path().join("fake-bin");
let stage = root.path().join("stage/dosh");
fs::create_dir_all(fake_bin.as_path()).unwrap();
fs::create_dir_all(stage.join("bin")).unwrap();
fs::write(stage.join("VERSION"), format!("{version}\n")).unwrap();
write_executable(
&stage.join("bin/dosh-client"),
"#!/bin/sh\nprintf 'fixture dosh\\n'\n",
);
let archive = root.path().join("dosh-macos-aarch64.tar.gz");
let status = Command::new("tar")
.args(["-C", root.path().join("stage").to_str().unwrap(), "-czf"])
.arg(&archive)
.arg("dosh")
.status()
.unwrap();
assert!(status.success());
let digest = Sha256::digest(fs::read(&archive).unwrap());
let checksum = root.path().join("dosh-macos-aarch64.tar.gz.sha256");
fs::write(
&checksum,
format!("{digest:x} dosh-macos-aarch64.tar.gz\n"),
)
.unwrap();
write_executable(
&fake_bin.join("uname"),
"#!/bin/sh\ncase \"${1:-}\" in -s) echo Darwin;; -m) echo arm64;; *) echo Darwin;; esac\n",
);
let curl_log = root.path().join("curl.log");
let git_log = root.path().join("git.log");
write_executable(
&fake_bin.join("curl"),
r#"#!/bin/sh
printf '%s\n' "$*" >>"$TEST_CURL_LOG"
out=
previous=
url=
for arg in "$@"; do
if [ "$previous" = "-o" ]; then out="$arg"; fi
previous="$arg"
case "$arg" in http://*|https://*) url="$arg";; esac
done
case "$url" in
*/raw/branch/main/Cargo.toml)
printf '[package]\nname = "dosh"\nversion = "%s"\n' "$TEST_RELEASE_VERSION"
;;
*.sha256)
cp "$TEST_ARCHIVE_CHECKSUM" "$out"
;;
*/releases/download/*)
[ "${TEST_FAIL_ARCHIVE:-0}" = 1 ] && exit 22
cp "$TEST_ARCHIVE" "$out"
;;
*) exit 22;;
esac
"#,
);
Self {
root,
fake_bin,
archive,
checksum,
curl_log,
git_log,
version: version.to_string(),
}
}
fn command(&self, cwd: &Path) -> Command {
let mut command = Command::new("sh");
command
.arg(format!("{}/install.sh", env!("CARGO_MANIFEST_DIR")))
.args([
"client",
"--repo",
"https://example.invalid/Palav/dosh.git",
"--prefix",
])
.arg(self.root.path().join("prefix"))
.current_dir(cwd)
.env("HOME", self.root.path().join("home"))
.env(
"PATH",
format!(
"{}:{}",
self.fake_bin.display(),
std::env::var("PATH").unwrap_or_default()
),
)
.env("DOSH_BINARY_REQUIRED", "1")
.env("TEST_RELEASE_VERSION", &self.version)
.env("TEST_ARCHIVE", &self.archive)
.env("TEST_ARCHIVE_CHECKSUM", &self.checksum)
.env("TEST_CURL_LOG", &self.curl_log)
.env("TEST_GIT_LOG", &self.git_log);
command
}
fn installed_client(&self) -> PathBuf {
self.root.path().join("prefix/bin/dosh-client")
}
}
#[test]
fn unix_installer_uses_repository_version_even_inside_an_unrelated_rust_project() {
let fixture = InstallerFixture::new(VERSION);
let unrelated = fixture.root.path().join("unrelated");
fs::create_dir_all(&unrelated).unwrap();
fs::write(
unrelated.join("Cargo.toml"),
"[package]\nname = \"not-dosh\"\nversion = \"99.0.0\"\n",
)
.unwrap();
let output = fixture.command(&unrelated).output().unwrap();
assert!(
output.status.success(),
"installer failed: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(fixture.installed_client().is_file());
let requests = fs::read_to_string(&fixture.curl_log).unwrap();
assert!(requests.contains("/raw/branch/main/Cargo.toml"));
assert!(requests.contains(&format!(
"/releases/download/v{VERSION}/dosh-macos-aarch64.tar.gz"
)));
assert!(!requests.contains("/releases/latest"));
assert!(!requests.contains("99.0.0"));
}
#[test]
fn unix_installer_source_fallback_checks_out_the_same_release_tag() {
let fixture = InstallerFixture::new(VERSION);
write_executable(
&fixture.fake_bin.join("git"),
r#"#!/bin/sh
printf '%s\n' "$*" >>"$TEST_GIT_LOG"
if [ "${1:-}" = clone ]; then
for destination in "$@"; do :; done
mkdir -p "$destination/.git"
fi
"#,
);
write_executable(
&fixture.fake_bin.join("cargo"),
"#!/bin/sh\nmkdir -p target/release\nprintf '#!/bin/sh\\n' >target/release/dosh-client\nchmod +x target/release/dosh-client\n",
);
let cwd = fixture.root.path().join("plain");
fs::create_dir_all(&cwd).unwrap();
let mut command = fixture.command(&cwd);
command
.env("DOSH_BINARY_REQUIRED", "0")
.env("TEST_FAIL_ARCHIVE", "1");
let output = command.output().unwrap();
assert!(
output.status.success(),
"installer failed: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(fixture.installed_client().is_file());
let git = fs::read_to_string(&fixture.git_log).unwrap();
assert!(git.contains(&format!("clone --depth 1 --branch v{VERSION}")));
assert!(!git.contains("--branch main"));
}
#[test]
fn unix_installer_resolves_a_stable_repository_version_without_latest_redirects() {
let version = "1.2.3";
let fixture = InstallerFixture::new(version);
let cwd = fixture.root.path().join("plain");
fs::create_dir_all(&cwd).unwrap();
let output = fixture.command(&cwd).output().unwrap();
assert!(
output.status.success(),
"installer failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let requests = fs::read_to_string(&fixture.curl_log).unwrap();
assert!(requests.contains(&format!(
"/releases/download/v{version}/dosh-macos-aarch64.tar.gz"
)));
assert!(!requests.contains("/releases/latest"));
}
#[test]
fn unix_installer_repairs_a_legacy_updater_that_requests_its_old_tag() {
let fixture = InstallerFixture::new(VERSION);
let cwd = fixture.root.path().join("plain");
fs::create_dir_all(&cwd).unwrap();
let mut command = fixture.command(&cwd);
command.env("DOSH_BINARY_VERSION", "v0.1.17");
let output = command.output().unwrap();
assert!(
output.status.success(),
"installer failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let requests = fs::read_to_string(&fixture.curl_log).unwrap();
assert!(requests.contains(&format!(
"/releases/download/v{VERSION}/dosh-macos-aarch64.tar.gz"
)));
assert!(!requests.contains("/releases/download/v0.1.17/"));
}
#[test]
fn unix_installer_honors_an_explicit_exact_release_pin() {
let pinned = "0.1.17";
let fixture = InstallerFixture::new(pinned);
let cwd = fixture.root.path().join("plain");
fs::create_dir_all(&cwd).unwrap();
let mut command = fixture.command(&cwd);
command
.env("TEST_RELEASE_VERSION", VERSION)
.env("DOSH_BINARY_VERSION", format!("v{pinned}"))
.env("DOSH_BINARY_EXACT", "1");
let output = command.output().unwrap();
assert!(
output.status.success(),
"installer failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let requests = fs::read_to_string(&fixture.curl_log).unwrap();
assert!(requests.contains(&format!(
"/releases/download/v{pinned}/dosh-macos-aarch64.tar.gz"
)));
assert!(!requests.contains(&format!("/releases/download/v{VERSION}/")));
}
+26 -23
View File
@@ -94,6 +94,8 @@ fn release_gates_test_all_targets() {
assert!(windows_client.contains("runs-on: windows-latest"));
assert!(windows_client.contains("name: Test Windows client"));
assert!(windows_client.contains("name: PowerShell syntax check"));
assert!(windows_client.contains("name: Windows installer runtime test"));
assert!(windows_client.contains("run: scripts/test-install-windows.ps1"));
assert!(windows_client.contains("shell: pwsh"));
assert!(
windows_client
@@ -138,28 +140,24 @@ fn one_dot_zero_gates_default_to_full_length_soaks() {
}
#[test]
fn installers_skip_stale_latest_release_prebuilts() {
fn installers_resolve_the_repository_declared_release_without_stable_redirects() {
let install = include_str!("../install.sh");
assert!(install.contains("latest_release_is_stale"));
assert!(install.contains("current_source_version"));
assert!(install.contains("latest release $latest is older than source $current"));
assert!(install.contains("version_prerelease"));
assert!(install.contains("prerelease_less_than"));
assert!(
install.contains("if latest_release_is_stale; then"),
"unix installer must skip stale latest before downloading a prebuilt"
);
assert!(install.contains("release_source_ref"));
assert!(install.contains("printf 'v%s\\n' \"${current#v}\""));
assert!(install.contains("DOSH_BINARY_EXACT=1"));
assert!(!install.contains("/releases/latest"));
assert!(install.contains("resolved_source_ref=\"$(release_source_ref || true)\""));
assert!(install.contains("checkout -q --detach FETCH_HEAD"));
let ps1 = include_str!("../install.ps1");
assert!(ps1.contains("Latest-ReleaseIsStale"));
assert!(ps1.contains("Current-SourceVersion"));
assert!(ps1.contains("latest release $latestVersion is older than source $current"));
assert!(ps1.contains("Version-Prerelease"));
assert!(ps1.contains("Compare-Prerelease"));
assert!(
ps1.contains("if (Latest-ReleaseIsStale)"),
"windows installer must skip stale latest before downloading a prebuilt"
);
assert!(ps1.contains("Source-ReleaseRef"));
assert!(ps1.contains("\"v$($current.TrimStart('v'))\""));
assert!(ps1.contains("DOSH_BINARY_EXACT"));
assert!(!ps1.contains("/releases/latest"));
assert!(ps1.contains("$script:ResolvedSourceRef = Source-ReleaseRef"));
assert!(ps1.contains("checkout -q --detach FETCH_HEAD"));
}
#[test]
@@ -198,15 +196,16 @@ fn windows_installer_reuses_persistent_source_update_cache() {
assert!(ps1.contains("$full -eq $localAppData -or $full -eq $localAppDataDosh"));
assert!(ps1.contains("$sourceCache = Assert-SafeUpdateCache $UpdateCache"));
assert!(ps1.contains(
"$fetchArgs = @(\"-C\", $sourceCache, \"fetch\", \"--depth\", \"1\", \"origin\", \"main\")"
"$fetchArgs = @(\"-C\", $sourceCache, \"fetch\", \"--depth\", \"1\", \"origin\", $sourceRef)"
));
assert!(ps1.contains("$fetchArgs = @(\"-C\", $sourceCache, \"fetch\", \"-q\", \"--depth\", \"1\", \"origin\", \"main\")"));
assert!(ps1.contains("$fetchArgs = @(\"-C\", $sourceCache, \"fetch\", \"-q\", \"--depth\", \"1\", \"origin\", $sourceRef)"));
assert!(ps1.contains("git @fetchArgs"));
assert!(ps1.contains("git -C $sourceCache checkout -q -B main FETCH_HEAD"));
assert!(ps1.contains("git -C $sourceCache checkout -q --detach FETCH_HEAD"));
assert!(ps1.contains(
"$cloneArgs = @(\"clone\", \"--depth\", \"1\", \"--branch\", \"main\", $Repo, $sourceCache)"
"$cloneArgs = @(\"clone\", \"--depth\", \"1\", \"--branch\", $sourceRef, $Repo, $sourceCache)"
));
assert!(ps1.contains("$cloneArgs = @(\"clone\", \"-q\", \"--depth\", \"1\", \"--branch\", \"main\", $Repo, $sourceCache)"));
assert!(ps1.contains("$cloneArgs = @(\"clone\", \"-q\", \"--depth\", \"1\", \"--branch\", $sourceRef, $Repo, $sourceCache)"));
assert!(ps1.contains("git @cloneArgs | Out-Null"));
assert!(
!ps1.contains("git clone --depth 1 $Repo $tmp"),
@@ -218,8 +217,12 @@ fn windows_installer_reuses_persistent_source_update_cache() {
fn windows_quiet_source_updates_silence_git_like_unix() {
let install = include_str!("../install.sh");
let ps1 = include_str!("../install.ps1");
assert!(install.contains("git -C \"$update_cache\" fetch -q --depth 1 origin main"));
assert!(install.contains("git clone -q --depth 1 --branch main \"$repo\" \"$update_cache\""));
assert!(install.contains("git -C \"$update_cache\" fetch -q --depth 1 origin \"$source_ref\""));
assert!(
install.contains(
"git clone -q --depth 1 --branch \"$source_ref\" \"$repo\" \"$update_cache\""
)
);
assert!(ps1.contains("if ($Quiet)"));
assert!(ps1.contains("\"fetch\", \"-q\", \"--depth\""));
assert!(ps1.contains("\"clone\", \"-q\", \"--depth\""));