1use std::ffi::OsString;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender};
7use std::sync::{Arc, Mutex, MutexGuard, Weak};
8use std::time::{Duration, Instant};
9
10use corundum_framework_platform::package;
11use corundum_framework_update::{PackageState, PackageStatus};
12use semver::Version;
13use velopack::UpdateInfo;
14
15use crate::backend::{Applied, Backend, Velopack};
16use crate::error::Error;
17use crate::services::{self, ServiceDriver};
18use crate::start::{self, PackageSpec};
19
20#[derive(Clone, Debug)]
22pub enum Source {
23 Github { repo: String, prerelease: bool },
26 Http(String),
28}
29
30#[derive(Clone, Debug)]
32pub enum After {
33 RestartApp,
35 RestartWith(Vec<OsString>),
37 Exit,
40}
41
42#[derive(Clone)]
46pub struct Updater {
47 shared: Arc<Shared>,
48}
49
50type Listener = Arc<dyn Fn() + Send + Sync>;
51
52struct Shared {
53 spec: &'static PackageSpec,
54 backend: Arc<dyn Backend>,
55 services: Arc<dyn ServiceDriver>,
57 current: Version,
58 state: Mutex<State>,
59 listeners: Mutex<Vec<Listener>>,
60 worker: Mutex<Option<Sender<Request>>>,
63 applying: AtomicBool,
66}
67
68#[cfg(feature = "test-support")]
70struct Inert;
71
72#[cfg(feature = "test-support")]
73impl Backend for Inert {
74 fn current(&self) -> Version {
75 Version::new(1, 0, 0)
76 }
77
78 fn check(&self) -> Result<Option<UpdateInfo>, Error> {
79 Ok(None)
80 }
81
82 fn download(&self, _info: &UpdateInfo, _progress: &dyn Fn(u8)) -> Result<(), Error> {
83 Ok(())
84 }
85
86 fn pending(&self) -> Option<Version> {
87 None
88 }
89
90 fn apply(&self, _applied: Applied, _after: &After) -> Result<(), Error> {
91 Ok(())
92 }
93}
94
95struct State {
96 state: PackageState,
97 downloaded: Option<UpdateInfo>,
99}
100
101enum Request {
102 Check,
103 Every(Duration),
104}
105
106impl Updater {
107 pub fn new(spec: &'static PackageSpec, source: Source) -> Result<Updater, Error> {
109 if package::current().is_none() {
112 return Err(Error::NotPackaged);
113 }
114 Ok(Updater::with_backend(
115 spec,
116 Arc::new(Velopack::new(&source)?),
117 services::driver(spec),
118 ))
119 }
120
121 pub(crate) fn with_backend(
122 spec: &'static PackageSpec,
123 backend: Arc<dyn Backend>,
124 services: Arc<dyn ServiceDriver>,
125 ) -> Updater {
126 Updater {
127 shared: Arc::new(Shared {
128 spec,
129 services,
130 current: backend.current(),
131 backend,
132 state: Mutex::new(State {
133 state: PackageState::Idle,
134 downloaded: None,
135 }),
136 listeners: Mutex::new(Vec::new()),
137 worker: Mutex::new(None),
138 applying: AtomicBool::new(false),
139 }),
140 }
141 }
142
143 #[cfg(feature = "test-support")]
147 #[doc(hidden)]
148 pub fn for_tests(state: PackageState) -> Updater {
149 static SPEC: PackageSpec = PackageSpec {
150 app_name: "test",
151 on_installed: None,
152 on_uninstalling: None,
153 before_apply: None,
154 cli: None,
155 services: crate::Services::NONE,
156 };
157 let updater = Updater::with_backend(&SPEC, Arc::new(Inert), Arc::new(()));
158 let downloaded = match &state {
159 PackageState::Ready { version } => Some(UpdateInfo {
160 TargetFullRelease: velopack::VelopackAsset {
161 Version: version.to_string(),
162 ..Default::default()
163 },
164 ..Default::default()
165 }),
166 _ => None,
167 };
168 *lock(&updater.shared.state) = State { state, downloaded };
169 updater
170 }
171
172 pub fn on_change(&self, f: impl Fn() + Send + Sync + 'static) {
175 lock(&self.shared.listeners).push(Arc::new(f));
176 }
177
178 pub fn status(&self) -> PackageStatus {
180 PackageStatus {
181 current: Some(self.shared.current.clone()),
182 state: lock(&self.shared.state).state.clone(),
183 }
184 }
185
186 pub fn check_now(&self) {
188 self.send(Request::Check);
189 }
190
191 pub fn run_in_background(&self, every: Duration) {
194 self.send(Request::Every(every));
195 }
196
197 pub fn apply(&self, after: After) -> Result<(), Error> {
214 let applying = self.shared.begin_apply()?;
215 let info = lock(&self.shared.state)
218 .downloaded
219 .clone()
220 .ok_or_else(|| Error::Velopack("nothing downloaded".into()))?;
221 self.shared.prepare()?;
222 self.shared.apply(Applied::Downloaded(info), &after)?;
223 applying.keep();
224 Ok(())
225 }
226
227 pub fn apply_pending_at_start(&self) -> Result<(), Error> {
238 if self.apply_pending(start::restarted())? {
239 std::process::exit(0);
240 }
241 Ok(())
242 }
243
244 fn apply_pending(&self, restarted: bool) -> Result<bool, Error> {
247 if restarted {
248 return Ok(false);
249 }
250 match self.shared.backend.pending() {
251 Some(pending) if pending > self.shared.current => {
252 let applying = self.shared.begin_apply()?;
253 self.shared.prepare()?;
254 self.shared.apply(Applied::Pending, &After::RestartApp)?;
255 applying.keep();
256 Ok(true)
257 }
258 _ => Ok(false),
259 }
260 }
261
262 fn send(&self, request: Request) {
264 let mut worker = lock(&self.shared.worker);
265 let sender = worker.get_or_insert_with(|| {
266 let (tx, rx) = mpsc::channel();
267 let shared = Arc::downgrade(&self.shared);
268 let backend = Arc::clone(&self.shared.backend);
269 std::thread::Builder::new()
270 .name("package-updater".into())
271 .spawn(move || work(shared, backend, rx))
272 .expect("could not start the updater's thread");
273 tx
274 });
275 let _ = sender.send(request);
277 }
278}
279
280struct ApplyGuard<'a>(Option<&'a AtomicBool>);
283
284impl ApplyGuard<'_> {
285 fn keep(mut self) {
286 self.0 = None;
287 }
288}
289
290impl Drop for ApplyGuard<'_> {
291 fn drop(&mut self) {
292 if let Some(applying) = self.0 {
293 applying.store(false, Ordering::SeqCst);
294 }
295 }
296}
297
298impl Shared {
299 fn begin_apply(&self) -> Result<ApplyGuard<'_>, Error> {
301 self.applying
302 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
303 .map_err(|_| Error::Applying)?;
304 Ok(ApplyGuard(Some(&self.applying)))
305 }
306
307 fn prepare(&self) -> Result<(), Error> {
310 let before_apply = self.spec.before_apply.unwrap_or(|| Ok(()));
311 before_apply()
312 .and_then(|()| self.services.stop_running())
313 .map_err(|e| {
314 self.set(PackageState::Failed(e.clone()));
315 Error::Prepare(e)
316 })
317 }
318
319 fn apply(&self, applied: Applied, after: &After) -> Result<(), Error> {
324 self.backend.apply(applied, after).inspect_err(|_| {
325 self.services.restart_stopped();
326 })
327 }
328
329 fn set(&self, new: PackageState) {
331 {
332 let mut state = lock(&self.state);
333 if state.state == new {
334 return;
335 }
336 state.state = new;
337 }
338 self.notify();
339 }
340
341 fn notify(&self) {
342 let listeners = lock(&self.listeners).clone();
344 for listener in listeners {
345 listener();
346 }
347 }
348
349 fn check(&self, backend: &dyn Backend) {
355 let ready = {
356 let state = lock(&self.state);
357 match (&state.state, &state.downloaded) {
358 (PackageState::Failed(_), Some(info)) => {
359 Version::parse(&info.TargetFullRelease.Version).ok()
360 }
361 _ => None,
362 }
363 };
364 if let Some(version) = ready {
365 self.set(PackageState::Ready { version });
366 }
367 let quiet = lock(&self.state).downloaded.is_some();
368 let fail = |msg: String| {
369 if quiet {
370 tracing::warn!("update check failed, keeping the downloaded update: {msg}");
371 } else {
372 self.set(PackageState::Failed(msg));
373 }
374 };
375 if !quiet {
376 self.set(PackageState::Checking);
377 }
378 let info = match backend.check() {
379 Ok(Some(info)) => info,
380 Ok(None) => {
381 if !quiet {
382 self.set(PackageState::UpToDate);
383 }
384 return;
385 }
386 Err(e) => return fail(e.to_string()),
387 };
388 let version = match Version::parse(&info.TargetFullRelease.Version) {
389 Ok(version) => version,
390 Err(e) => {
391 return fail(format!(
392 "the update's version {:?} is not a version: {e}",
393 info.TargetFullRelease.Version
394 ));
395 }
396 };
397 if !quiet {
398 self.set(PackageState::Downloading { percent: 0 });
399 }
400 let progress = |percent: u8| {
401 if !quiet {
402 self.set(PackageState::Downloading { percent });
403 }
404 };
405 match backend.download(&info, &progress) {
406 Ok(()) => {
407 {
408 let mut state = lock(&self.state);
409 state.state = PackageState::Ready { version };
410 state.downloaded = Some(info);
411 }
412 self.notify();
413 }
414 Err(e) => fail(e.to_string()),
415 }
416 }
417}
418
419fn work(shared: Weak<Shared>, backend: Arc<dyn Backend>, requests: Receiver<Request>) {
422 let mut every: Option<Duration> = None;
423 let mut next: Option<Instant> = None;
424 loop {
425 let request = match next {
426 Some(at) => match requests.recv_timeout(at.saturating_duration_since(Instant::now())) {
427 Ok(request) => request,
428 Err(RecvTimeoutError::Timeout) => Request::Check,
429 Err(RecvTimeoutError::Disconnected) => return,
430 },
431 None => match requests.recv() {
432 Ok(request) => request,
433 Err(_) => return,
434 },
435 };
436 if let Request::Every(interval) = request {
437 every = Some(interval);
438 }
439 let Some(shared) = shared.upgrade() else {
440 return;
441 };
442 shared.check(&*backend);
443 drop(shared);
444 next = every.map(|every| Instant::now() + every);
445 }
446}
447
448fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
451 mutex.lock().unwrap_or_else(|e| e.into_inner())
452}
453
454#[cfg(test)]
455mod tests {
456 use std::cell::RefCell;
457 use std::collections::VecDeque;
458 use std::sync::atomic::{AtomicUsize, Ordering};
459 use std::sync::{Arc, Mutex};
460 use std::time::{Duration, Instant};
461
462 use corundum_framework_update::{PackageState, PackageStatus};
463 use semver::Version;
464 use velopack::{UpdateInfo, VelopackAsset};
465
466 use super::*;
467 use crate::backend::{Applied, Backend};
468 use crate::error::Error;
469 use crate::services::ServiceDriver;
470 use crate::start::PackageSpec;
471
472 thread_local! {
473 static APPLY_LOG: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
476 }
477
478 fn log(entry: impl Into<String>) {
479 APPLY_LOG.with(|l| l.borrow_mut().push(entry.into()));
480 }
481
482 fn apply_log() -> Vec<String> {
483 APPLY_LOG.with(|l| l.borrow().clone())
484 }
485
486 fn v(s: &str) -> Version {
487 Version::parse(s).unwrap()
488 }
489
490 fn info(version: &str) -> UpdateInfo {
491 UpdateInfo {
492 TargetFullRelease: VelopackAsset {
493 Version: version.to_owned(),
494 FileName: format!("app-{version}-full.nupkg"),
495 ..Default::default()
496 },
497 ..Default::default()
498 }
499 }
500
501 fn spec(before_apply: Option<fn() -> Result<(), String>>) -> &'static PackageSpec {
502 Box::leak(Box::new(PackageSpec {
503 app_name: "updater-test",
504 on_installed: None,
505 on_uninstalling: None,
506 before_apply,
507 cli: None,
508 services: crate::Services::NONE,
509 }))
510 }
511
512 fn stopping() -> Result<(), String> {
513 log("before_apply");
514 Ok(())
515 }
516
517 fn failing_to_stop() -> Result<(), String> {
518 log("before_apply");
519 Err("stop failed".to_owned())
520 }
521
522 struct Fake {
525 current: Version,
526 checks: Mutex<VecDeque<Result<Option<UpdateInfo>, Error>>>,
527 checked: AtomicUsize,
528 pending: Option<Version>,
529 fails_apply: std::sync::atomic::AtomicBool,
531 }
532
533 impl Fake {
534 fn new(
535 current: &str,
536 checks: Vec<Result<Option<UpdateInfo>, Error>>,
537 pending: Option<&str>,
538 ) -> Arc<Fake> {
539 Arc::new(Fake {
540 current: v(current),
541 checks: Mutex::new(checks.into()),
542 checked: AtomicUsize::new(0),
543 pending: pending.map(v),
544 fails_apply: std::sync::atomic::AtomicBool::new(false),
545 })
546 }
547 }
548
549 impl Backend for Fake {
550 fn current(&self) -> Version {
551 self.current.clone()
552 }
553
554 fn check(&self) -> Result<Option<UpdateInfo>, Error> {
555 self.checked.fetch_add(1, Ordering::SeqCst);
556 self.checks.lock().unwrap().pop_front().unwrap_or(Ok(None))
557 }
558
559 fn download(&self, _info: &UpdateInfo, progress: &dyn Fn(u8)) -> Result<(), Error> {
560 for percent in [0, 50, 100] {
561 progress(percent);
562 }
563 Ok(())
564 }
565
566 fn pending(&self) -> Option<Version> {
567 self.pending.clone()
568 }
569
570 fn apply(&self, applied: Applied, after: &After) -> Result<(), Error> {
571 let what = match applied {
572 Applied::Downloaded(info) => info.TargetFullRelease.Version,
573 Applied::Pending => "pending".to_owned(),
574 };
575 log(format!("apply {what} {after:?}"));
576 if self.fails_apply.load(Ordering::SeqCst) {
577 return Err(Error::Velopack("apply failed".into()));
578 }
579 Ok(())
580 }
581 }
582
583 struct RecordedServices {
586 fails_stop: bool,
587 }
588
589 impl ServiceDriver for RecordedServices {
590 fn stop_running(&self) -> Result<(), String> {
591 log("stop services");
592 if self.fails_stop {
593 return Err("demo could not be stopped".to_owned());
594 }
595 Ok(())
596 }
597
598 fn restart_stopped(&self) {
599 log("restart services");
600 }
601 }
602
603 fn updater(spec: &'static PackageSpec, fake: Arc<Fake>) -> Updater {
604 Updater::with_backend(spec, fake, Arc::new(()))
605 }
606
607 fn updater_with_services(
608 spec: &'static PackageSpec,
609 fake: Arc<Fake>,
610 fails_stop: bool,
611 ) -> Updater {
612 Updater::with_backend(spec, fake, Arc::new(RecordedServices { fails_stop }))
613 }
614
615 fn wait_for(updater: &Updater, done: impl Fn(&PackageState) -> bool) -> PackageStatus {
617 let deadline = Instant::now() + Duration::from_secs(5);
618 loop {
619 let status = updater.status();
620 if done(&status.state) {
621 return status;
622 }
623 assert!(
624 Instant::now() < deadline,
625 "still {:?} after 5 seconds",
626 status.state
627 );
628 std::thread::sleep(Duration::from_millis(5));
629 }
630 }
631
632 fn settled(state: &PackageState) -> bool {
633 matches!(
634 state,
635 PackageState::UpToDate | PackageState::Ready { .. } | PackageState::Failed(_)
636 )
637 }
638
639 #[test]
640 fn a_found_update_is_downloaded_and_ready() {
641 let updater = updater(
642 spec(None),
643 Fake::new("1.0.0", vec![Ok(Some(info("1.1.0")))], None),
644 );
645 assert_eq!(
646 updater.status(),
647 PackageStatus {
648 current: Some(v("1.0.0")),
649 state: PackageState::Idle
650 }
651 );
652 updater.check_now();
653 let status = wait_for(&updater, settled);
654 assert_eq!(
655 status.state,
656 PackageState::Ready {
657 version: v("1.1.0")
658 }
659 );
660 assert_eq!(status.current, Some(v("1.0.0")));
661 }
662
663 #[test]
664 fn nothing_found_is_up_to_date() {
665 let updater = updater(spec(None), Fake::new("1.0.0", vec![Ok(None)], None));
666 updater.check_now();
667 assert_eq!(wait_for(&updater, settled).state, PackageState::UpToDate);
668 }
669
670 #[test]
671 fn a_check_error_is_failed_and_a_later_check_recovers() {
672 let updater = updater(
673 spec(None),
674 Fake::new(
675 "1.0.0",
676 vec![Err(Error::Network("offline".into())), Ok(None)],
677 None,
678 ),
679 );
680 updater.check_now();
681 let failed = wait_for(&updater, settled).state;
682 assert!(
683 matches!(&failed, PackageState::Failed(msg) if msg.contains("offline")),
684 "{failed:?}"
685 );
686 updater.check_now();
687 assert_eq!(
688 wait_for(&updater, |s| *s == PackageState::UpToDate).state,
689 PackageState::UpToDate
690 );
691 }
692
693 #[test]
694 fn apply_runs_before_apply_first() {
695 let updater = updater(
696 spec(Some(stopping)),
697 Fake::new("1.0.0", vec![Ok(Some(info("1.1.0")))], None),
698 );
699 updater.check_now();
700 wait_for(&updater, settled);
701 updater.apply(After::RestartApp).unwrap();
702 assert_eq!(apply_log(), ["before_apply", "apply 1.1.0 RestartApp"]);
703 }
704
705 #[test]
706 fn apply_stops_services_after_before_apply() {
707 let updater = updater_with_services(
708 spec(Some(stopping)),
709 Fake::new("1.0.0", vec![Ok(Some(info("1.1.0")))], None),
710 false,
711 );
712 updater.check_now();
713 wait_for(&updater, settled);
714 updater.apply(After::RestartApp).unwrap();
715 assert_eq!(
716 apply_log(),
717 ["before_apply", "stop services", "apply 1.1.0 RestartApp"]
718 );
719 }
720
721 #[test]
722 fn a_failed_apply_starts_the_stopped_services_again() {
723 let fake = Fake::new("1.0.0", vec![Ok(Some(info("1.1.0")))], Some("1.1.0"));
724 fake.fails_apply.store(true, Ordering::SeqCst);
725 let updater = updater_with_services(spec(Some(stopping)), fake, false);
726 updater.check_now();
727 wait_for(&updater, settled);
728 let err = updater.apply(After::RestartApp).unwrap_err();
729 assert!(matches!(err, Error::Velopack(_)), "{err:?}");
730 let err = updater.apply_pending(false).unwrap_err();
731 assert!(matches!(err, Error::Velopack(_)), "{err:?}");
732 let once = [
733 "before_apply",
734 "stop services",
735 "apply 1.1.0 RestartApp",
736 "restart services",
737 ];
738 let pending = [
739 "before_apply",
740 "stop services",
741 "apply pending RestartApp",
742 "restart services",
743 ];
744 assert_eq!(apply_log(), [once, pending].concat());
745 }
746
747 #[test]
748 fn a_failed_stop_applies_nothing() {
749 let updater = updater_with_services(
750 spec(Some(stopping)),
751 Fake::new("1.0.0", vec![Ok(Some(info("1.1.0")))], None),
752 true,
753 );
754 updater.check_now();
755 wait_for(&updater, settled);
756 let err = updater.apply(After::Exit).unwrap_err();
757 assert!(
758 matches!(err, Error::Prepare(ref m) if m == "demo could not be stopped"),
759 "{err:?}"
760 );
761 assert_eq!(apply_log(), ["before_apply", "stop services"]);
762 assert_eq!(
763 updater.status().state,
764 PackageState::Failed("demo could not be stopped".to_owned())
765 );
766 }
767
768 #[test]
769 fn a_second_apply_while_one_runs_is_refused() {
770 static UPDATER: std::sync::OnceLock<Updater> = std::sync::OnceLock::new();
771 fn applying_again() -> Result<(), String> {
774 let again = UPDATER.get().unwrap().apply(After::RestartApp);
775 log(format!("again {again:?}"));
776 Ok(())
777 }
778 let updater = updater(
779 spec(Some(applying_again)),
780 Fake::new("1.0.0", vec![Ok(Some(info("1.1.0")))], None),
781 );
782 UPDATER.set(updater.clone()).ok().unwrap();
783 updater.check_now();
784 wait_for(&updater, settled);
785 updater.apply(After::RestartApp).unwrap();
786 assert_eq!(
787 apply_log(),
788 ["again Err(Applying)", "apply 1.1.0 RestartApp"]
789 );
790 assert!(matches!(
792 updater.apply(After::RestartApp),
793 Err(Error::Applying)
794 ));
795 }
796
797 #[test]
798 fn a_failing_before_apply_applies_nothing() {
799 let updater = updater(
800 spec(Some(failing_to_stop)),
801 Fake::new("1.0.0", vec![Ok(Some(info("1.1.0")))], None),
802 );
803 updater.check_now();
804 wait_for(&updater, settled);
805 let err = updater.apply(After::Exit).unwrap_err();
806 assert!(
807 matches!(err, Error::Prepare(ref m) if m == "stop failed"),
808 "{err:?}"
809 );
810 assert_eq!(apply_log(), ["before_apply"]);
811 assert_eq!(
812 updater.status().state,
813 PackageState::Failed("stop failed".to_owned())
814 );
815 }
816
817 #[test]
818 fn apply_without_a_download_is_an_error() {
819 let updater = updater(spec(Some(stopping)), Fake::new("1.0.0", vec![], None));
820 let err = updater.apply(After::RestartApp).unwrap_err();
821 assert!(
822 matches!(err, Error::Velopack(ref m) if m == "nothing downloaded"),
823 "{err:?}"
824 );
825 assert!(apply_log().is_empty());
826 }
827
828 #[test]
829 fn a_pending_update_newer_than_current_is_applied_at_start() {
830 let updater = updater(
831 spec(Some(stopping)),
832 Fake::new("1.0.0", vec![], Some("1.1.0")),
833 );
834 assert!(updater.apply_pending(false).unwrap());
835 assert_eq!(apply_log(), ["before_apply", "apply pending RestartApp"]);
836 }
837
838 #[test]
839 fn a_pending_package_not_newer_is_left_alone() {
840 for pending in [Some("1.0.0"), Some("0.9.0"), None] {
841 let updater = updater(spec(Some(stopping)), Fake::new("1.0.0", vec![], pending));
842 assert!(!updater.apply_pending(false).unwrap());
843 }
844 assert!(apply_log().is_empty());
845 }
846
847 #[test]
848 fn a_start_by_velopack_s_updater_applies_no_pending_package() {
849 let updater = updater(
850 spec(Some(stopping)),
851 Fake::new("1.0.0", vec![], Some("1.1.0")),
852 );
853 assert!(!updater.apply_pending(true).unwrap());
854 assert!(apply_log().is_empty());
855 }
856
857 #[test]
858 fn on_change_is_called_on_every_state_change() {
859 let updater = updater(
860 spec(None),
861 Fake::new("1.0.0", vec![Ok(Some(info("1.1.0")))], None),
862 );
863 let first = Arc::new(AtomicUsize::new(0));
864 let second = Arc::new(AtomicUsize::new(0));
865 for counter in [&first, &second] {
866 let counter = Arc::clone(counter);
867 updater.on_change(move || {
868 counter.fetch_add(1, Ordering::SeqCst);
869 });
870 }
871 updater.check_now();
872 wait_for(&updater, settled);
873 let deadline = Instant::now() + Duration::from_secs(5);
876 while second.load(Ordering::SeqCst) < 5 && Instant::now() < deadline {
877 std::thread::sleep(Duration::from_millis(5));
878 }
879 assert_eq!(first.load(Ordering::SeqCst), 5);
880 assert_eq!(second.load(Ordering::SeqCst), 5);
881 }
882
883 #[test]
884 fn a_failing_check_after_a_download_keeps_it_ready() {
885 let fake = Fake::new(
886 "1.0.0",
887 vec![
888 Ok(Some(info("1.1.0"))),
889 Err(Error::Network("offline".into())),
890 ],
891 None,
892 );
893 let updater = updater(spec(Some(stopping)), Arc::clone(&fake));
894 let changes = Arc::new(AtomicUsize::new(0));
895 let counter = Arc::clone(&changes);
896 updater.on_change(move || {
897 counter.fetch_add(1, Ordering::SeqCst);
898 });
899 updater.check_now();
900 wait_for(&updater, settled);
901 updater.check_now();
904 updater.check_now();
905 let deadline = Instant::now() + Duration::from_secs(5);
906 while fake.checked.load(Ordering::SeqCst) < 3 {
907 assert!(Instant::now() < deadline, "the checks never ran");
908 std::thread::sleep(Duration::from_millis(5));
909 }
910 assert_eq!(
911 updater.status().state,
912 PackageState::Ready {
913 version: v("1.1.0")
914 }
915 );
916 assert_eq!(changes.load(Ordering::SeqCst), 5);
918 updater.apply(After::RestartApp).unwrap();
919 assert_eq!(apply_log(), ["before_apply", "apply 1.1.0 RestartApp"]);
920 }
921
922 #[test]
923 fn apply_is_retried_after_a_failing_before_apply() {
924 static REFUSE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
925 fn refusing_once() -> Result<(), String> {
926 log("before_apply");
927 if REFUSE.swap(false, Ordering::SeqCst) {
928 Err("stop failed".to_owned())
929 } else {
930 Ok(())
931 }
932 }
933 let updater = updater(
934 spec(Some(refusing_once)),
935 Fake::new("1.0.0", vec![Ok(Some(info("1.1.0")))], None),
936 );
937 updater.check_now();
938 wait_for(&updater, settled);
939 let err = updater.apply(After::Exit).unwrap_err();
940 assert!(matches!(err, Error::Prepare(_)), "{err:?}");
941 assert_eq!(
942 updater.status().state,
943 PackageState::Failed("stop failed".to_owned())
944 );
945 updater.apply(After::Exit).unwrap();
946 assert_eq!(
947 apply_log(),
948 ["before_apply", "before_apply", "apply 1.1.0 Exit"]
949 );
950 }
951
952 #[test]
953 fn a_check_after_a_refused_before_apply_is_ready_again() {
954 let fake = Fake::new(
955 "1.0.0",
956 vec![
957 Ok(Some(info("1.1.0"))),
958 Err(Error::Network("offline".into())),
959 ],
960 None,
961 );
962 let updater = updater(spec(Some(failing_to_stop)), Arc::clone(&fake));
963 updater.check_now();
964 wait_for(&updater, settled);
965 updater.apply(After::RestartApp).unwrap_err();
966 assert_eq!(
967 updater.status().state,
968 PackageState::Failed("stop failed".to_owned())
969 );
970 updater.check_now();
972 let status = wait_for(&updater, |s| matches!(s, PackageState::Ready { .. }));
973 assert_eq!(
974 status.state,
975 PackageState::Ready {
976 version: v("1.1.0")
977 }
978 );
979 }
980
981 #[test]
982 fn run_in_background_checks_at_once_and_stops_with_the_last_updater() {
983 let fake = Fake::new("1.0.0", vec![Ok(None)], None);
984 let updater = updater(spec(None), Arc::clone(&fake));
985 updater.run_in_background(Duration::from_secs(3600));
986 assert_eq!(wait_for(&updater, settled).state, PackageState::UpToDate);
987 let clone = updater.clone();
988 drop(updater);
989 drop(clone);
990 let deadline = Instant::now() + Duration::from_secs(5);
993 while Arc::strong_count(&fake) > 1 {
994 assert!(Instant::now() < deadline, "the worker outlived the updater");
995 std::thread::sleep(Duration::from_millis(5));
996 }
997 }
998}