|
1 |
| -use anyhow::{bail, Context, Error, Result}; |
| 1 | +use anyhow::{bail, Context, Result}; |
2 | 2 | use std::{
|
3 | 3 | env,
|
4 | 4 | fs::{self, File},
|
5 |
| - io::{Read, StdoutLock, Write}, |
| 5 | + io::{self, Read, StdoutLock, Write}, |
6 | 6 | path::Path,
|
7 | 7 | process::{Command, Stdio},
|
8 | 8 | thread,
|
@@ -35,6 +35,12 @@ pub enum StateFileStatus {
|
35 | 35 | NotRead,
|
36 | 36 | }
|
37 | 37 |
|
| 38 | +enum AllExercisesCheck { |
| 39 | + Pending(usize), |
| 40 | + AllDone, |
| 41 | + CheckedUntil(usize), |
| 42 | +} |
| 43 | + |
38 | 44 | pub struct AppState {
|
39 | 45 | current_exercise_ind: usize,
|
40 | 46 | exercises: Vec<Exercise>,
|
@@ -340,59 +346,80 @@ impl AppState {
|
340 | 346 | }
|
341 | 347 | }
|
342 | 348 |
|
343 |
| - /// Mark the current exercise as done and move on to the next pending exercise if one exists. |
344 |
| - /// If all exercises are marked as done, run all of them to make sure that they are actually |
345 |
| - /// done. If an exercise which is marked as done fails, mark it as pending and continue on it. |
346 |
| - pub fn done_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<ExercisesProgress> { |
347 |
| - let exercise = &mut self.exercises[self.current_exercise_ind]; |
348 |
| - if !exercise.done { |
349 |
| - exercise.done = true; |
350 |
| - self.n_done += 1; |
351 |
| - } |
352 |
| - |
353 |
| - if let Some(ind) = self.next_pending_exercise_ind() { |
354 |
| - self.set_current_exercise_ind(ind)?; |
355 |
| - return Ok(ExercisesProgress::NewPending); |
356 |
| - } |
357 |
| - |
| 349 | + // Return the exercise index of the first pending exercise found. |
| 350 | + fn check_all_exercises(&self, stdout: &mut StdoutLock) -> Result<Option<usize>> { |
358 | 351 | stdout.write_all(RERUNNING_ALL_EXERCISES_MSG)?;
|
359 |
| - |
360 | 352 | let n_exercises = self.exercises.len();
|
361 | 353 |
|
362 |
| - let pending_exercise_ind = thread::scope(|s| { |
| 354 | + let status = thread::scope(|s| { |
363 | 355 | let handles = self
|
364 | 356 | .exercises
|
365 |
| - .iter_mut() |
366 |
| - .map(|exercise| { |
367 |
| - s.spawn(|| { |
368 |
| - let success = exercise.run_exercise(None, &self.cmd_runner)?; |
369 |
| - exercise.done = success; |
370 |
| - Ok::<_, Error>(success) |
371 |
| - }) |
372 |
| - }) |
| 357 | + .iter() |
| 358 | + .map(|exercise| s.spawn(|| exercise.run_exercise(None, &self.cmd_runner))) |
373 | 359 | .collect::<Vec<_>>();
|
374 | 360 |
|
375 | 361 | for (exercise_ind, handle) in handles.into_iter().enumerate() {
|
376 | 362 | write!(stdout, "\rProgress: {exercise_ind}/{n_exercises}")?;
|
377 | 363 | stdout.flush()?;
|
378 | 364 |
|
379 |
| - let success = handle.join().unwrap()?; |
| 365 | + let Ok(success) = handle.join().unwrap() else { |
| 366 | + return Ok(AllExercisesCheck::CheckedUntil(exercise_ind)); |
| 367 | + }; |
| 368 | + |
380 | 369 | if !success {
|
381 |
| - stdout.write_all(b"\n\n")?; |
382 |
| - return Ok(Some(exercise_ind)); |
| 370 | + return Ok(AllExercisesCheck::Pending(exercise_ind)); |
383 | 371 | }
|
384 | 372 | }
|
385 | 373 |
|
386 |
| - Ok::<_, Error>(None) |
| 374 | + Ok::<_, io::Error>(AllExercisesCheck::AllDone) |
387 | 375 | })?;
|
388 | 376 |
|
389 |
| - if let Some(pending_exercise_ind) = pending_exercise_ind { |
| 377 | + let mut exercise_ind = match status { |
| 378 | + AllExercisesCheck::Pending(exercise_ind) => return Ok(Some(exercise_ind)), |
| 379 | + AllExercisesCheck::AllDone => return Ok(None), |
| 380 | + AllExercisesCheck::CheckedUntil(ind) => ind, |
| 381 | + }; |
| 382 | + |
| 383 | + // We got an error while checking all exercises in parallel. |
| 384 | + // This could be because we exceeded the limit of open file descriptors. |
| 385 | + // Therefore, try to continue the check sequentially. |
| 386 | + for exercise in &self.exercises[exercise_ind..] { |
| 387 | + write!(stdout, "\rProgress: {exercise_ind}/{n_exercises}")?; |
| 388 | + stdout.flush()?; |
| 389 | + |
| 390 | + let success = exercise.run_exercise(None, &self.cmd_runner)?; |
| 391 | + if !success { |
| 392 | + return Ok(Some(exercise_ind)); |
| 393 | + } |
| 394 | + |
| 395 | + exercise_ind += 1; |
| 396 | + } |
| 397 | + |
| 398 | + Ok(None) |
| 399 | + } |
| 400 | + |
| 401 | + /// Mark the current exercise as done and move on to the next pending exercise if one exists. |
| 402 | + /// If all exercises are marked as done, run all of them to make sure that they are actually |
| 403 | + /// done. If an exercise which is marked as done fails, mark it as pending and continue on it. |
| 404 | + pub fn done_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<ExercisesProgress> { |
| 405 | + let exercise = &mut self.exercises[self.current_exercise_ind]; |
| 406 | + if !exercise.done { |
| 407 | + exercise.done = true; |
| 408 | + self.n_done += 1; |
| 409 | + } |
| 410 | + |
| 411 | + if let Some(ind) = self.next_pending_exercise_ind() { |
| 412 | + self.set_current_exercise_ind(ind)?; |
| 413 | + return Ok(ExercisesProgress::NewPending); |
| 414 | + } |
| 415 | + |
| 416 | + if let Some(pending_exercise_ind) = self.check_all_exercises(stdout)? { |
| 417 | + stdout.write_all(b"\n\n")?; |
| 418 | + |
390 | 419 | self.current_exercise_ind = pending_exercise_ind;
|
391 |
| - self.n_done = self |
392 |
| - .exercises |
393 |
| - .iter() |
394 |
| - .filter(|exercise| exercise.done) |
395 |
| - .count() as u16; |
| 420 | + self.exercises[pending_exercise_ind].done = false; |
| 421 | + // All exercises were marked as done. |
| 422 | + self.n_done -= 1; |
396 | 423 | self.write()?;
|
397 | 424 | return Ok(ExercisesProgress::NewPending);
|
398 | 425 | }
|
|
0 commit comments