Skip to content

Commit 7c559c0

Browse files
authored
fix: don't return true from isNewPatchAvailableForDownload if the new patch will not be installed (#201)
1 parent f8275ce commit 7c559c0

File tree

1 file changed

+129
-26
lines changed

1 file changed

+129
-26
lines changed

library/src/updater.rs

Lines changed: 129 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ use crate::cache::{PatchInfo, UpdaterState};
1212
use crate::config::{set_config, with_config, UpdateConfig};
1313
use crate::events::{EventType, PatchEvent};
1414
use crate::logging::init_logging;
15-
use crate::network::{
16-
download_to_path, patches_check_url, NetworkHooks, PatchCheckRequest, PatchCheckResponse,
17-
};
15+
use crate::network::{download_to_path, patches_check_url, NetworkHooks, PatchCheckRequest};
1816
use crate::updater_lock::{with_updater_thread_lock, UpdaterLockState};
1917
use crate::yaml::YamlConfig;
2018

@@ -50,6 +48,13 @@ impl Display for UpdateStatus {
5048
}
5149
}
5250

51+
/// Whether a patch is OK to install, and if not, why.
52+
pub enum ShouldInstallPatchCheckResult {
53+
PatchOkToInstall,
54+
PatchKnownBad,
55+
PatchAlreadyInstalled,
56+
}
57+
5358
/// Returned when a call to `init` is not successful. These indicate that the specific call to
5459
/// `init` was not successful, but the library may still be in a valid state (e.g., if
5560
/// `AlreadyInitialized` is returned, the library is still initialized). Callers can safely ignore
@@ -234,7 +239,8 @@ pub fn should_auto_update() -> anyhow::Result<bool> {
234239
with_config(|config| Ok(config.auto_update))
235240
}
236241

237-
fn check_for_update_internal() -> anyhow::Result<PatchCheckResponse> {
242+
/// Synchronously checks for an update and returns true if an update is available.
243+
pub fn check_for_update() -> anyhow::Result<bool> {
238244
let (request, url, request_fn) = with_config(|config| {
239245
// Get the required info to make the request.
240246
Ok((
@@ -246,12 +252,16 @@ fn check_for_update_internal() -> anyhow::Result<PatchCheckResponse> {
246252

247253
let response = request_fn(&url, request)?;
248254
debug!("Patch check response: {:?}", response);
249-
Ok(response)
250-
}
251255

252-
/// Synchronously checks for an update and returns true if an update is available.
253-
pub fn check_for_update() -> anyhow::Result<bool> {
254-
check_for_update_internal().map(|res| res.patch_available)
256+
if let Some(patch) = response.patch {
257+
match should_install_patch(patch.number)? {
258+
ShouldInstallPatchCheckResult::PatchOkToInstall => Ok(true),
259+
ShouldInstallPatchCheckResult::PatchKnownBad => Ok(false),
260+
ShouldInstallPatchCheckResult::PatchAlreadyInstalled => Ok(false),
261+
}
262+
} else {
263+
Ok(false)
264+
}
255265
}
256266

257267
fn check_hash(path: &Path, expected_string: &str) -> anyhow::Result<()> {
@@ -370,23 +380,10 @@ fn update_internal(_: &UpdaterLockState) -> anyhow::Result<UpdateStatus> {
370380

371381
let patch = response.patch.ok_or(UpdateError::BadServerResponse)?;
372382

373-
// Don't install a patch if it has previously failed to boot.
374-
let is_known_bad_patch = with_state(|state| Ok(state.is_known_bad_patch(patch.number)))?;
375-
if is_known_bad_patch {
376-
info!(
377-
"Patch {} has previously failed to boot, skipping.",
378-
patch.number
379-
);
380-
return Ok(UpdateStatus::UpdateIsBadPatch);
381-
}
382-
383-
// If we already have the latest available patch downloaded, we don't need to download it again.
384-
let next_boot_patch = with_mut_state(|state| Ok(state.next_boot_patch()))?;
385-
if let Some(next_boot_patch) = next_boot_patch {
386-
if next_boot_patch.number == patch.number {
387-
info!("Patch {} is already installed, skipping.", patch.number);
388-
return Ok(UpdateStatus::NoUpdate);
389-
}
383+
match should_install_patch(patch.number)? {
384+
ShouldInstallPatchCheckResult::PatchOkToInstall => {}
385+
ShouldInstallPatchCheckResult::PatchKnownBad => return Ok(UpdateStatus::UpdateIsBadPatch),
386+
ShouldInstallPatchCheckResult::PatchAlreadyInstalled => return Ok(UpdateStatus::NoUpdate),
390387
}
391388

392389
let download_dir = PathBuf::from(&config.download_dir);
@@ -437,6 +434,29 @@ fn update_internal(_: &UpdaterLockState) -> anyhow::Result<UpdateStatus> {
437434
})
438435
}
439436

437+
fn should_install_patch(patch_number: usize) -> Result<ShouldInstallPatchCheckResult> {
438+
// Don't install a patch if it has previously failed to boot.
439+
let is_known_bad_patch = with_state(|state| Ok(state.is_known_bad_patch(patch_number)))?;
440+
if is_known_bad_patch {
441+
info!(
442+
"Patch {} has previously failed to boot, skipping.",
443+
patch_number
444+
);
445+
return Ok(ShouldInstallPatchCheckResult::PatchKnownBad);
446+
}
447+
448+
// If we already have the latest available patch downloaded, we don't need to download it again.
449+
let next_boot_patch = with_mut_state(|state| Ok(state.next_boot_patch()))?;
450+
if let Some(next_boot_patch) = next_boot_patch {
451+
if next_boot_patch.number == patch_number {
452+
info!("Patch {} is already installed, skipping.", patch_number);
453+
return Ok(ShouldInstallPatchCheckResult::PatchAlreadyInstalled);
454+
}
455+
}
456+
457+
Ok(ShouldInstallPatchCheckResult::PatchOkToInstall)
458+
}
459+
440460
/// Synchronously checks for an update and downloads and installs it if available.
441461
pub fn update() -> anyhow::Result<UpdateStatus> {
442462
with_updater_thread_lock(update_internal)
@@ -1428,3 +1448,86 @@ mod rollback_tests {
14281448
Ok(())
14291449
}
14301450
}
1451+
1452+
#[cfg(test)]
1453+
mod check_for_update_tests {
1454+
use anyhow::Result;
1455+
use serial_test::serial;
1456+
use tempdir::TempDir;
1457+
1458+
use crate::{
1459+
network::PatchCheckResponse, report_launch_failure, report_launch_start,
1460+
test_utils::install_fake_patch, updater::tests::init_for_testing,
1461+
};
1462+
1463+
use super::Patch;
1464+
1465+
fn mock_server(available_patch_number: usize) -> mockito::ServerGuard {
1466+
let mut server = mockito::Server::new();
1467+
let check_response = PatchCheckResponse {
1468+
patch_available: true,
1469+
patch: Some(Patch {
1470+
number: available_patch_number,
1471+
hash: "#".to_string(),
1472+
download_url: "download_url".to_string(),
1473+
hash_signature: None,
1474+
}),
1475+
rolled_back_patch_numbers: None,
1476+
};
1477+
let check_response_body = serde_json::to_string(&check_response).unwrap();
1478+
let _ = server
1479+
.mock("POST", "/api/v1/patches/check")
1480+
.with_status(200)
1481+
.with_body(check_response_body)
1482+
.create();
1483+
server
1484+
}
1485+
1486+
#[serial]
1487+
#[test]
1488+
fn returns_false_if_patch_is_already_installed() -> Result<()> {
1489+
let patch_number = 1;
1490+
let server = mock_server(patch_number);
1491+
let tmp_dir = TempDir::new("example").unwrap();
1492+
init_for_testing(&tmp_dir, Some(&server.url()));
1493+
1494+
install_fake_patch(patch_number)?;
1495+
1496+
let is_update_available = crate::check_for_update()?;
1497+
assert!(!is_update_available);
1498+
1499+
Ok(())
1500+
}
1501+
1502+
#[serial]
1503+
#[test]
1504+
fn returns_false_if_patch_is_known_bad() -> Result<()> {
1505+
let patch_number = 1;
1506+
let server = mock_server(patch_number);
1507+
let tmp_dir = TempDir::new("example").unwrap();
1508+
init_for_testing(&tmp_dir, Some(&server.url()));
1509+
1510+
install_fake_patch(patch_number)?;
1511+
report_launch_start()?;
1512+
report_launch_failure()?;
1513+
1514+
let is_update_available = crate::check_for_update()?;
1515+
assert!(!is_update_available);
1516+
1517+
Ok(())
1518+
}
1519+
1520+
#[serial]
1521+
#[test]
1522+
fn returns_true_if_patch_has_no_issues() -> Result<()> {
1523+
let patch_number = 1;
1524+
let server = mock_server(patch_number);
1525+
let tmp_dir = TempDir::new("example").unwrap();
1526+
init_for_testing(&tmp_dir, Some(&server.url()));
1527+
1528+
let is_update_available = crate::check_for_update()?;
1529+
assert!(is_update_available);
1530+
1531+
Ok(())
1532+
}
1533+
}

0 commit comments

Comments
 (0)