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
//! What:
//! Test-only fault points of the resident service: `CLI_GIT_TEST_ONLY_SERVICE_FAULT=<point>` ends the service with
//! `SIGKILL` the moment it reaches the named point of a batch.
//! Why:
//! The kill tests need a deterministic instant, as the transaction phase markers give the wrapper
//! (`transaction_phase.rs`). The marker is compiled into the release executable, and only the explicit variable arms it.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! if (process.env.CLI_GIT_TEST_ONLY_SERVICE_FAULT === point) process.kill(process.pid, 'SIGKILL');
//! ```
/// Environment variable arming one fault point.
pub const FAULT_VARIABLE: &str = "CLI_GIT_TEST_ONLY_SERVICE_FAULT";
/// The instants of a batch at which a kill is tested, in the order a batch reaches them.
pub const FAULT_POINTS: [&str; 5] = ["after-objects", "after-intent", "after-ref", "after-index", "before-ack"];
/// What:
/// Ends the process if `point` is the armed one.
/// Why:
/// A kill at a point is the strongest crash a test can name without injecting a system call failure.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// reach('after-intent')
/// ```
pub fn reach(point: &str) {
if std::env::var(FAULT_VARIABLE).is_ok_and(|armed| { return armed == point; }) {
crate::service_log!("test fault at {point}");
#[cfg(unix)]
// SAFETY: `kill` takes no pointers; the process ends itself.
unsafe {
libc::kill(libc::getpid(), libc::SIGKILL);
}
std::process::exit(137);
}
}
/// Environment variable naming a directory in which the service leaves one file for each commit request it has queued.
pub const QUEUED_DIRECTORY_VARIABLE: &str = "CLI_GIT_TEST_ONLY_SERVICE_QUEUED_DIRECTORY";
/// What:
/// Leaves a file named for request `id` in the directory the variable names, when it is armed.
/// Why:
/// A control that sends requests in a set order needs to know that the service holds one before it sends the next; a pause
/// of its own can only guess. The file appears after the request is in the queue, so what the control sees is the service's own
/// state. Only the explicit variable arms it, as with the fault points.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// if (process.env.CLI_GIT_TEST_ONLY_SERVICE_QUEUED_DIRECTORY) writeFile(join(dir, safe(id)), '')
/// ```
pub fn queued(id: &str) {
let Some(directory) = std::env::var_os(QUEUED_DIRECTORY_VARIABLE) else { return };
let name: String = id.chars().map(|character| { return if character.is_ascii_alphanumeric() { character } else { '_' }; }).collect();
let path: std::path::PathBuf = std::path::Path::new(&directory).join(format!("queued-{name}"));
if let Err(error) = std::fs::write(&path, b"") {
crate::service_warn!("cannot leave the queued-request file {}: {error}", path.display());
}
}