feat: enhance update command with detailed progress and summary output #2
+20
-23
@@ -1378,7 +1378,13 @@ fn install_planned_packages_to_rootfs(
|
|||||||
rootfs: &Path,
|
rootfs: &Path,
|
||||||
config: &config::Config,
|
config: &config::Config,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
install_planned_packages_to_rootfs_with_pre_removed(plans, rootfs, config, &HashSet::new())
|
install_planned_packages_to_rootfs_with_pre_removed(
|
||||||
|
plans,
|
||||||
|
rootfs,
|
||||||
|
config,
|
||||||
|
&HashSet::new(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_planned_packages_to_rootfs_with_pre_removed(
|
fn install_planned_packages_to_rootfs_with_pre_removed(
|
||||||
@@ -1386,18 +1392,21 @@ fn install_planned_packages_to_rootfs_with_pre_removed(
|
|||||||
rootfs: &Path,
|
rootfs: &Path,
|
||||||
config: &config::Config,
|
config: &config::Config,
|
||||||
pre_removed_packages: &HashSet<String>,
|
pre_removed_packages: &HashSet<String>,
|
||||||
|
show_progress: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut removed_replacements = HashSet::new();
|
let mut removed_replacements = HashSet::new();
|
||||||
let mut pending_post_hooks = Vec::new();
|
let mut pending_post_hooks = Vec::new();
|
||||||
for (idx, plan) in plans.iter().enumerate() {
|
for (idx, plan) in plans.iter().enumerate() {
|
||||||
ui::info(format!(
|
if show_progress {
|
||||||
"{}/{} Installing package {}-{}-{}",
|
ui::info(format!(
|
||||||
idx + 1,
|
"{}/{} Installing package {}-{}-{}",
|
||||||
plans.len(),
|
idx + 1,
|
||||||
plan.spec.package.name,
|
plans.len(),
|
||||||
plan.spec.package.version,
|
plan.spec.package.name,
|
||||||
plan.spec.package.revision
|
plan.spec.package.version,
|
||||||
));
|
plan.spec.package.revision
|
||||||
|
));
|
||||||
|
}
|
||||||
for package in &plan.staged.replacement_removals {
|
for package in &plan.staged.replacement_removals {
|
||||||
if pre_removed_packages.contains(package) {
|
if pre_removed_packages.contains(package) {
|
||||||
continue;
|
continue;
|
||||||
@@ -2712,10 +2721,6 @@ fn install_update_transaction(
|
|||||||
install::hooks::run_transaction_hooks_batch(rootfs, install::hooks::HookPhase::Pre, &contexts)?;
|
install::hooks::run_transaction_hooks_batch(rootfs, install::hooks::HookPhase::Pre, &contexts)?;
|
||||||
|
|
||||||
for removal in removals {
|
for removal in removals {
|
||||||
ui::info(format!(
|
|
||||||
"Removing package for update transaction: {}",
|
|
||||||
removal.package
|
|
||||||
));
|
|
||||||
remove_installed_package_without_transaction_hooks(
|
remove_installed_package_without_transaction_hooks(
|
||||||
&removal.package,
|
&removal.package,
|
||||||
rootfs,
|
rootfs,
|
||||||
@@ -2733,6 +2738,7 @@ fn install_update_transaction(
|
|||||||
rootfs,
|
rootfs,
|
||||||
config,
|
config,
|
||||||
&pre_removed_packages,
|
&pre_removed_packages,
|
||||||
|
false,
|
||||||
)?;
|
)?;
|
||||||
install::hooks::run_transaction_hooks_batch(
|
install::hooks::run_transaction_hooks_batch(
|
||||||
rootfs,
|
rootfs,
|
||||||
@@ -2865,7 +2871,6 @@ fn run_update_transaction_install_requests(
|
|||||||
let install_lock_path = locking::lock_path(config);
|
let install_lock_path = locking::lock_path(config);
|
||||||
let _install_lock_guard = locking::try_write(&mut install_lock, &install_lock_path, "update")?;
|
let _install_lock_guard = locking::try_write(&mut install_lock, &install_lock_path, "update")?;
|
||||||
|
|
||||||
let suppress_output = suppress_nested_install_output();
|
|
||||||
if requests
|
if requests
|
||||||
.iter()
|
.iter()
|
||||||
.any(|request| !is_archive_install_request(request))
|
.any(|request| !is_archive_install_request(request))
|
||||||
@@ -2876,14 +2881,6 @@ fn run_update_transaction_install_requests(
|
|||||||
let mut transaction_plans = Vec::new();
|
let mut transaction_plans = Vec::new();
|
||||||
let mut resources = Vec::with_capacity(requests.len());
|
let mut resources = Vec::with_capacity(requests.len());
|
||||||
for (idx, request) in requests.iter().enumerate() {
|
for (idx, request) in requests.iter().enumerate() {
|
||||||
if !suppress_output {
|
|
||||||
ui::info(format!(
|
|
||||||
"[{}/{}] preparing update payload from {}",
|
|
||||||
idx + 1,
|
|
||||||
requests.len(),
|
|
||||||
request.display()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let build_dir = isolated_update_build_dir(config, idx);
|
let build_dir = isolated_update_build_dir(config, idx);
|
||||||
let prepared = prepare_direct_install_request(
|
let prepared = prepare_direct_install_request(
|
||||||
options,
|
options,
|
||||||
@@ -2892,7 +2889,7 @@ fn run_update_transaction_install_requests(
|
|||||||
DirectInstallPreparationOptions {
|
DirectInstallPreparationOptions {
|
||||||
build_dir: &build_dir,
|
build_dir: &build_dir,
|
||||||
clean_sources_before_build: false,
|
clean_sources_before_build: false,
|
||||||
suppress_output,
|
suppress_output: true,
|
||||||
confirm_installation: false,
|
confirm_installation: false,
|
||||||
resolve_installed_conflicts: false,
|
resolve_installed_conflicts: false,
|
||||||
},
|
},
|
||||||
|
|||||||
+309
-120
@@ -747,26 +747,131 @@ pub(crate) fn collect_missing_update_dependencies(
|
|||||||
Ok(missing)
|
Ok(missing)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_update_replacements(candidates: &[UpdateCandidate]) -> Result<()> {
|
fn update_release(version: &str, revision: u32) -> String {
|
||||||
for candidate in candidates {
|
format!("{version}-{revision}")
|
||||||
if !candidate.replaces_installed {
|
}
|
||||||
continue;
|
|
||||||
}
|
fn package_word(count: usize) -> &'static str {
|
||||||
if !ui::prompt_yes_no(
|
if count == 1 { "package" } else { "packages" }
|
||||||
&format!(
|
}
|
||||||
"replace {} with {}?",
|
|
||||||
candidate.installed_package, candidate.candidate_package
|
fn update_package_label(candidate: &UpdateCandidate) -> String {
|
||||||
),
|
if candidate.replaces_installed {
|
||||||
true,
|
format!(
|
||||||
)? {
|
"{} → {}",
|
||||||
anyhow::bail!(
|
candidate.installed_package, candidate.candidate_package
|
||||||
"Replacement declined: {} -> {}",
|
)
|
||||||
candidate.installed_package,
|
} else {
|
||||||
candidate.candidate_package
|
candidate.candidate_package.clone()
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_update_summary(
|
||||||
|
updates: &[UpdateCandidate],
|
||||||
|
missing_dependencies: &[String],
|
||||||
|
download_size: Option<u64>,
|
||||||
|
) -> String {
|
||||||
|
let labels: Vec<_> = updates.iter().map(update_package_label).collect();
|
||||||
|
let label_width = labels
|
||||||
|
.iter()
|
||||||
|
.map(|label| label.chars().count())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
let mut output = format!("Updates ({})\n", updates.len());
|
||||||
|
|
||||||
|
for (candidate, label) in updates.iter().zip(labels) {
|
||||||
|
let installed = update_release(&candidate.installed_version, candidate.installed_revision);
|
||||||
|
let available = update_release(&candidate.candidate_version, candidate.candidate_revision);
|
||||||
|
let newer_build = candidate.installed_version == candidate.candidate_version
|
||||||
|
&& candidate.installed_revision == candidate.candidate_revision
|
||||||
|
&& compare_completed_at(
|
||||||
|
candidate.candidate_completed_at,
|
||||||
|
candidate.installed_completed_at,
|
||||||
|
) == Ordering::Greater;
|
||||||
|
let build_note = if newer_build { " (newer build)" } else { "" };
|
||||||
|
output.push_str(&format!(
|
||||||
|
" {label:<label_width$} {installed} → {available}{build_note}\n"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(bytes) = download_size {
|
||||||
|
output.push_str(&format!("\nDownload: {}\n", human_bytes(bytes)));
|
||||||
|
}
|
||||||
|
if !missing_dependencies.is_empty() {
|
||||||
|
output.push_str(&format!(
|
||||||
|
"Dependencies: {}\n",
|
||||||
|
missing_dependencies.join(", ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
output.push('\n');
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn planned_update_download_size(
|
||||||
|
updates: &[UpdateCandidate],
|
||||||
|
dependency_plan: Option<&planner::ExecutionPlan>,
|
||||||
|
) -> Option<u64> {
|
||||||
|
let mut total = 0u64;
|
||||||
|
for candidate in updates {
|
||||||
|
let UpdateOrigin::Binary { record, .. } = &candidate.origin else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
total = total.checked_add(record.size)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(plan) = dependency_plan {
|
||||||
|
for step in plan.actionable_steps() {
|
||||||
|
match &step.origin {
|
||||||
|
planner::PlanOrigin::Binary { record, .. } => {
|
||||||
|
total = total.checked_add(record.size)?;
|
||||||
|
}
|
||||||
|
planner::PlanOrigin::Source { .. } => return None,
|
||||||
|
planner::PlanOrigin::Installed => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
Some(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_update_status(package_count: usize) -> (ProgressBar, bool) {
|
||||||
|
let interactive = std::io::stdout().is_terminal() && std::io::stderr().is_terminal();
|
||||||
|
if !interactive {
|
||||||
|
println!(
|
||||||
|
"Updating {package_count} {}...",
|
||||||
|
package_word(package_count)
|
||||||
|
);
|
||||||
|
return (ProgressBar::hidden(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = ProgressBar::new_spinner();
|
||||||
|
status.set_style(
|
||||||
|
ProgressStyle::default_spinner()
|
||||||
|
.template("{spinner:.cyan} {msg}")
|
||||||
|
.unwrap_or_else(|_| ProgressStyle::default_spinner()),
|
||||||
|
);
|
||||||
|
status.enable_steady_tick(std::time::Duration::from_millis(80));
|
||||||
|
status.set_message(format!(
|
||||||
|
"Updating {package_count} {}",
|
||||||
|
package_word(package_count)
|
||||||
|
));
|
||||||
|
(status, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_update_complete(updates: &[UpdateCandidate], interactive: bool) {
|
||||||
|
if interactive {
|
||||||
|
println!("Updated {} {}", updates.len(), package_word(updates.len()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for candidate in updates {
|
||||||
|
println!(
|
||||||
|
"Updated {} {} → {}",
|
||||||
|
update_package_label(candidate),
|
||||||
|
update_release(&candidate.installed_version, candidate.installed_revision),
|
||||||
|
update_release(&candidate.candidate_version, candidate.candidate_revision)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run_update_command(
|
pub(crate) fn run_update_command(
|
||||||
@@ -776,36 +881,10 @@ pub(crate) fn run_update_command(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let updates = collect_update_candidates(config, options.rootfs, packages)?;
|
let updates = collect_update_candidates(config, options.rootfs, packages)?;
|
||||||
if updates.is_empty() {
|
if updates.is_empty() {
|
||||||
ui::info("All installed packages are up to date.");
|
println!("All packages are up to date.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let targets: Vec<String> = updates
|
|
||||||
.iter()
|
|
||||||
.map(|candidate| {
|
|
||||||
let summary = format!(
|
|
||||||
"{} v{}-{} -> {} v{}-{}",
|
|
||||||
candidate.installed_package,
|
|
||||||
candidate.installed_version,
|
|
||||||
candidate.installed_revision,
|
|
||||||
candidate.candidate_package,
|
|
||||||
candidate.candidate_version,
|
|
||||||
candidate.candidate_revision
|
|
||||||
);
|
|
||||||
if candidate.installed_version == candidate.candidate_version
|
|
||||||
&& candidate.installed_revision == candidate.candidate_revision
|
|
||||||
&& compare_completed_at(
|
|
||||||
candidate.candidate_completed_at,
|
|
||||||
candidate.installed_completed_at,
|
|
||||||
) == Ordering::Greater
|
|
||||||
{
|
|
||||||
format!("{summary} (newer UTC build timestamp)")
|
|
||||||
} else {
|
|
||||||
summary
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let conflict_subjects: Vec<_> = updates
|
let conflict_subjects: Vec<_> = updates
|
||||||
.iter()
|
.iter()
|
||||||
.map(|candidate| super::super::InstallConflictSubject {
|
.map(|candidate| super::super::InstallConflictSubject {
|
||||||
@@ -816,32 +895,39 @@ pub(crate) fn run_update_command(
|
|||||||
.collect();
|
.collect();
|
||||||
super::super::validate_no_transaction_conflicts(&conflict_subjects)?;
|
super::super::validate_no_transaction_conflicts(&conflict_subjects)?;
|
||||||
|
|
||||||
ui::info(format!("{} package(s) can be updated:", updates.len()));
|
|
||||||
for target in &targets {
|
|
||||||
ui::info(format!(" {}", target));
|
|
||||||
}
|
|
||||||
|
|
||||||
let db_path = config.installed_db_path(options.rootfs);
|
let db_path = config.installed_db_path(options.rootfs);
|
||||||
let missing_deps = if options.no_deps {
|
let missing_deps = if options.no_deps {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
} else {
|
} else {
|
||||||
collect_missing_update_dependencies(&updates, &db_path)?
|
collect_missing_update_dependencies(&updates, &db_path)?
|
||||||
};
|
};
|
||||||
if !missing_deps.is_empty() {
|
|
||||||
ui::info(format!(
|
|
||||||
"New dependencies required by updates: {}",
|
|
||||||
missing_deps.join(", ")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !options.dry_run && !ui::prompt_package_action("update", &targets, true)? {
|
let dependency_plan = if missing_deps.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(planner::build_dependency_install_plan(
|
||||||
|
config,
|
||||||
|
options.rootfs,
|
||||||
|
&missing_deps,
|
||||||
|
planner::PlannerOptions {
|
||||||
|
assume_yes: options.assume_yes,
|
||||||
|
prefer_binary: config.repo_settings.prefer_binary,
|
||||||
|
local_sibling_root: None,
|
||||||
|
include_test_deps: options.install_test_deps,
|
||||||
|
lib32_only_requested_specs: false,
|
||||||
|
},
|
||||||
|
)?)
|
||||||
|
};
|
||||||
|
let download_size = planned_update_download_size(&updates, dependency_plan.as_ref());
|
||||||
|
print!(
|
||||||
|
"{}",
|
||||||
|
format_update_summary(&updates, &missing_deps, download_size)
|
||||||
|
);
|
||||||
|
|
||||||
|
if !options.dry_run && !options.assume_yes && !ui::prompt_yes_no("Proceed?", true)? {
|
||||||
anyhow::bail!("Aborted");
|
anyhow::bail!("Aborted");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !options.dry_run {
|
|
||||||
confirm_update_replacements(&updates)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.dry_run {
|
if options.dry_run {
|
||||||
super::super::resolve_installed_conflicts_for_subjects(
|
super::super::resolve_installed_conflicts_for_subjects(
|
||||||
&conflict_subjects,
|
&conflict_subjects,
|
||||||
@@ -851,70 +937,173 @@ pub(crate) fn run_update_command(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut transaction_requests = Vec::new();
|
if options.dry_run {
|
||||||
if !missing_deps.is_empty() {
|
if let Some(plan) = dependency_plan.as_ref() {
|
||||||
let dep_plan = planner::build_dependency_install_plan(
|
crate::commands::print_plan_summary(plan);
|
||||||
config,
|
|
||||||
options.rootfs,
|
|
||||||
&missing_deps,
|
|
||||||
planner::PlannerOptions {
|
|
||||||
assume_yes: options.assume_yes,
|
|
||||||
prefer_binary: config.repo_settings.prefer_binary,
|
|
||||||
local_sibling_root: None,
|
|
||||||
include_test_deps: options.install_test_deps,
|
|
||||||
lib32_only_requested_specs: false,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
crate::commands::print_plan_summary(&dep_plan);
|
|
||||||
|
|
||||||
if options.dry_run {
|
|
||||||
ui::info("Dry run enabled, stopping before dependency installation/update.");
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
println!("Dry run: no changes made.");
|
||||||
transaction_requests.extend(super::super::install_requests_for_plan(
|
|
||||||
&dep_plan,
|
|
||||||
config,
|
|
||||||
options.rootfs,
|
|
||||||
)?);
|
|
||||||
} else if options.dry_run {
|
|
||||||
ui::info("Dry run enabled, stopping before update.");
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (idx, candidate) in updates.iter().enumerate() {
|
let (status, interactive) = start_update_status(updates.len());
|
||||||
ui::info(format!(
|
let update_result = (|| -> Result<()> {
|
||||||
"[{}/{}] staging update {} v{}-{} -> {} v{}-{}",
|
let mut transaction_requests = Vec::new();
|
||||||
idx + 1,
|
if let Some(plan) = dependency_plan.as_ref() {
|
||||||
updates.len(),
|
crate::commands::print_plan_summary(plan);
|
||||||
candidate.installed_package,
|
transaction_requests.extend(super::super::install_requests_for_plan(
|
||||||
candidate.installed_version,
|
plan,
|
||||||
candidate.installed_revision,
|
config,
|
||||||
candidate.candidate_package,
|
options.rootfs,
|
||||||
candidate.candidate_version,
|
)?);
|
||||||
candidate.candidate_revision
|
}
|
||||||
));
|
|
||||||
let request = candidate_request_path(candidate, config, options.rootfs)?;
|
|
||||||
transaction_requests.push(request);
|
|
||||||
}
|
|
||||||
super::super::run_update_transaction_install_requests(
|
|
||||||
super::super::DirectInstallOptions {
|
|
||||||
rootfs: options.rootfs,
|
|
||||||
no_deps: true,
|
|
||||||
no_flags: options.no_flags,
|
|
||||||
cross_prefix: options.cross_prefix,
|
|
||||||
clean: options.clean,
|
|
||||||
dry_run: false,
|
|
||||||
lib32_only: false,
|
|
||||||
install_test_deps: options.install_test_deps,
|
|
||||||
},
|
|
||||||
config,
|
|
||||||
&transaction_requests,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if options.clean {
|
for candidate in &updates {
|
||||||
crate::commands::clean_build_workspace(config)?;
|
status.set_message(format!(
|
||||||
}
|
"Updating {} {} · preparing {}",
|
||||||
|
updates.len(),
|
||||||
|
package_word(updates.len()),
|
||||||
|
candidate.candidate_package
|
||||||
|
));
|
||||||
|
let request = candidate_request_path(candidate, config, options.rootfs)?;
|
||||||
|
transaction_requests.push(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
status.set_message(format!(
|
||||||
|
"Updating {} {} · installing",
|
||||||
|
updates.len(),
|
||||||
|
package_word(updates.len())
|
||||||
|
));
|
||||||
|
super::super::run_update_transaction_install_requests(
|
||||||
|
super::super::DirectInstallOptions {
|
||||||
|
rootfs: options.rootfs,
|
||||||
|
no_deps: true,
|
||||||
|
no_flags: options.no_flags,
|
||||||
|
cross_prefix: options.cross_prefix,
|
||||||
|
clean: options.clean,
|
||||||
|
dry_run: false,
|
||||||
|
lib32_only: false,
|
||||||
|
install_test_deps: options.install_test_deps,
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
&transaction_requests,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if options.clean {
|
||||||
|
crate::commands::clean_build_workspace(config)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})();
|
||||||
|
status.finish_and_clear();
|
||||||
|
update_result?;
|
||||||
|
print_update_complete(&updates, interactive);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod ux_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn binary_record(name: &str, version: &str, size: u64) -> db::repo::BinaryRepoPackageRecord {
|
||||||
|
db::repo::BinaryRepoPackageRecord {
|
||||||
|
repo_name: "core".into(),
|
||||||
|
name: name.into(),
|
||||||
|
real_name: None,
|
||||||
|
version: version.into(),
|
||||||
|
revision: 1,
|
||||||
|
abi_breaking: false,
|
||||||
|
built_against: Vec::new(),
|
||||||
|
completed_at: None,
|
||||||
|
filename: format!("{name}-{version}-1-x86_64.depot.pkg.tar.zst"),
|
||||||
|
size,
|
||||||
|
sha512: String::new(),
|
||||||
|
description: None,
|
||||||
|
homepage: None,
|
||||||
|
license: None,
|
||||||
|
provides: Vec::new(),
|
||||||
|
conflicts: Vec::new(),
|
||||||
|
replaces: Vec::new(),
|
||||||
|
runtime_dependencies: Vec::new(),
|
||||||
|
optional_dependencies: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn binary_update(
|
||||||
|
name: &str,
|
||||||
|
installed_version: &str,
|
||||||
|
candidate_version: &str,
|
||||||
|
size: u64,
|
||||||
|
) -> UpdateCandidate {
|
||||||
|
UpdateCandidate {
|
||||||
|
installed_package: name.into(),
|
||||||
|
candidate_package: name.into(),
|
||||||
|
replaces_installed: false,
|
||||||
|
installed_version: installed_version.into(),
|
||||||
|
installed_revision: 1,
|
||||||
|
installed_completed_at: None,
|
||||||
|
candidate_version: candidate_version.into(),
|
||||||
|
candidate_revision: 1,
|
||||||
|
candidate_completed_at: None,
|
||||||
|
runtime_dependencies: Vec::new(),
|
||||||
|
provides: Vec::new(),
|
||||||
|
conflicts: Vec::new(),
|
||||||
|
repo_priority: 0,
|
||||||
|
origin: UpdateOrigin::Binary {
|
||||||
|
repo_name: "core".into(),
|
||||||
|
record: Box::new(binary_record(name, candidate_version, size)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_summary_is_compact_and_aligned() {
|
||||||
|
let updates = [
|
||||||
|
binary_update("curl", "8.11.0", "8.12.0", 3 * 1024),
|
||||||
|
binary_update("openssl", "3.4.0", "3.4.1", 5 * 1024),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
format_update_summary(&updates, &["ca-certificates".into()], Some(8 * 1024)),
|
||||||
|
concat!(
|
||||||
|
"Updates (2)\n",
|
||||||
|
" curl 8.11.0-1 → 8.12.0-1\n",
|
||||||
|
" openssl 3.4.0-1 → 3.4.1-1\n",
|
||||||
|
"\n",
|
||||||
|
"Download: 8.0 KiB\n",
|
||||||
|
"Dependencies: ca-certificates\n",
|
||||||
|
"\n",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_summary_marks_replacements_and_newer_builds() {
|
||||||
|
let mut update = binary_update("icu78", "78.1", "79.1", 1024);
|
||||||
|
update.candidate_package = "icu79".into();
|
||||||
|
update.replaces_installed = true;
|
||||||
|
update.installed_version = "79.1".into();
|
||||||
|
update.installed_completed_at = Some(1);
|
||||||
|
update.candidate_completed_at = Some(2);
|
||||||
|
|
||||||
|
let summary = format_update_summary(&[update], &[], Some(1024));
|
||||||
|
assert!(summary.contains("icu78 → icu79"));
|
||||||
|
assert!(summary.contains("79.1-1 → 79.1-1 (newer build)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn planned_download_size_requires_all_binary_inputs() {
|
||||||
|
let binary = binary_update("curl", "8.11.0", "8.12.0", 3 * 1024);
|
||||||
|
assert_eq!(
|
||||||
|
planned_update_download_size(std::slice::from_ref(&binary), None),
|
||||||
|
Some(3 * 1024)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut source = binary;
|
||||||
|
source.origin = UpdateOrigin::Source {
|
||||||
|
repo_name: "core".into(),
|
||||||
|
path: PathBuf::from("/repo/curl.toml"),
|
||||||
|
};
|
||||||
|
assert_eq!(planned_update_download_size(&[source], None), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user