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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! The host that talks to the real terminal and the real process.
use std::io::{IsTerminal, Write, stderr, stdout};
use cargo_gamma_lib::Host;
/// Host that talks to the real terminal and the real process.
#[derive(Debug, Clone, Copy, Default)]
pub struct RealHost;
fn progress_is_terminal(_stdout: impl FnOnce() -> bool, stderr: impl FnOnce() -> bool) -> bool {
stderr()
}
fn diagnostic_is_terminal() -> bool {
#[cfg(test)]
if let Some(terminal) = tests::DIAGNOSTIC_TERMINAL.get() {
return terminal;
}
// #[gamma::skip(bool_expr.negate, tag = "native", reason = "the native stderr attachment is host state; DIAGNOSTIC_TERMINAL exercises both answers through the same host policy")]
stderr().is_terminal()
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl Host for RealHost {
fn output(&mut self) -> impl Write {
stdout()
}
fn error(&mut self) -> impl Write {
stderr()
}
// #[gamma::skip(fn_value.bool_false, fn_value.bool_true, reason = "the answer belongs to the invoking terminal and tests validly run both attached and detached")]
fn is_terminal(&self) -> bool {
progress_is_terminal(|| stdout().is_terminal(), diagnostic_is_terminal)
}
// #[gamma::skip(fn_value.none, reason = "terminal dimensions are optional and None is already the valid fallback when the host cannot report them")]
fn terminal_width(&self) -> Option<u16> {
terminal_size::terminal_size().map(|(width, _)| width.0)
}
/// This host is the real process, so `current_exe` is cargo-gamma and replacing it is safe.
fn may_replace_process(&self) -> bool {
true
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
thread_local! {
pub(super) static DIAGNOSTIC_TERMINAL: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
}
#[test]
fn survivor_real_host_preserves_both_native_terminal_answers() {
struct Restore(Option<bool>);
impl Drop for Restore {
fn drop(&mut self) {
DIAGNOSTIC_TERMINAL.set(self.0);
}
}
let _restore = Restore(DIAGNOSTIC_TERMINAL.get());
for terminal in [true, false] {
DIAGNOSTIC_TERMINAL.set(Some(terminal));
assert_eq!(RealHost.is_terminal(), terminal);
}
}
use std::env;
use std::process::Command;
use super::*;
/// A variable the parent gives the child, whose exact value `RealHost` has to return.
const PRESENT_VAR: &str = "GAMMA_REAL_HOST_PRESENT";
/// What the parent sets it to, chosen so that no ambient environment could supply it.
const PRESENT_VALUE: &str = "a value only this test chose";
/// A variable the parent removes from the child, whatever the launching environment held.
const ABSENT_VAR: &str = "GAMMA_REAL_HOST_ABSENT";
/// The real host is driven through the trait, because every other test in the workspace is not.
///
/// The fakes in `cargo_gamma_lib::testing` are only worth as much as their agreement with this
/// implementation, and that agreement is otherwise assumed: `RealHost` is excluded from coverage
/// and is constructed in exactly one place, `main`, which no test calls. Nothing here asserts a
/// particular terminal — the suite runs both on a tty and in CI — but it does assert the parts
/// of the contract that hold either way, and it makes the type reachable so that a signature
/// that stopped satisfying `Host` fails a test rather than only the release build.
#[test]
fn the_real_host_answers_the_same_contract_the_fakes_stand_in_for() {
let mut host = RealHost;
// Both streams accept a write. An empty one is used because a test that printed would be
// indistinguishable from a test that failed noisily, and the write path is what is under
// test, not the bytes.
host.output().write_all(b"").expect("stdout accepts a write");
host.error().write_all(b"").expect("stderr accepts a write");
// This is the one answer that differs from every fake, and it is the answer that decides
// whether the tool may replace its own process. It is true here precisely because this host
// is the real process; a fake saying so would relaunch the test harness.
assert!(host.may_replace_process());
}
#[test]
fn progress_uses_stderr_terminal_state_independently_of_stdout() {
for output in [false, true] {
for error in [false, true] {
let mut output_queries = 0;
let mut error_queries = 0;
let actual = progress_is_terminal(
|| {
output_queries += 1;
output
},
|| {
error_queries += 1;
error
},
);
assert_eq!(actual, error);
assert_eq!(output_queries, 0);
assert_eq!(error_queries, 1);
}
}
}
/// The real host reads the real environment rather than overriding `env`.
///
/// Driven through a child process this test launches itself, because the two states the
/// host has to tell apart — set, and not set — are properties of a process environment, and
/// this process's own is whatever the developer or the CI runner happened to export. Asserting
/// against it reads correct code as broken in a sanitized environment, and reads broken code as
/// correct whenever the ambient process happens to agree. Writing this process's environment to
/// arrange the two states is not an option either: `setenv` races every other thread's read,
/// which is why nothing in this workspace does it.
///
/// So the states are arranged on the child's `Command` — one variable given a value only this
/// test chose, one removed outright — and the child reports what the host returned for each.
#[test]
fn the_real_host_reads_the_process_environment() {
let executable = env::current_exe().expect("the test binary knows its own path");
// `--exact` confines the child to the reporting helper, and `--nocapture` exposes its
// printed protocol to `Command::output()`.
let output = Command::new(&executable)
.args(["--exact", "real_host::tests::the_child_reports_what_the_host_read", "--nocapture"])
.env(PRESENT_VAR, PRESENT_VALUE)
.env_remove(ABSENT_VAR)
.output()
.expect("the child runs");
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
let text = String::from_utf8_lossy(&output.stdout);
// The value is compared, not merely its presence: a host that answered `Some` with the
// name, the empty string, or another variable's value would satisfy a presence check.
assert!(text.contains(&format!("present={:?}", Some(PRESENT_VALUE))), "{text}");
assert!(text.contains("absent=None"), "{text}");
}
/// Reports what [`RealHost`] read for each variable the test above arranged.
///
/// Only meaningful when launched by that test; harmless on its own, where it prints two
/// absences and asserts nothing.
#[test]
fn the_child_reports_what_the_host_read() {
let host = RealHost;
println!("present={:?} absent={:?}", host.env(PRESENT_VAR), host.env(ABSENT_VAR));
}
}