docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment guidelines: delete redundant restatements, decorative banners, change narration, and end-of-line comments; keep and tighten the crucial ones (invariants, bug rationale, SAFETY blocks, ported-source attribution). No functional code changed. Every edit is proven comment-only against the prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a separate doctest-fence check. Where removing a comment made rustfmt or clippy want to re-lay-out adjacent code, the minimal triggering comment is restored so code tokens stay byte-identical. Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy --workspace --all-targets (0 warnings). Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for these guidelines (flags banners, end-of-line comments, change narration, and commented-out code).
This commit is contained in:
@@ -1969,9 +1969,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// needs_update — channel/upgrade/downgrade semantics
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_needs_update_matrix() {
|
||||
@@ -2107,9 +2105,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// installer_allows_downgrade
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_installer_allows_downgrade_internal_only() {
|
||||
@@ -2120,9 +2116,7 @@ mod tests {
|
||||
assert!(!installer_allows_downgrade("homebrew"));
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// atomic_symlink_swap
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
@@ -2360,9 +2354,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// cleanup_old_downloads
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Backdate a file's mtime past [`STALE_TMP_AGE`] so cleanup treats it
|
||||
/// as an abandoned download / genuinely old binary.
|
||||
@@ -2609,9 +2601,7 @@ mod tests {
|
||||
assert!(!d.join("kigi-0.1.139-macos-aarch64").exists());
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// reinstall_hint / manual_install_cmd
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_reinstall_hint_points_at_repo_install_script() {
|
||||
@@ -2634,9 +2624,7 @@ mod tests {
|
||||
assert_eq!(reinstall_hint(""), hint);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Asset naming: targets → release asset names
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
@@ -2687,9 +2675,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// SHA256SUMS parsing
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_expected_sha256_for_parses_sha256sum_format() {
|
||||
@@ -2753,9 +2739,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Archive extraction (Unix: tar.gz)
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn make_tar_gz(entries: &[(&str, &[u8])]) -> Vec<u8> {
|
||||
@@ -2813,9 +2797,7 @@ mod tests {
|
||||
assert!(extract_kigi_binary(&archive, &out).await.is_err());
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// UpdateStatus serialization (camelCase contract for --json clients)
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn make_status() -> UpdateStatus {
|
||||
UpdateStatus {
|
||||
@@ -2907,9 +2889,7 @@ mod tests {
|
||||
assert!(!json.contains('\n'), "must be single line: {json}");
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// print_update_status — both code paths must not panic or error.
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_print_update_status_all_shapes_return_ok() {
|
||||
@@ -2943,17 +2923,16 @@ mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// UpdateRunMode
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_update_run_mode_is_copy_clone_debug() {
|
||||
// The ergonomic Copy/Clone/Debug derives must not regress: we pass
|
||||
// `run_mode` by value through several layers.
|
||||
let m1 = UpdateRunMode::Blocking;
|
||||
let m2 = m1; // Copy
|
||||
let m3 = m1; // Copy again, m1 not moved
|
||||
let m2 = m1;
|
||||
// Copy again, m1 not moved
|
||||
let m3 = m1;
|
||||
assert!(matches!(m1, UpdateRunMode::Blocking));
|
||||
assert!(matches!(m2, UpdateRunMode::Blocking));
|
||||
assert!(matches!(m3, UpdateRunMode::Blocking));
|
||||
@@ -2961,9 +2940,7 @@ mod tests {
|
||||
let _ = format!("{:?}", UpdateRunMode::NonBlocking);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Constants — lock them in so silent renames are caught.
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_user_facing_constants_are_stable() {
|
||||
@@ -2984,14 +2961,12 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// env_installer — env-var based, must run serially.
|
||||
//
|
||||
// Resolution order (matches function body):
|
||||
// 1. KIGI_INSTALLER (internal; anything else → None)
|
||||
// 2. KIGI_MANAGED_BY_INTERNAL → internal
|
||||
// 3. None
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Snapshot every installer-related env var so the test can clear them
|
||||
/// at start and restore them at end.
|
||||
@@ -3072,9 +3047,7 @@ mod tests {
|
||||
assert_eq!(env_installer(), Some("internal"));
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// windows_replace_exe — runs only on Windows CI
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tokio::test]
|
||||
|
||||
@@ -14,14 +14,12 @@ use crate::version::{
|
||||
use kigi_shell::util::config;
|
||||
use tracing::{info, warn};
|
||||
|
||||
/// Result of comparing the running binary against a configured floor.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum MinimumVersionDecision {
|
||||
Allow,
|
||||
BelowMinimum { current: String, minimum: String },
|
||||
}
|
||||
|
||||
/// Outcome of a successful enforcement pass.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum EnforcementOutcome {
|
||||
Allowed,
|
||||
@@ -34,7 +32,6 @@ enum EnforcementOutcome {
|
||||
/// telemetry can distinguish them.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum MinimumVersionError {
|
||||
/// `source` chains via `Error::source()`; omitted from `Display`.
|
||||
#[error(
|
||||
"The minimum version \"{value}\" in your Kigi configuration \
|
||||
isn't a valid version number. Update `cli.minimum_version` and try again."
|
||||
@@ -49,7 +46,6 @@ pub(crate) enum MinimumVersionError {
|
||||
Run `kigi update` to install version {minimum} or later."
|
||||
)]
|
||||
AutoUpdateDisabled { current: String, minimum: String },
|
||||
/// No installer backend detected.
|
||||
#[error(
|
||||
"This version of Kigi ({current}) is no longer supported. \
|
||||
Run `kigi update` to install version {minimum} or later."
|
||||
@@ -160,7 +156,6 @@ pub(crate) fn apply_floor(target: &str) -> Result<String, MinimumVersionError> {
|
||||
apply_floor_inner(target, floor.as_deref())
|
||||
}
|
||||
|
||||
/// Adapts `config::resolve_minimum_version`'s error shape into ours.
|
||||
fn resolve_floor_or_error() -> Result<Option<String>, MinimumVersionError> {
|
||||
config::resolve_minimum_version()
|
||||
.map_err(|(value, source)| MinimumVersionError::InvalidMinimum { value, source })
|
||||
@@ -233,7 +228,6 @@ async fn enforce_minimum_version(
|
||||
});
|
||||
}
|
||||
|
||||
// Post-install: pass None for stable_version (same rationale as run_update).
|
||||
write_version_cache(&target, None).await;
|
||||
|
||||
// Stale channel pointer or partial install can leave us below the floor;
|
||||
@@ -299,7 +293,6 @@ mod tests {
|
||||
fn evaluate_minimum_version_decisions() {
|
||||
use MinimumVersionDecision::{Allow, BelowMinimum};
|
||||
|
||||
// Allow: floor unset (None / empty / whitespace) or satisfied (equal / above).
|
||||
assert_eq!(evaluate_minimum_version("0.1.100", None).unwrap(), Allow);
|
||||
assert_eq!(
|
||||
evaluate_minimum_version("0.1.100", Some("")).unwrap(),
|
||||
@@ -318,13 +311,11 @@ mod tests {
|
||||
Allow
|
||||
);
|
||||
|
||||
// BelowMinimum: current < floor.
|
||||
assert!(matches!(
|
||||
evaluate_minimum_version("0.1.99", Some("0.1.100")).unwrap(),
|
||||
BelowMinimum { .. }
|
||||
));
|
||||
|
||||
// InvalidMinimum: unparseable floor (admin typo).
|
||||
assert!(matches!(
|
||||
evaluate_minimum_version("0.1.100", Some("not-a-version")),
|
||||
Err(MinimumVersionError::InvalidMinimum { .. })
|
||||
@@ -333,8 +324,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn pick_target_returns_max_of_latest_and_minimum() {
|
||||
// The `None` branch is only reachable here — apply_floor always
|
||||
// passes `Some(target)`. Production hits it on fetch failure.
|
||||
// `None` stands in for a failed `fetch_latest_version`.
|
||||
assert_eq!(pick_target_version(Some("0.1.200"), "0.1.150"), "0.1.200");
|
||||
assert_eq!(pick_target_version(Some("0.1.140"), "0.1.150"), "0.1.150");
|
||||
assert_eq!(pick_target_version(None, "0.1.150"), "0.1.150");
|
||||
@@ -342,7 +332,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn install_target_helpers_consult_floor() {
|
||||
// check_install_target rejects below-floor targets.
|
||||
assert!(check_install_target_inner("0.1.50", None).is_ok());
|
||||
assert!(check_install_target_inner("0.1.150", Some("0.1.100")).is_ok());
|
||||
assert!(matches!(
|
||||
@@ -350,7 +339,6 @@ mod tests {
|
||||
MinimumVersionError::TargetBelowFloor { .. }
|
||||
));
|
||||
|
||||
// apply_floor bumps below-floor targets up.
|
||||
assert_eq!(apply_floor_inner("0.1.50", None).unwrap(), "0.1.50");
|
||||
assert_eq!(
|
||||
apply_floor_inner("0.1.200", Some("0.1.100")).unwrap(),
|
||||
|
||||
@@ -45,9 +45,7 @@ impl UpdateConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// GitHub Releases API wire shape
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// One downloadable asset attached to a GitHub release.
|
||||
///
|
||||
@@ -238,9 +236,7 @@ pub(crate) async fn try_fetch_stable_version() -> Option<String> {
|
||||
.unwrap_or(None)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Version cache (~/.kigi/version.json)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, serde::Serialize, Deserialize)]
|
||||
struct VersionCache {
|
||||
@@ -486,12 +482,17 @@ mod tests {
|
||||
// would make an alpha install masquerade as the release and
|
||||
// mask alpha → stable updates.
|
||||
("kigi-0.1.220-alpha.4-linux-x86_64", Some("0.1.220-alpha.4")),
|
||||
("kigi-0.1.220-alpha.4", Some("0.1.220-alpha.4")), // no platform suffix
|
||||
("kigi-garbage-darwin-arm64", None), // unparseable version
|
||||
("kigi-0.2.46", Some("0.2.46")), // no platform suffix
|
||||
("other-0.2.46-darwin-arm64", None), // wrong prefix
|
||||
("kigi-latest", None), // symlink alias, not a version
|
||||
("kigi", None), // bare name
|
||||
// no platform suffix
|
||||
("kigi-0.1.220-alpha.4", Some("0.1.220-alpha.4")),
|
||||
// unparseable version
|
||||
("kigi-garbage-darwin-arm64", None),
|
||||
// no platform suffix
|
||||
("kigi-0.2.46", Some("0.2.46")),
|
||||
// wrong prefix
|
||||
("other-0.2.46-darwin-arm64", None),
|
||||
// symlink alias, not a version
|
||||
("kigi-latest", None),
|
||||
("kigi", None),
|
||||
("", None),
|
||||
// Release archives must never parse as versioned binaries, or
|
||||
// cleanup would treat them as installable versions.
|
||||
@@ -520,13 +521,11 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// GitHub release JSON — wire-shape invariants
|
||||
//
|
||||
// Fixtures mirror the real GitHub REST API response for
|
||||
// GET /repos/{owner}/{repo}/releases/latest:
|
||||
// https://docs.github.com/en/rest/releases/releases#get-the-latest-release
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_release_json_parses_github_wire_shape() {
|
||||
@@ -611,36 +610,49 @@ mod tests {
|
||||
assert!(serde_json::from_str::<Release>(r#"{"assets":[]}"#).is_err());
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// derive_channel — invariant matrix
|
||||
//
|
||||
// Tests the pure comparison logic that determines [alpha] vs [stable].
|
||||
// Covers current 0.1.X-alpha.N, future 0.2.X, edge cases, and errors.
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_derive_channel_matrix() {
|
||||
// (current, stable_pointer, expected_channel)
|
||||
let cases: &[(&str, &str, Option<&str>)] = &[
|
||||
// ── Current 0.1.X workflow ──
|
||||
("0.1.220-alpha.2", "0.1.219", Some("alpha")), // alpha ahead of stable
|
||||
("0.1.219", "0.1.219", Some("stable")), // stable user on latest
|
||||
("0.1.218", "0.1.219", Some("stable")), // stable user behind latest
|
||||
("0.1.220-alpha.2", "0.1.220-alpha.2", Some("stable")), // pointer matches exactly
|
||||
("0.1.220-alpha.2", "0.1.220", Some("stable")), // semver: release > pre-release
|
||||
// ── Future 0.2.X workflow ──
|
||||
("0.2.5", "0.2.3", Some("alpha")), // alpha ahead of stable
|
||||
("0.2.5", "0.2.5", Some("stable")), // promoted to stable
|
||||
("0.2.3", "0.2.5", Some("stable")), // behind stable
|
||||
("0.2.0", "0.2.0", Some("stable")), // first release, both 0.2.0
|
||||
// ── Cross-regime upgrade ──
|
||||
("0.2.0", "0.1.219", Some("alpha")), // new regime ahead of old stable
|
||||
("0.1.220-alpha.2", "0.2.0", Some("stable")), // old pre-release < new stable
|
||||
// ── Error cases ──
|
||||
("garbage", "0.1.219", None), // unparseable current
|
||||
("0.1.219", "garbage", None), // unparseable stable
|
||||
("", "0.1.219", None), // empty current
|
||||
("0.1.219", "", None), // empty stable
|
||||
// Current 0.1.X workflow
|
||||
// alpha ahead of stable
|
||||
("0.1.220-alpha.2", "0.1.219", Some("alpha")),
|
||||
// stable user on latest
|
||||
("0.1.219", "0.1.219", Some("stable")),
|
||||
// stable user behind latest
|
||||
("0.1.218", "0.1.219", Some("stable")),
|
||||
// pointer matches exactly
|
||||
("0.1.220-alpha.2", "0.1.220-alpha.2", Some("stable")),
|
||||
// semver: release > pre-release
|
||||
("0.1.220-alpha.2", "0.1.220", Some("stable")),
|
||||
// Future 0.2.X workflow
|
||||
// alpha ahead of stable
|
||||
("0.2.5", "0.2.3", Some("alpha")),
|
||||
// promoted to stable
|
||||
("0.2.5", "0.2.5", Some("stable")),
|
||||
// behind stable
|
||||
("0.2.3", "0.2.5", Some("stable")),
|
||||
// first release, both 0.2.0
|
||||
("0.2.0", "0.2.0", Some("stable")),
|
||||
// Cross-regime upgrade
|
||||
// new regime ahead of old stable
|
||||
("0.2.0", "0.1.219", Some("alpha")),
|
||||
// old pre-release < new stable
|
||||
("0.1.220-alpha.2", "0.2.0", Some("stable")),
|
||||
// Error cases
|
||||
// unparseable current
|
||||
("garbage", "0.1.219", None),
|
||||
// unparseable stable
|
||||
("0.1.219", "garbage", None),
|
||||
// empty current
|
||||
("", "0.1.219", None),
|
||||
// empty stable
|
||||
("0.1.219", "", None),
|
||||
];
|
||||
|
||||
for (current, stable, expected) in cases {
|
||||
@@ -653,9 +665,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// VersionCache JSON shape — backward compatibility invariants
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_version_json_backward_compat() {
|
||||
@@ -691,9 +701,7 @@ mod tests {
|
||||
assert!(serde_json::from_str::<VersionCache>(missing).is_err());
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// is_fresh — TTL boundary invariants
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_is_fresh_ttl_boundaries() {
|
||||
@@ -722,9 +730,7 @@ mod tests {
|
||||
assert!(!bad.is_fresh(now, Duration::from_secs(60)));
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// UpdateConfig defaults
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_update_config_default_channel_is_stable() {
|
||||
|
||||
@@ -277,7 +277,7 @@ fn handle_connection(
|
||||
let range = parse_range(&request);
|
||||
let addr = stream.local_addr().unwrap();
|
||||
|
||||
// ── Metadata routes ─────────────────────────────────────────────────────
|
||||
// Metadata routes
|
||||
if path == "/releases/latest" {
|
||||
let latest = state.lock().unwrap().latest.clone();
|
||||
let body = release_json(&addr, &latest);
|
||||
@@ -290,7 +290,7 @@ fn handle_connection(
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Asset routes: /dl/{version}/{name} ──────────────────────────────────
|
||||
// Asset routes: /dl/{version}/{name}
|
||||
let Some(rest) = path.strip_prefix("/dl/") else {
|
||||
write_simple_response(&mut stream, "404 Not Found", b"not found", is_head);
|
||||
return;
|
||||
@@ -330,7 +330,7 @@ fn handle_connection(
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Archive body with corruption modes ──────────────────────────────────
|
||||
// Archive body with corruption modes
|
||||
// Count only body-serving GETs; the parallel path's HEAD probe is excluded.
|
||||
if !is_head {
|
||||
gets.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![allow(dead_code)] // each test binary uses a different subset
|
||||
// each test binary uses a different subset
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[cfg(unix)]
|
||||
pub mod artifact_server;
|
||||
@@ -31,9 +32,7 @@ use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// KIGI_SHARE_DIR isolation
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns a process-wide test `KIGI_SHARE_DIR`, initialized exactly once per test
|
||||
/// binary. Once initialized, `kigi_config::kigi_home()` will resolve to
|
||||
@@ -92,9 +91,7 @@ pub fn set_update_base(base: &str) {
|
||||
unsafe { std::env::set_var(kigi_env::UPDATE_BASE_URL_ENV, base) };
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Install-test fixtures
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Host `{os}-{arch}` string matching the versioned binary naming scheme
|
||||
/// (`kigi-{version}-{platform}`).
|
||||
@@ -193,7 +190,6 @@ pub fn backdate_downloads() {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// GitHub Releases fixtures
|
||||
//
|
||||
// Wire shapes mirror the real GitHub REST API
|
||||
@@ -203,7 +199,6 @@ pub fn backdate_downloads() {
|
||||
// GET /repos/{o}/{r}/releases → array of release objects
|
||||
// Release object: {"tag_name":"v0.1.0","assets":[{"name":"...",
|
||||
// "browser_download_url":"..."}]}
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Build a tar.gz archive from `(name, bytes)` entries.
|
||||
#[cfg(unix)]
|
||||
@@ -299,9 +294,7 @@ pub async fn mount_latest(server: &wiremock::MockServer, version: &str) {
|
||||
.await;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// PATH-override fake binary (used by the install.sh harness)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// RAII guard that places a sh-script with name `name` at the head of `PATH`.
|
||||
/// Restores `PATH` on drop.
|
||||
|
||||
@@ -33,9 +33,7 @@ use common::{
|
||||
};
|
||||
use kigi_update::auto_update::install_internal_from_base;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Artifacts + fixtures
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A real executable whose ARCHIVE clears the 16 MiB parallel threshold (at
|
||||
/// least 2 chunks), so the parallel byte-range path is exercised. The shell
|
||||
@@ -174,9 +172,7 @@ async fn run_one(
|
||||
assert_invariant(home, &prev_good, &new_binary, expect);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Deterministic matrix — single-connection path (small archive)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
@@ -224,9 +220,7 @@ async fn blitz_single_connection_matrix() {
|
||||
run_one(&server, Mode::Full, "0.1.182", None).await;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Deterministic matrix — parallel byte-range path (>= 16 MiB archive)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
@@ -269,10 +263,8 @@ async fn blitz_parallel_path_matrix() {
|
||||
run_one(&server, Mode::Full, "0.1.182", None).await;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Checksum + smoke-test rejections keep previous-good, then recover WITHOUT
|
||||
// a reset in between.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
@@ -312,9 +304,7 @@ async fn smoke_and_checksum_failures_keep_previous_good_then_recover() {
|
||||
assert_invariant(home, &prev_good, &new_binary, Expect::NewBinary);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Bounded randomized fuzz (CI) + ignored stress (1e5+ iterations).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Cheap deterministic PRNG so the fuzz needs no extra dependency.
|
||||
struct Rng(u64);
|
||||
|
||||
@@ -31,9 +31,7 @@ fn base(server: &MockServer) -> String {
|
||||
format!("{}/releases", server.uri())
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Happy-path
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
@@ -207,9 +205,7 @@ async fn install_removes_legacy_links() {
|
||||
assert!(bin_dir.join("kigi").is_symlink(), "kigi link installed");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Failure paths
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
@@ -447,10 +443,8 @@ async fn install_swap_failure_leaves_prior_install_active() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Rollback semantics: pinned installs move DOWN as well as up (the release
|
||||
// channel is authoritative for the internal installer).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
@@ -479,9 +473,7 @@ async fn install_rollback_then_upgrade_sequence() {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Cleanup integration: install v1..v3, verify N-1 retention.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
|
||||
@@ -168,11 +168,9 @@ fn install_sh_happy_path_installs_versioned_binary_and_symlink() {
|
||||
"symlink must be relative (survives bind-mounted homes)"
|
||||
);
|
||||
|
||||
// The active link runs.
|
||||
let status = Command::new(&link).arg("--version").status().unwrap();
|
||||
assert!(status.success(), "installed kigi must run");
|
||||
|
||||
// Resolved the latest endpoint (no pinned version).
|
||||
assert!(
|
||||
fx.curl_log().contains("/latest"),
|
||||
"must resolve via /latest: {}",
|
||||
@@ -255,7 +253,6 @@ fn install_sh_fails_when_release_lacks_platform_asset() {
|
||||
return;
|
||||
}
|
||||
let fx = Fixture::new("0.1.5", &small_good_artifact(), None);
|
||||
// Rewrite release.json without the platform archive asset.
|
||||
let json = serde_json::json!({
|
||||
"tag_name": "v0.1.5",
|
||||
"assets": [
|
||||
@@ -282,9 +279,8 @@ fn install_sh_fails_when_archive_lacks_kigi_binary() {
|
||||
return;
|
||||
}
|
||||
let fx = Fixture::new("0.1.5", &small_good_artifact(), None);
|
||||
// Replace the archive with one that has no `kigi` entry; keep the
|
||||
// manifest consistent so the checksum gate passes and the extraction
|
||||
// check is what trips.
|
||||
// The manifest hash is kept consistent so the checksum gate passes and the
|
||||
// extraction check is what trips.
|
||||
let archive = common::make_tar_gz(&[("LICENSE", b"license only")]);
|
||||
std::fs::write(
|
||||
fx.dir.path().join("SHA256SUMS"),
|
||||
|
||||
@@ -29,9 +29,7 @@ fn reset() {
|
||||
reset_home();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// write_version_cache
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
@@ -130,10 +128,8 @@ async fn write_version_cache_records_recent_timestamp() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// is_version_cache_fresh — exercised via the public re-export. Each scenario
|
||||
// writes the file directly so we can control the timestamp.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Write a `KigiVersion`-shaped JSON file with an arbitrary timestamp.
|
||||
fn write_cache_with_timestamp(version: &str, ts: time::OffsetDateTime) {
|
||||
@@ -217,9 +213,7 @@ async fn version_cache_missing_file_is_not_fresh() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// version.json wire format — the on-disk file is read by every kigi launch.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
@@ -276,12 +270,10 @@ async fn write_version_cache_idempotent_for_same_version() {
|
||||
assert_eq!(v1["version"], "0.1.180");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// get_installed_kigi_version env override
|
||||
//
|
||||
// The function honors `KIGI_TEST_VERSION` for testing. We exercise it
|
||||
// via the public re-export only — no private items leaked.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Note: `get_installed_kigi_version` is not re-exported from `lib.rs`, but
|
||||
// it's `pub` from `version` module and accessible via `version::`.
|
||||
|
||||
@@ -26,9 +26,7 @@ fn tag_json(tag: &str) -> serde_json::Value {
|
||||
serde_json::json!({ "tag_name": tag, "draft": false, "prerelease": false, "assets": [] })
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Happy-path tests (fast, no retries triggered).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn latest_release_returns_version_on_success() {
|
||||
@@ -217,10 +215,8 @@ async fn base_url_trailing_slash_is_tolerated() {
|
||||
assert_eq!(release.version().unwrap(), "0.1.181");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Retry behavior — these tests intentionally exercise the 1s+2s+4s backoff,
|
||||
// Retry behavior — these tests deliberately exercise the 1s+2s+4s backoff,
|
||||
// so each takes up to ~7 seconds. They run in parallel.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn latest_release_retries_on_5xx_then_succeeds() {
|
||||
@@ -320,10 +316,8 @@ async fn latest_release_connection_refused_is_retried_and_returns_error() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// download_silent — same body shape as download_with_progress but no
|
||||
// progress bar to capture.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn download_silent_writes_body_to_dest() {
|
||||
@@ -540,10 +534,8 @@ async fn download_silent_to_nonexistent_parent_dir_fails() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// download_with_progress — same contract; covers the spinner path
|
||||
// (no Content-Length) and the progress-bar path (with Content-Length).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn download_with_progress_writes_body_with_content_length() {
|
||||
@@ -604,10 +596,8 @@ async fn download_with_progress_atomic_rename() {
|
||||
assert!(!dest.with_extension("tmp").exists());
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Parallel byte-range path — exercises the HEAD + 206 Partial Content code path
|
||||
// in download_silent / download_with_progress for files >= 16 MiB.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Wiremock responder for `GET` that honors `Range: bytes=A-B` with `206`.
|
||||
/// Without a Range header it returns the full body with `200`.
|
||||
|
||||
@@ -95,10 +95,8 @@ fn setup(server: &ArtifactServer, latest: &str, running: &str) {
|
||||
set_test_version(running);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Convergence: ensure_latest_on_disk downloads once, then every subsequent
|
||||
// pass (the leader's hourly re-entry) converges without re-downloading.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
@@ -207,9 +205,7 @@ async fn run_update_rolls_back_when_latest_moved_backwards() {
|
||||
assert_eq!(server.request_count(), 1);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Disk-version probe
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
@@ -288,9 +284,7 @@ async fn ensure_latest_repairs_dangling_symlink_by_downloading() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// check_update_status (`kigi update --check`)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
@@ -369,9 +363,7 @@ async fn check_status_unsupported_channel_reports_error() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// run_update_if_available — the auto-update opt-out gate.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
@@ -399,11 +391,9 @@ async fn run_update_if_available_respects_auto_update_false() {
|
||||
assert!(!ran, "auto_update=false must suppress the update entirely");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Race integrity: the accepted same-instant race must stay harmless. Two (or
|
||||
// three) installers running concurrently — even for DIFFERENT versions —
|
||||
// must never leave a corrupt active binary.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
async fn run_concurrent_installs(
|
||||
server: &ArtifactServer,
|
||||
|
||||
Reference in New Issue
Block a user