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
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:
@@ -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
@@ -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\""));
|
||||
|
||||
Reference in New Issue
Block a user