1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! What:
//! Controls for the guard around the linked linter:
//! a result comes back, a panic comes back as text, a job that shows no sign of work for the window is abandoned,
//! and a long wait prints notices that name what it waits for.
//! Why:
//! The linter shares the wrapper's address space, so these are the only things that keep a planted infinite loop
//! or a panic from hanging or crashing a commit.
//! Each control plants its fault through the job the guard runs,
//! and each fails against a guard that runs the job on the caller's thread.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! // expect(await runGuarded(() => { throw new Error('planted'); }, ...)).rejects.toEqual({ kind: 'panicked' });
//! ```
use super::{GuardFailure, run_guarded};
use crate::test_support::{ActionSink, SharedSink};
use crate::wait_progress::Cadence;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::channel;
use std::sync::Arc;
use std::time::Duration;
/// A quiet window short enough for a test to wait out. It is under the sampling interval of the meter, which is fine for a
/// blocked job: it shows nothing at any sample.
const QUIET: Duration = Duration::from_millis(100);
/// A window no control reaches: the jobs that return, panic or are released by a notice always end first.
const LONG_WINDOW: Duration = Duration::from_secs(60);
/// A cadence whose notices come at the pace of the short window.
const FAST: Cadence = Cadence {
grace: Duration::from_millis(5),
interval: Duration::from_millis(5),
};
/// A job's value comes back, and the job ran on another thread.
#[test]
fn a_finished_job_returns_its_value_from_another_thread() {
let caller = std::thread::current().id();
let outcome: Result<bool, GuardFailure> = run_guarded(
move || {
return std::thread::current().id() != caller;
},
LONG_WINDOW,
FAST,
"a job",
&mut std::io::sink(),
);
assert_eq!(outcome, Ok(true));
}
/// A panic in the job comes back as its message, and the caller is still running.
#[test]
fn a_panic_comes_back_as_text() {
const MESSAGE: &str = "planted panic";
let outcome: Result<(), GuardFailure> = run_guarded(
|| {
panic!("{MESSAGE}");
},
LONG_WINDOW,
FAST,
"a job",
&mut std::io::sink(),
);
assert_eq!(outcome, Err(GuardFailure::Panicked(String::from(MESSAGE))));
}
/// A job that shows no sign of work for the window is abandoned:
/// the guard answers while the job is still blocked,
/// and the abandoned thread ends once it is released.
#[test]
fn a_job_that_shows_no_sign_of_work_is_abandoned() {
let (release, blocked) = channel::<()>();
let finished: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
let marker: Arc<AtomicBool> = Arc::clone(&finished);
let outcome: Result<(), GuardFailure> = run_guarded(
move || {
// The planted stall: blocked until the test lets go, as an infinite loop would be.
let _ = blocked.recv();
marker.store(true, Ordering::SeqCst);
},
QUIET,
FAST,
"a job",
&mut std::io::sink(),
);
assert_eq!(outcome, Err(GuardFailure::Quiet(QUIET)));
assert!(!finished.load(Ordering::SeqCst), "the guard answered while the job was blocked");
drop(release);
}
/// A long wait prints notices that name what it waits for and where it gives up;
/// the third notice releases the job, so the wait ends on that event, whatever the host's speed.
#[test]
fn a_long_wait_prints_notices_naming_the_file() {
let (release, blocked) = channel::<()>();
let sink: SharedSink = SharedSink::default();
let mut counted: ActionSink = ActionSink::new(&sink, 3, move || {
let _ = release.send(());
});
let waiting_for: &str = "monochromatic-lint to check docs/a.md";
let outcome: Result<(), GuardFailure> = run_guarded(
move || {
let _ = blocked.recv();
},
LONG_WINDOW,
FAST,
waiting_for,
&mut counted,
);
assert_eq!(outcome, Ok(()));
let text: String = sink.text();
assert!(text.lines().count() >= 3, "{text}");
for line in text.lines() {
assert!(line.starts_with(format!("cli-git: still waiting for {waiting_for} after ").as_str()), "{text}");
assert!(
line.contains(format!("giving up on it only after {} seconds with no sign of work", LONG_WINDOW.as_secs()).as_str()),
"{text}"
);
}
}