Skip to content

Commit cf251e3

Browse files
amitksingh1490forge-code-agentautofix-ci[bot]
authored
fix(ui): prevent spinner cycling causing early exit in model selection (#2086)
Co-authored-by: ForgeCode <noreply@forgecode.dev> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 4ce4cf7 commit cf251e3

3 files changed

Lines changed: 48 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/forge_spinner/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ colored.workspace = true
99
tokio.workspace = true
1010
indicatif = "0.18.0"
1111
rand = "0.9.2"
12+
13+
[dev-dependencies]
14+
pretty_assertions.workspace = true

crates/forge_spinner/src/lib.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,22 @@ pub struct SpinnerManager {
1616
start_time: Option<Instant>,
1717
message: Option<String>,
1818
tracker: Option<JoinHandle<()>>,
19+
#[cfg(test)]
20+
tick_counter: Option<std::sync::Arc<std::sync::atomic::AtomicU64>>,
1921
}
2022

2123
impl SpinnerManager {
2224
pub fn new() -> Self {
2325
Self::default()
2426
}
2527

28+
#[cfg(test)]
29+
pub fn test_with_tick_counter(
30+
tick_counter: std::sync::Arc<std::sync::atomic::AtomicU64>,
31+
) -> Self {
32+
Self { tick_counter: Some(tick_counter), ..Self::default() }
33+
}
34+
2635
/// Start the spinner with a message
2736
pub fn start(&mut self, message: Option<&str>) -> Result<()> {
2837
self.stop(None)?;
@@ -80,12 +89,18 @@ impl SpinnerManager {
8089
let spinner_clone = self.spinner.clone();
8190
let start_time_clone = self.start_time;
8291
let message_clone = self.message.clone();
92+
#[cfg(test)]
93+
let tick_counter_clone = self.tick_counter.clone();
8394

8495
// Spwan tracker to keep the track of time in sec.
8596
self.tracker = Some(tokio::spawn(async move {
8697
let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(500));
8798
loop {
8899
interval.tick().await;
100+
#[cfg(test)]
101+
if let Some(counter) = &tick_counter_clone {
102+
counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
103+
}
89104
// Update the spinner with the current elapsed time
90105
if let (Some(spinner), Some(start_time), Some(message)) =
91106
(&spinner_clone, start_time_clone, &message_clone)
@@ -135,6 +150,7 @@ impl SpinnerManager {
135150

136151
// Tracker task will be dropped here.
137152
if let Some(a) = self.tracker.take() {
153+
a.abort();
138154
drop(a)
139155
}
140156
self.tracker = None;
@@ -164,3 +180,31 @@ impl SpinnerManager {
164180
self.write_with_restart(message, |msg| eprintln!("{msg}"))
165181
}
166182
}
183+
#[cfg(test)]
184+
mod tests {
185+
use std::sync::Arc;
186+
use std::sync::atomic::{AtomicU64, Ordering};
187+
188+
use pretty_assertions::assert_eq;
189+
190+
use super::SpinnerManager;
191+
192+
#[tokio::test]
193+
async fn test_spinner_tracker_task_is_stopped_on_stop() {
194+
let fixture_counter = Arc::new(AtomicU64::new(0));
195+
let mut fixture_spinner = SpinnerManager::test_with_tick_counter(fixture_counter.clone());
196+
197+
fixture_spinner.start(Some("Test")).unwrap();
198+
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
199+
200+
let actual_before_stop = fixture_counter.load(Ordering::SeqCst);
201+
assert!(actual_before_stop > 0);
202+
203+
fixture_spinner.stop(None).unwrap();
204+
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
205+
206+
let actual_after_stop = fixture_counter.load(Ordering::SeqCst);
207+
let expected = actual_before_stop;
208+
assert_eq!(actual_after_stop, expected);
209+
}
210+
}

0 commit comments

Comments
 (0)