1use sysknife_types::JobState;
14
15pub fn allowed_transition(current: &JobState, next: &JobState) -> bool {
16 matches!(
17 (current, next),
18 (JobState::Queued, JobState::Running)
19 | (JobState::Queued, JobState::Canceled)
20 | (JobState::Running, JobState::Succeeded)
21 | (JobState::Running, JobState::Failed)
22 | (JobState::Running, JobState::Canceled)
23 | (JobState::Running, JobState::RolledBack)
24 | (JobState::Running, JobState::NeedsReboot)
25 )
26}
27
28pub fn is_terminal(state: &JobState) -> bool {
30 matches!(
31 state,
32 JobState::Succeeded
33 | JobState::Failed
34 | JobState::Canceled
35 | JobState::RolledBack
36 | JobState::NeedsReboot
37 )
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 const ALL: &[JobState] = &[
45 JobState::Queued,
46 JobState::Running,
47 JobState::Succeeded,
48 JobState::Failed,
49 JobState::Canceled,
50 JobState::RolledBack,
51 JobState::NeedsReboot,
52 ];
53
54 const ALLOWED: &[(JobState, JobState)] = &[
56 (JobState::Queued, JobState::Running),
57 (JobState::Queued, JobState::Canceled),
58 (JobState::Running, JobState::Succeeded),
59 (JobState::Running, JobState::Failed),
60 (JobState::Running, JobState::Canceled),
61 (JobState::Running, JobState::RolledBack),
62 (JobState::Running, JobState::NeedsReboot),
63 ];
64
65 #[test]
66 fn every_permitted_edge_is_allowed() {
67 for (from, to) in ALLOWED {
68 assert!(
69 allowed_transition(from, to),
70 "{from:?} -> {to:?} must be permitted"
71 );
72 }
73 }
74
75 #[test]
78 fn every_other_edge_is_refused() {
79 for from in ALL {
80 for to in ALL {
81 let permitted = ALLOWED.iter().any(|(f, t)| f == from && t == to);
82 assert_eq!(
83 allowed_transition(from, to),
84 permitted,
85 "{from:?} -> {to:?}"
86 );
87 }
88 }
89 }
90
91 #[test]
92 fn terminal_states_cannot_be_left() {
93 for from in ALL.iter().filter(|s| is_terminal(s)) {
94 for to in ALL {
95 assert!(
96 !allowed_transition(from, to),
97 "terminal {from:?} must not transition to {to:?}"
98 );
99 }
100 }
101 }
102
103 #[test]
104 fn queued_and_running_are_not_terminal() {
105 assert!(!is_terminal(&JobState::Queued));
106 assert!(!is_terminal(&JobState::Running));
107 }
108
109 #[test]
110 fn a_job_cannot_restart_after_finishing() {
111 assert!(!allowed_transition(
115 &JobState::NeedsReboot,
116 &JobState::Running
117 ));
118 assert!(!allowed_transition(
119 &JobState::Succeeded,
120 &JobState::Running
121 ));
122 assert!(!allowed_transition(&JobState::Canceled, &JobState::Running));
123 }
124}