use saphyr::{LoadableYamlNode, Yaml};
use std::path::{Path, PathBuf};
fn manifest_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn ci_yml() -> PathBuf {
manifest_dir()
.join(".github")
.join("workflows")
.join("ci.yml")
}
fn read_or_panic(path: &Path) -> String {
std::fs::read_to_string(path).unwrap_or_else(|e| {
panic!(
"cannot read {}: {e}. This guard must fail rather than skip: a \
version of it that returned early here would be the same \
blindness it exists to prevent.",
path.display()
)
})
}
fn command_lines(script: &str) -> Vec<String> {
let mut out = Vec::new();
let mut pending = String::new();
let mut continuing = false;
for raw in script.lines() {
let line = if continuing { raw } else { raw.trim_start() };
let (text, continues) = match comment_start(line) {
Some(at) => (&line[..at], false),
None => match line.strip_suffix('\\') {
Some(body) if body.chars().rev().take_while(|c| *c == '\\').count() % 2 == 0 => {
(body, true)
}
_ => (line, false),
},
};
pending.push_str(text);
let ends_in_a_list_operator = !continues
&& shell_commands(&pending)
.last()
.is_some_and(|(_, sep)| matches!(sep, Sep::And | Sep::Or | Sep::Pipe));
if ends_in_a_list_operator {
pending.push(' ');
}
continuing = continues || ends_in_a_list_operator;
if continuing {
continue;
}
let logical = pending.trim();
if !logical.is_empty() {
out.push(logical.to_string());
}
pending.clear();
}
let logical = pending.trim();
if !logical.is_empty() {
out.push(logical.to_string());
}
out
}
fn comment_start(line: &str) -> Option<usize> {
let mut quote: Option<char> = None;
let mut at_word_start = true;
for (index, c) in line.char_indices() {
if let Some(q) = quote {
if c == q {
quote = None;
}
at_word_start = false;
continue;
}
match c {
'#' if at_word_start => return Some(index),
'\'' | '"' => {
quote = Some(c);
at_word_start = false;
}
';' | '&' | '|' | '(' | ')' => at_word_start = true,
c if c.is_whitespace() => at_word_start = true,
_ => at_word_start = false,
}
}
None
}
fn set_disables_errexit(words: &[String]) -> bool {
let mut words = words.iter().map(String::as_str);
if words.next() != Some("set") {
return false;
}
let mut expecting_option_name = false;
for word in words {
if expecting_option_name {
if word == "errexit" {
return true;
}
expecting_option_name = false;
continue;
}
if word == "+o" {
expecting_option_name = true;
continue;
}
if let Some(flags) = word.strip_prefix('+') {
if flags.contains('e') {
return true;
}
}
}
false
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Sep {
End,
And,
Or,
Semi,
Pipe,
Amp,
}
fn end_of_substitution(chars: &[char], open: usize) -> Option<usize> {
debug_assert_eq!(chars.get(open), Some(&'('), "open must index the `(`");
let mut depth = 0usize;
let mut quote: Option<char> = None;
let mut j = open;
while j < chars.len() {
let c = chars[j];
match quote {
Some('\'') => {
if c == '\'' {
quote = None;
}
}
Some(q) => {
if c == '\\' && j + 1 < chars.len() {
j += 2;
continue;
}
if c == q {
quote = None;
}
}
None => {
if c == '\\' && j + 1 < chars.len() {
j += 2;
continue;
}
if c == '`' {
match end_of_backticks(chars, j) {
Some(close) => {
j = close + 1;
continue;
}
None => return None,
}
}
if c == '$' && j + 1 < chars.len() && chars[j + 1] == '{' {
match end_of_braces(chars, j + 1) {
Some(close) => {
j = close + 1;
continue;
}
None => return None,
}
}
match c {
'\'' | '"' => quote = Some(c),
'(' => depth += 1,
')' => {
depth = depth.saturating_sub(1);
if depth == 0 {
return Some(j);
}
}
_ => {}
}
}
}
j += 1;
}
None
}
fn end_of_braces(chars: &[char], open: usize) -> Option<usize> {
debug_assert_eq!(chars.get(open), Some(&'{'), "open must index the brace");
let mut depth = 0usize;
let mut j = open;
while j < chars.len() {
if chars[j] == '$' && j + 1 < chars.len() && chars[j + 1] == '(' {
match end_of_substitution(chars, j + 1) {
Some(close) => {
j = close + 1;
continue;
}
None => return None,
}
}
if chars[j] == '`' {
match end_of_backticks(chars, j) {
Some(close) => {
j = close + 1;
continue;
}
None => return None,
}
}
if chars[j] == '$' && j + 1 < chars.len() && chars[j + 1] == '{' {
depth += 1;
j += 2;
continue;
}
match chars[j] {
'\\' if j + 1 < chars.len() => {
j += 1;
}
'{' if j == open => depth += 1,
'}' => {
depth = depth.saturating_sub(1);
if depth == 0 {
return Some(j);
}
}
_ => {}
}
j += 1;
}
None
}
fn end_of_backticks(chars: &[char], open: usize) -> Option<usize> {
debug_assert_eq!(chars.get(open), Some(&'`'), "open must index the backtick");
let mut j = open + 1;
while j < chars.len() {
if chars[j] == '\\' && j + 1 < chars.len() {
j += 2;
continue;
}
if chars[j] == '`' {
return Some(j);
}
j += 1;
}
None
}
fn shell_commands(line: &str) -> Vec<(Vec<String>, Sep)> {
scan_shell(line).commands
}
struct ShellScan {
commands: Vec<(Vec<String>, Sep)>,
substitutions: Vec<Vec<Option<String>>>,
plain: bool,
}
fn scan_shell(line: &str) -> ShellScan {
let chars: Vec<char> = line.chars().collect();
let mut out: Vec<(Vec<String>, Sep)> = Vec::new();
let mut substitutions: Vec<Vec<Option<String>>> = Vec::new();
let mut spans: Vec<Option<String>> = Vec::new();
let mut words: Vec<String> = Vec::new();
let mut word = String::new();
let mut started = false;
let mut quote: Option<char> = None;
let mut i = 0;
let mut plain = true;
macro_rules! end_word {
() => {
if started {
words.push(std::mem::take(&mut word));
started = false;
}
};
}
while i < chars.len() {
let c = chars[i];
if let Some(q) = quote {
if q == '"' && c == '\\' && i + 1 < chars.len() {
i += 2;
continue;
}
if q == '"'
&& (c == '`'
|| (c == '$'
&& chars.get(i + 1) == Some(&'(')
&& chars.get(i + 2) != Some(&'(')))
{
spans.push(None);
}
if c == q {
quote = None;
}
i += 1;
continue;
}
if matches!(c, '$' | '<' | '>') && i + 1 < chars.len() && chars[i + 1] == '(' {
started = true;
i = match end_of_substitution(&chars, i + 1) {
Some(close) => {
let inner: String = chars[i + 2..close].iter().collect();
if c != '$' {
spans.push(None);
} else if !inner.starts_with('(') {
spans.push(Some(inner));
} else if runs_a_substitution(&inner) {
spans.push(None);
}
close + 1
}
None => {
spans.push(None);
plain = false;
chars.len()
}
};
continue;
}
if c == '`' {
started = true;
i = match end_of_backticks(&chars, i) {
Some(close) => {
spans.push(Some(chars[i + 1..close].iter().collect()));
close + 1
}
None => {
spans.push(None);
plain = false;
chars.len()
}
};
continue;
}
if c == '$' && i + 1 < chars.len() && chars[i + 1] == '{' {
started = true;
i = match end_of_braces(&chars, i + 1) {
Some(close) => {
let text: String = chars[i..=close].iter().collect();
if runs_a_substitution(&text[2..]) {
spans.push(None);
}
close + 1
}
None => {
spans.push(None);
plain = false;
chars.len()
}
};
continue;
}
match c {
'\\' if i + 1 < chars.len() => {
word.push(chars[i + 1]);
started = true;
i += 2;
}
'\'' | '"' => {
quote = Some(c);
started = true;
i += 1;
}
'&' | '|' | ';' | '(' | ')' => {
if matches!(c, '(' | ')') {
plain = false;
}
if c == '&' && started && (word.ends_with('>') || word.ends_with('<')) {
word.push(c);
i += 1;
continue;
}
let doubled = i + 1 < chars.len() && chars[i + 1] == c;
let sep = match (c, doubled) {
('&', true) => Sep::And,
('&', false) => Sep::Amp,
('|', true) => Sep::Or,
('|', false) => Sep::Pipe,
_ => Sep::Semi,
};
end_word!();
if !words.is_empty() {
out.push((std::mem::take(&mut words), sep));
substitutions.push(std::mem::take(&mut spans));
}
i += if doubled && c != ';' { 2 } else { 1 };
}
c if c.is_whitespace() => {
end_word!();
i += 1;
}
_ => {
word.push(c);
started = true;
i += 1;
}
}
}
if started {
words.push(word);
}
if !words.is_empty() {
out.push((words, Sep::End));
substitutions.push(spans);
}
if quote.is_some() {
plain = false;
}
ShellScan {
commands: out,
substitutions,
plain,
}
}
fn cargo_test_arguments(words: &[String]) -> Option<Vec<&str>> {
let words = budgeted_tier_payload(words).unwrap_or(words);
let mut words = words
.iter()
.map(String::as_str)
.skip_while(|w| *w == "env" || (!w.starts_with('-') && w.contains('=')));
let program = words.next()?;
if program != "cargo" && !program.ends_with("/cargo") {
return None;
}
let mut rest = words.skip_while(|w| w.starts_with('+'));
if rest.next()? != "test" {
return None;
}
let mut arguments = Vec::new();
let mut argument_is_a_redirection_target = false;
for word in rest {
if argument_is_a_redirection_target {
argument_is_a_redirection_target = false;
continue;
}
if word.contains('>') || word.contains('<') {
argument_is_a_redirection_target = word.ends_with('>') || word.ends_with('<');
continue;
}
arguments.push(word);
}
Some(arguments)
}
fn budgeted_tier_payload(words: &[String]) -> Option<&[String]> {
let mut at = 0;
while words
.get(at)
.is_some_and(|word| word == "env" || (!word.starts_with('-') && word.contains('=')))
{
at += 1;
}
if words
.get(at)
.is_some_and(|word| word == "bash" || word.rsplit('/').next() == Some("bash"))
{
at += 1;
}
let adapter = words.get(at)?;
if adapter.rsplit('/').next() != Some("tier.sh")
|| !adapter.split('/').any(|part| part == "scripts")
{
return None;
}
let delimiter = words[at + 1..].iter().position(|word| word == "--")? + at + 1;
Some(&words[delimiter + 1..])
}
#[test]
fn the_checked_tier_wrapper_still_exposes_the_gating_cargo_test() {
let wrapped = shell_commands(
"bash scripts/tier.sh 'test (debug)' debug 750 50000 -- cargo test --locked --all-targets",
);
assert_eq!(wrapped.len(), 1);
assert_eq!(
cargo_test_arguments(&wrapped[0].0),
Some(vec!["--locked", "--all-targets"])
);
let arbitrary = shell_commands(
"bash scripts/not-the-checked-wrapper.sh -- cargo test --locked --all-targets",
);
assert_eq!(cargo_test_arguments(&arbitrary[0].0), None);
}
const OPTIONS_TAKING_A_VALUE: [&str; 18] = [
"-p",
"--package",
"--exclude",
"-F",
"--features",
"--target",
"--target-dir",
"--manifest-path",
"--profile",
"--test",
"--bin",
"--example",
"--bench",
"-j",
"--jobs",
"--message-format",
"--color",
"--config",
];
fn selects_release_by_short_flag(arguments: &[&str]) -> bool {
let mut next_is_a_value = false;
for argument in arguments {
if std::mem::take(&mut next_is_a_value) {
continue;
}
if *argument == "--" {
return false;
}
if argument.starts_with("--") {
next_is_a_value = !argument.contains('=') && OPTIONS_TAKING_A_VALUE.contains(argument);
continue;
}
let Some(cluster) = argument.strip_prefix('-') else {
continue;
};
for (at, flag) in cluster.char_indices() {
match flag {
'r' => return true,
'p' | 'j' | 'F' | 'Z' => {
next_is_a_value = at + 1 == cluster.len();
break;
}
_ => {}
}
}
}
false
}
const HARNESS_OPTIONS_TAKING_A_VALUE: [&str; 6] = [
"--test-threads",
"--color",
"--format",
"--logfile",
"--shuffle-seed",
"-Z",
];
fn omits_the_library_unit_tests(arguments: &[&str]) -> bool {
let mut harness = arguments.iter().skip_while(|a| **a != "--").skip(1);
let mut expecting_a_value = false;
while let Some(argument) = harness.next() {
if expecting_a_value {
expecting_a_value = false;
continue;
}
if *argument == "--" {
if harness.next().is_some() {
return true;
}
break;
}
if ["--ignored", "--list", "--skip"].contains(argument) || argument.starts_with("--skip=") {
return true;
}
if HARNESS_OPTIONS_TAKING_A_VALUE.contains(argument) {
expecting_a_value = true;
continue;
}
if !argument.starts_with('-') {
return true;
}
}
let cargo_arguments = arguments.iter().take_while(|a| **a != "--");
let mut expecting_a_value = false;
for argument in cargo_arguments {
if expecting_a_value {
expecting_a_value = false;
continue;
}
if OPTIONS_TAKING_A_VALUE.contains(argument) {
expecting_a_value = true;
}
let selects_elsewhere = [
"--test",
"--doc",
"--no-run",
"--bin",
"--example",
"--bench",
]
.iter()
.any(|o| *argument == *o || argument.starts_with(&format!("{o}=")))
|| ["--bins", "--examples", "--benches"].contains(argument);
if selects_elsewhere {
return true;
}
if !argument.starts_with('-') {
return true;
}
}
false
}
fn status_is_read(
commands: &[(Vec<String>, Sep)],
index: usize,
is_last_command_line: bool,
) -> bool {
let tail = &commands[index..];
if !tail
.iter()
.all(|(_, sep)| matches!(sep, Sep::End | Sep::And | Sep::Semi))
{
return false;
}
if tail[0].1 == Sep::And {
return is_last_command_line
&& tail
.iter()
.all(|(_, sep)| matches!(sep, Sep::And | Sep::End));
}
true
}
fn list_can_skip(
commands: &[(Vec<String>, Sep)],
index: usize,
is_last_command_line: bool,
) -> bool {
let before = commands[..index]
.iter()
.rev()
.map(|(_, sep)| *sep)
.find(|sep| *sep != Sep::Pipe);
match before {
Some(Sep::Or) => true,
Some(Sep::And) => {
!(is_last_command_line
&& commands[index..]
.iter()
.all(|(_, sep)| matches!(sep, Sep::And | Sep::End)))
}
_ => false,
}
}
fn whole_command_substitution<'a>(
words: &[String],
substitutions: &'a [Option<String>],
) -> Option<&'a str> {
if words.is_empty() || !words.iter().all(|word| is_assignment(word)) {
return None;
}
substitutions.last()?.as_deref()
}
fn substitution_gates(script: &str) -> Vec<(Vec<String>, Option<bool>)> {
let scan = scan_shell(script);
let commands = &scan.commands;
let Some(last) = commands.len().checked_sub(1) else {
return Vec::new();
};
if !scan.plain || !matches!(commands[last].1, Sep::End | Sep::Semi) {
return Vec::new();
}
let mut out = Vec::new();
let mut handshake = None;
let mut exports_reach = true;
for (index, (words, sep)) in commands.iter().enumerate() {
let handshake_before = handshake;
if opens_a_compound_command(words) {
exports_reach = false;
}
let unconditional = (index == 0 || commands[index - 1].1 == Sep::Semi)
&& !matches!(sep, Sep::Pipe | Sep::Amp);
if exports_the_handshake(words) {
if exports_reach && unconditional {
handshake = Some(true);
}
} else if withdraws_the_handshake(words) {
handshake = Some(false);
}
let decides = commands[index..last]
.iter()
.all(|(_, sep)| *sep == Sep::And)
&& commands[..index]
.iter()
.rev()
.map(|(_, sep)| *sep)
.find(|sep| *sep != Sep::Pipe)
!= Some(Sep::Or);
if !decides {
continue;
}
if cargo_test_arguments(words).is_some() {
out.push((words.clone(), handshake_before));
} else if let Some(inner) = whole_command_substitution(words, &scan.substitutions[index]) {
out.extend(
substitution_gates(inner)
.into_iter()
.map(|(run, inner)| (run, inner.or(handshake_before))),
);
}
}
out
}
fn runs_with_overflow_checks(script: &str) -> Vec<String> {
debug_runs(script, false)
.into_iter()
.map(|run| run.line)
.collect()
}
#[derive(Debug)]
struct DebugRun {
line: String,
receives_the_handshake: bool,
}
fn debug_runs(script: &str, handshake_in_env: bool) -> Vec<DebugRun> {
let lines = command_lines(script);
let last_line = lines.len().saturating_sub(1);
let mut errexit_withdrawn = false;
let mut exported = handshake_in_env;
let mut exports_reach_the_shell = true;
let mut out = Vec::new();
for (line_index, line) in lines.iter().enumerate() {
let ShellScan {
commands,
substitutions,
plain,
} = scan_shell(line);
if !plain {
exports_reach_the_shell = false;
}
let profile_is_named = line.contains("--release")
|| line.contains("--profile")
|| line.contains("CARGO_PROFILE_");
let mut qualifies = false;
let mut receives_the_handshake = false;
for (index, (words, sep)) in commands.iter().enumerate() {
if opens_a_compound_command(words) {
exports_reach_the_shell = false;
}
let is_last_command_line = line_index == last_line;
let decides_the_step = status_is_read(&commands, index, is_last_command_line)
&& !list_can_skip(&commands, index, is_last_command_line);
if !errexit_withdrawn && !profile_is_named && decides_the_step {
let runs = if cargo_test_arguments(words).is_some() {
vec![(words.clone(), None)]
} else {
whole_command_substitution(words, &substitutions[index])
.map(substitution_gates)
.unwrap_or_default()
};
for (run, set_inside) in runs {
let Some(arguments) = cargo_test_arguments(&run) else {
continue;
};
if omits_the_library_unit_tests(&arguments)
|| selects_release_by_short_flag(&arguments)
{
continue;
}
qualifies = true;
receives_the_handshake |= handshake_prefix(&run)
.0
.unwrap_or(set_inside.unwrap_or(exported));
}
}
if set_disables_errexit(words) {
errexit_withdrawn = true;
}
let unconditional = (index == 0 || commands[index - 1].1 == Sep::Semi)
&& !matches!(sep, Sep::Pipe | Sep::Amp);
if exports_the_handshake(words) {
if exports_reach_the_shell && unconditional {
exported = true;
}
} else if withdraws_the_handshake(words) {
exported = false;
}
}
if qualifies {
out.push(DebugRun {
line: line.clone(),
receives_the_handshake,
});
}
}
out
}
#[derive(Debug)]
struct Step {
keys: Vec<String>,
run: String,
env: Vec<String>,
}
#[derive(Debug)]
struct Job {
keys: Vec<String>,
steps: Vec<Step>,
}
#[derive(Debug)]
struct Workflow {
triggers: Vec<String>,
jobs: Vec<Job>,
}
fn field<'a, 'b>(node: &'a Yaml<'b>, name: &str) -> Option<&'a Yaml<'b>> {
node.as_mapping()?
.iter()
.find(|(key, _)| key.as_str() == Some(name))
.map(|(_, value)| value)
}
fn scalar_text(node: &Yaml) -> Option<String> {
if let Some(text) = node.as_str() {
return Some(text.to_string());
}
if let Some(i) = node.as_integer() {
return Some(i.to_string());
}
node.as_bool().map(|b| b.to_string())
}
fn keys_of(node: &Yaml) -> Vec<String> {
node.as_mapping()
.map(|mapping| {
mapping
.iter()
.filter_map(|(key, _)| key.as_str().map(str::to_string))
.collect()
})
.unwrap_or_default()
}
fn parse_workflow(text: &str) -> Workflow {
let documents = Yaml::load_from_str(text).unwrap_or_else(|e| {
panic!(
"workflow is not valid YAML: {e}. This guard reads the workflow \
rather than scanning its text, so a file it cannot parse is a \
failure and never a pass."
)
});
let Some(document) = documents.first() else {
return Workflow {
triggers: Vec::new(),
jobs: Vec::new(),
};
};
let triggers = match field(document, "on") {
Some(on) if on.as_mapping().is_some() => keys_of(on),
Some(on) if on.as_sequence().is_some() => on
.as_sequence()
.into_iter()
.flatten()
.filter_map(|item| item.as_str().map(str::to_string))
.collect(),
Some(on) => on.as_str().map(str::to_string).into_iter().collect(),
None => Vec::new(),
};
let mut jobs = Vec::new();
if let Some(mapping) = field(document, "jobs").and_then(Yaml::as_mapping) {
for (_, body) in mapping.iter() {
let steps = field(body, "steps")
.and_then(Yaml::as_sequence)
.into_iter()
.flatten()
.map(|step| Step {
keys: keys_of(step),
env: field(step, "env")
.and_then(Yaml::as_mapping)
.map(|m| {
m.iter()
.filter_map(|(k, v)| {
Some(format!("{}={}", k.as_str()?, scalar_text(v)?))
})
.collect()
})
.unwrap_or_default(),
run: field(step, "run")
.and_then(Yaml::as_str)
.unwrap_or_default()
.to_string(),
})
.collect();
jobs.push(Job {
keys: keys_of(body),
steps,
});
}
}
Workflow { triggers, jobs }
}
fn runs_on_pull_request(wf: &Workflow) -> bool {
wf.triggers.iter().any(|t| t == "pull_request")
}
const NON_GATING_KEYS: [&str; 2] = ["if", "continue-on-error"];
fn collect_steps(workflow: &str, gating: bool) -> Vec<Step> {
let wf = parse_workflow(workflow);
if gating && !runs_on_pull_request(&wf) {
return Vec::new();
}
let carries_a_non_gating_key =
|keys: &[String]| keys.iter().any(|k| NON_GATING_KEYS.contains(&k.as_str()));
let mut out = Vec::new();
for job in wf.jobs {
if gating && carries_a_non_gating_key(&job.keys) {
continue;
}
for step in job.steps {
if gating && carries_a_non_gating_key(&step.keys) {
continue;
}
out.push(step);
}
}
out
}
fn scan_steps(workflow: &str, gating: bool, select: fn(&str) -> Vec<String>) -> Vec<String> {
collect_steps(workflow, gating)
.iter()
.flat_map(|step| select(&step.run))
.collect()
}
fn step_runs_that_prove_the_build_traps(step: &Step) -> Vec<String> {
let handshake_in_env = step.env.iter().any(|entry| entry == HANDSHAKE);
debug_runs(&step.run, handshake_in_env)
.into_iter()
.filter(|run| run.receives_the_handshake)
.map(|run| run.line)
.collect()
}
fn line_assigns_the_handshake(line: &str) -> bool {
shell_commands(line)
.iter()
.any(|(words, _)| command_assigns_the_handshake(words))
}
const HANDSHAKE_NAME: &str = "EXPECT_OVERFLOW_CHECKS";
const HANDSHAKE: &str = "EXPECT_OVERFLOW_CHECKS=1";
fn command_assigns_the_handshake(words: &[String]) -> bool {
if exports_the_handshake(words) {
return true;
}
let (value, a_command_follows) = handshake_prefix(words);
value == Some(true) && a_command_follows
}
fn handshake_prefix(words: &[String]) -> (Option<bool>, bool) {
let mut value = None;
let mut at = 0;
while at < words.len() {
let word = words[at].as_str();
if word == "env" {
at += 1;
continue;
}
if is_assignment(word) {
if word.split_once('=').map(|(name, _)| name) == Some(HANDSHAKE_NAME) {
value = Some(word == HANDSHAKE);
}
at += 1;
continue;
}
break;
}
(value, at < words.len())
}
fn exports_the_handshake(words: &[String]) -> bool {
let mut rest = words
.iter()
.map(String::as_str)
.skip_while(|word| is_assignment(word));
if rest.next() != Some("export") {
return false;
}
let mut value = None;
for word in rest {
if word.starts_with('-') && word != "--" {
return false;
}
if let Some((name, _)) = word.split_once('=') {
if name == HANDSHAKE_NAME {
value = Some(word == HANDSHAKE);
}
}
}
value == Some(true)
}
fn withdraws_the_handshake(words: &[String]) -> bool {
let program = words
.iter()
.map(String::as_str)
.find(|word| !is_assignment(word));
let declares = matches!(
program,
Some("export" | "declare" | "typeset" | "local" | "readonly")
);
words.iter().any(|word| {
word == HANDSHAKE_NAME
|| (word.starts_with("EXPECT_OVERFLOW_CHECKS=") && (word != HANDSHAKE || declares))
})
}
fn runs_a_substitution(text: &str) -> bool {
let b = text.as_bytes();
b.contains(&b'`')
|| b.windows(3)
.any(|w| w[0] == b'$' && w[1] == b'(' && w[2] != b'(')
|| b.ends_with(b"$(")
}
const COMPOUND_WORDS: [&str; 17] = [
"{", "}", "if", "then", "elif", "else", "fi", "for", "while", "until", "do", "done", "case",
"esac", "select", "function", "coproc",
];
fn opens_a_compound_command(words: &[String]) -> bool {
let program = words
.iter()
.map(String::as_str)
.find(|word| !is_assignment(word));
program.is_some_and(|program| COMPOUND_WORDS.contains(&program))
|| words.iter().any(|word| word.contains("<<"))
}
fn is_assignment(word: &str) -> bool {
match word.split_once('=') {
Some((name, _)) => {
!name.is_empty()
&& name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
&& !name.starts_with(|c: char| c.is_ascii_digit())
}
None => false,
}
}
fn gating_runs_with_overflow_checks(workflow: &str) -> Vec<String> {
scan_steps(workflow, true, runs_with_overflow_checks)
}
fn gating_runs_that_prove_the_build_traps(workflow: &str) -> Vec<String> {
collect_steps(workflow, true)
.iter()
.flat_map(step_runs_that_prove_the_build_traps)
.collect()
}
fn debug_runs_that_prove_the_build_traps(script: &str) -> Vec<String> {
debug_runs(script, false)
.into_iter()
.filter(|run| run.receives_the_handshake)
.map(|run| run.line)
.collect()
}
#[test]
fn the_pr_gate_still_tests_in_a_profile_that_can_see_an_overflow() {
let path = ci_yml();
let workflow = read_or_panic(&path);
let debug_runs = gating_runs_with_overflow_checks(&workflow);
assert!(
!debug_runs.is_empty(),
"no `cargo test` in {} runs without `--release`, so a defect whose \
only symptom is an arithmetic overflow panic can merge without the \
PR gate ever seeing it. release.yml already runs a debug suite, and \
that does not help: it triggers on a version tag, after the change \
has merged. If the debug step in ci.yml looked redundant beside the \
release ones, it is not -- see the comment above it.",
path.display()
);
}
#[test]
fn the_debug_run_asks_the_build_to_prove_it_traps_overflows() {
let path = ci_yml();
let workflow = read_or_panic(&path);
let proving = gating_runs_that_prove_the_build_traps(&workflow);
assert!(
!proving.is_empty(),
"no `cargo test` in {} runs without `--release` while setting \
EXPECT_OVERFLOW_CHECKS=1, so nothing checks whether the profile the \
gate builds actually traps an arithmetic overflow. Reading \
Cargo.toml is not enough: the checks can also be turned off by a \
CARGO_PROFILE_TEST_OVERFLOW_CHECKS variable at step or job level, \
or by a .cargo/config.toml, neither of which is in any file this \
test reads. The handshake is what arms the one check that cannot be \
fooled by where the setting lives.",
path.display()
);
}
#[test]
fn a_checking_debug_run_that_is_not_in_ci_yml_does_not_satisfy_this_guard() {
let release_yml_as_it_is = "\
jobs:
test:
steps:
- run: cargo test --locked --all-targets
- run: cargo test --locked --all-targets -- --ignored
";
assert_eq!(
scan_steps(release_yml_as_it_is, false, runs_with_overflow_checks),
vec!["cargo test --locked --all-targets".to_string()],
"release.yml's plain step IS a debug run -- the parser counts it, \
and the only reason it does not satisfy the guard is that the guard \
never opens that file"
);
assert!(
scan_steps(
release_yml_as_it_is,
false,
debug_runs_that_prove_the_build_traps
)
.is_empty(),
"release.yml carries no handshake, and is not asked to"
);
let release_yml_with_a_handshake = "\
jobs:
test:
steps:
- run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --all-targets
";
assert_eq!(
scan_steps(
release_yml_with_a_handshake,
false,
debug_runs_that_prove_the_build_traps
),
vec!["EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --all-targets".to_string()],
"the parser itself would count this step too -- so widening the scan \
to every workflow would silently stop catching this repository's \
actual defect"
);
let scanned = read_or_panic(&ci_yml());
assert!(
!gating_runs_that_prove_the_build_traps(&scanned).is_empty(),
"the guards above must be satisfied by ci.yml's own content, not by \
any of the strings in this test"
);
}
fn profiles_disabling_overflow_checks(manifest: &str) -> Vec<String> {
fn normalise(path: &str) -> String {
path.split('.')
.map(|segment| {
segment
.trim()
.trim_matches(|c| c == '"' || c == '\'')
.trim()
})
.collect::<Vec<_>>()
.join(".")
}
const DISABLED: [&str; 2] = [
"profile.dev.overflow-checks",
"profile.test.overflow-checks",
];
let mut section = String::new();
let mut found = Vec::new();
for raw in manifest.lines() {
let line = raw.split('#').next().unwrap_or(raw).trim();
if line.starts_with('[') {
section = normalise(line.trim_matches(|c| c == '[' || c == ']'));
continue;
}
let Some((key, value)) = line.split_once('=') else {
continue;
};
if value.trim() != "false" {
continue;
}
let key = normalise(key);
let path = if section.is_empty() {
key
} else {
format!("{section}.{key}")
};
if DISABLED.contains(&path.as_str()) {
found.push(path);
}
}
found
}
#[test]
fn the_profile_that_cargo_test_builds_still_checks_for_overflow() {
let path = manifest_dir().join("Cargo.toml");
let manifest = read_or_panic(&path);
let disabled = profiles_disabling_overflow_checks(&manifest);
assert!(
disabled.is_empty(),
"{} sets `overflow-checks = false` under {disabled:?}. `cargo test` \
builds the `test` profile, which inherits from `dev`, so this \
switches off the check that the debug step in ci.yml exists to run \
-- leaving that step present, green, and blind. Put it back, or the \
debug step is costing a compile and buying nothing.",
path.display()
);
}
mod shell_scan {
use super::runs_with_overflow_checks;
fn selects_away_from_the_library(line: &str) -> bool {
super::shell_commands(line)
.iter()
.filter_map(|(words, _)| super::cargo_test_arguments(words))
.any(|arguments| super::omits_the_library_unit_tests(&arguments))
}
#[test]
fn an_echoed_command_is_not_a_run() {
for line in [
"echo \"EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\"",
"echo 'cargo test --locked --all-targets'",
"echo cargo test --locked --lib",
"printf '%s\\n' \"cargo test --locked --lib\"",
"echo \"running: cargo test\" && cargo build --locked",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line} prints the command; it does not run it"
);
}
}
#[test]
fn the_spellings_that_do_invoke_cargo_test_still_count() {
for line in [
"cargo test --locked --all-targets",
"EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib",
"cd .. && cargo test --locked --lib",
"cargo test --locked --lib 2>&1",
"cargo test --locked --lib > test.log",
"cargo +stable test --locked --lib",
"env EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib",
] {
assert_eq!(
runs_with_overflow_checks(line).len(),
1,
"{line} runs the suite in debug and must be counted"
);
}
}
#[test]
fn a_debug_run_quoted_in_a_shell_comment_does_not_count() {
let block = "\
set -euo pipefail
# Measured on this branch:
# cargo test --locked --release --lib -> EXIT=0
# EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib -> EXIT=101
cargo test --locked --release
";
assert_eq!(
runs_with_overflow_checks(block),
Vec::<String>::new(),
"a debug command quoted inside a comment is documentation, not a run"
);
}
#[test]
fn a_real_debug_run_counts() {
let block = "\
cargo test --locked --release
EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib
";
assert_eq!(
runs_with_overflow_checks(block),
vec!["EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib".to_string()],
);
}
#[test]
fn a_trailing_comment_does_not_promote_a_release_run() {
let inline = "cargo test --locked --release # not cargo test --lib\n";
assert_eq!(
runs_with_overflow_checks(inline),
Vec::<String>::new(),
"the command is --release; the comment after it is not a second run"
);
}
#[test]
fn a_hash_that_does_not_begin_a_word_is_not_a_comment() {
for line in [
"cargo test --locked --features a#b --all-targets",
"cargo test --locked --all-targets && echo \"done #1\"",
] {
assert_eq!(
runs_with_overflow_checks(line).len(),
1,
"{line:?} has no comment on it: the `#` is inside a word or a quoted span"
);
}
}
#[test]
fn a_quoted_hash_before_the_run_does_not_cut_the_line_short() {
for line in [
"echo \"step # 1\"; cargo test --locked --all-targets",
"echo 'step # 1' && cargo test --locked --all-targets",
"printf '%s\\n' \"# not a comment\"; cargo test --locked --lib",
] {
assert_eq!(
runs_with_overflow_checks(line).len(),
1,
"{line:?} runs the suite: the `#` is inside quotes and ends nothing"
);
}
}
#[test]
fn a_trailing_comment_naming_release_does_not_disqualify_a_debug_run() {
let line = "cargo test --locked --lib # deliberately not --release\n";
assert_eq!(
runs_with_overflow_checks(line),
vec!["cargo test --locked --lib".to_string()],
"the command is a debug run; --release appears only in its comment"
);
}
#[test]
fn the_short_release_flag_does_not_count_in_any_spelling() {
for line in [
"cargo test --locked -r --all-targets",
"cargo test --locked -qr --all-targets",
"cargo test --locked -rq --all-targets",
"cargo test --locked -j4 -r",
"cargo test --locked -j 4 -r --lib",
"cargo test --locked --features x -r",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line} builds the release profile"
);
}
for line in [
"cargo test --locked --all-targets -- -r",
"cargo test --locked --features r",
"cargo test --locked -F r",
"cargo test --locked -pr --lib",
"cargo test --locked -j r --lib",
] {
assert_eq!(
runs_with_overflow_checks(line).len(),
1,
"{line}: the r is a value or the harness's, and the run is debug"
);
}
}
#[test]
fn a_profile_named_another_way_does_not_count() {
let lines = [
"cargo test --locked --profile release-with-debug --lib",
"CARGO_PROFILE_TEST_OVERFLOW_CHECKS=false cargo test --locked --lib",
"CARGO_PROFILE_DEV_OVERFLOW_CHECKS=false cargo test --locked --lib",
];
for line in lines {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line} does not compile the overflow checks"
);
}
assert_eq!(
lines.len(),
3,
"the loop above must have examined every shape"
);
}
#[test]
fn a_cargo_build_step_is_not_a_test_run() {
let validate_job = "\
cargo build --locked --release
./target/release/some-tool --check /tmp/image
";
assert_eq!(
runs_with_overflow_checks(validate_job),
Vec::<String>::new(),
"building a binary is not running a test suite"
);
}
#[test]
fn a_run_whose_status_is_discarded_does_not_count() {
for line in [
"cargo test --locked --all-targets || true",
"cargo test --locked --all-targets || echo 'ignored'",
"cargo test --locked --all-targets | tee test.log",
"cargo test --locked --all-targets &",
"set +e\ncargo test --locked --all-targets\n",
"set +o errexit\ncargo test --locked --all-targets\n",
"set +ex\ncargo test --locked --all-targets\n",
"set +o errexit; cargo test --locked --all-targets\n",
"set +o errexit&& cargo test --locked --all-targets\n",
"set +o errexit\ncargo test --locked --all-targets | tee log\n",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line:?} runs the suite and throws the answer away"
);
}
}
#[test]
fn a_run_whose_failure_still_ends_the_step_counts() {
for line in [
"cargo test --locked --all-targets",
"cargo test --locked --all-targets && echo ok",
"cd .. && cargo test --locked --all-targets && echo ok",
"cargo test --locked --all-targets 2>&1",
"cargo test --locked --all-targets; echo done",
"cargo test --locked --all-targets ; true",
"set -euo pipefail\ncargo test --locked --all-targets\n",
"set -o errexit; cargo test --locked --all-targets\n",
"echo \"set +o errexit\"\ncargo test --locked --all-targets\n",
] {
assert_eq!(
runs_with_overflow_checks(line).len(),
1,
"{line:?} fails the step when the suite fails"
);
}
}
#[test]
fn a_selection_that_leaves_the_library_unit_tests_out_does_not_count() {
for line in [
"cargo test --locked --doc",
"cargo test --locked --no-run",
"cargo test --locked --bins",
"cargo test --locked --examples",
"cargo test --locked --bin some_tool",
"cargo test --locked --example inspect",
"cargo test --locked some_filter",
"EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --features qemu-validation qemu",
] {
assert!(
selects_away_from_the_library(line),
"{line} selects something other than the library unit tests"
);
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line} does not build and run the library unit tests"
);
}
}
#[test]
fn an_equals_spelled_single_target_does_not_count_either() {
for line in [
"cargo test --locked --features qemu-validation --test=qemu_validation",
"EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --test=some_oracle",
"cargo test --locked --test=\"qemu_validation\"",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line} builds one integration target and no library unit tests"
);
assert!(
selects_away_from_the_library(line),
"{line} names a single integration target"
);
}
}
#[test]
fn options_that_only_look_like_test_do_not_disqualify_a_run() {
for line in [
"cargo test --locked --tests",
"cargo test --locked --all-targets",
"cargo test --locked --lib -- --test-threads=1",
] {
assert!(
!selects_away_from_the_library(line),
"{line} does not restrict the run to one integration target"
);
assert_eq!(
runs_with_overflow_checks(line).len(),
1,
"{line} builds the library unit tests and must be counted"
);
}
}
#[test]
fn a_harness_filter_after_the_separator_does_not_count() {
let lines = [
"cargo test --locked --lib -- nonexistent_filter",
"cargo test --locked --all-targets -- some::module",
"cargo test --locked --lib -- --exact some::test",
"cargo test --locked --lib -- --test-threads 1 some_filter",
"cargo test --locked --lib -- --skip overflow_checks",
"cargo test --locked --lib -- --ignored",
"cargo test --locked --lib -- --list",
"cargo test --locked --lib -- -- --nocapture",
];
for line in lines {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"harness_filter_after_separator: {line} narrows the harness run, so the \
overflow probe may never execute"
);
assert!(
selects_away_from_the_library(line),
"harness_filter_after_separator: {line} narrows what the harness runs"
);
}
assert_eq!(lines.len(), 8, "every shape above must have been examined");
}
#[test]
fn harness_options_that_run_every_test_still_count() {
for line in [
"cargo test --locked --lib -- --test-threads=1",
"cargo test --locked --lib -- --test-threads 1",
"cargo test --locked --all-targets -- --nocapture",
"cargo test --locked --all-targets -- --include-ignored",
"cargo test --locked --lib -- --show-output --color always",
"cargo test --locked --lib -- --format terse --logfile test.log",
"cargo test --locked --lib --",
] {
assert!(
!selects_away_from_the_library(line),
"{line} runs every library unit test"
);
assert_eq!(
runs_with_overflow_checks(line).len(),
1,
"{line} runs every library unit test and must be counted"
);
}
}
#[test]
fn a_continued_line_is_one_command() {
for script in [
"cargo test --locked --all-targets && \\\necho done\n",
"cargo test --locked \\\n --all-targets\n",
"cd .. && \\\n cargo test --locked --lib && \\\n echo ok\n",
"cargo test --locked --all-targets &&\necho done\n",
] {
assert_eq!(
runs_with_overflow_checks(script).len(),
1,
"{script:?} is one logical command whose failure ends the step"
);
}
for script in [
"cargo test --locked --lib && echo a\\\\\necho b\n",
"cargo test --locked --lib && echo a # \\\necho b\n",
"cargo test --locked --all-targets && \\\necho done\necho after\n",
"cargo test --locked --all-targets &&\necho done\necho after\n",
] {
assert_eq!(
runs_with_overflow_checks(script),
Vec::<String>::new(),
"{script:?}: the && list is followed by another command, so its status is replaced"
);
}
}
#[test]
fn set_plus_e_after_the_run_does_not_disqualify_it() {
for script in [
"cargo test --locked --all-targets\nset +e\necho cleanup\n",
"cargo test --locked --lib; set +e\n",
"cargo test --locked --lib\nset +o errexit\n",
] {
assert_eq!(
runs_with_overflow_checks(script).len(),
1,
"{script:?}: the suite ran under errexit"
);
}
assert_eq!(
runs_with_overflow_checks("echo x\nset +e\ncargo test --locked --lib\n"),
Vec::<String>::new(),
);
}
#[test]
fn a_single_integration_target_does_not_count() {
for line in [
"cargo test --locked --features qemu-validation --test qemu_validation",
"EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --test some_oracle",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line} builds one integration target and no library unit tests"
);
}
}
#[test]
fn a_nested_backtick_does_not_close_the_outer_substitution() {
let line = "echo $(echo `echo x)`) ; cargo test --locked --lib";
assert_eq!(
runs_with_overflow_checks(line),
vec![line.to_string()],
"{line}: the `)` is inside a nested backtick span, so the substitution runs on \
and the cargo test after it is top-level"
);
}
#[test]
fn a_brace_expansion_does_not_close_the_outer_substitution() {
assert_eq!(
runs_with_overflow_checks("$(echo ${x:+)} ; cargo test --locked --lib)"),
Vec::<String>::new(),
"the whole thing is inside `$( )`; the `)` belongs to the brace expansion"
);
let line = "echo $(echo ${x:+)}) ; cargo test --locked --lib";
assert_eq!(
runs_with_overflow_checks(line),
vec![line.to_string()],
"{line}: the substitution closes and the cargo test after it is top-level"
);
let line = "cargo test --locked --lib && echo ${x:+)}";
assert_eq!(
runs_with_overflow_checks(line),
vec![line.to_string()],
"{line}: the `)` belongs to the expansion, so the && list still ends the step"
);
assert_eq!(
runs_with_overflow_checks("cargo test --locked --lib && echo ${x:-$(echo {)} ; true"),
Vec::<String>::new(),
"the `; true` swallows the failure; the brace inside the substitution is data"
);
let line = "cargo test --locked --lib && echo ${x:-$(echo {)}";
assert_eq!(
runs_with_overflow_checks(line),
vec![line.to_string()],
"{line}: the && chain ends the step, so the suite's failure is read"
);
for expansion in [
"${x:-{}",
"${x:-$(echo })}",
"${x:-`echo }`}",
"${x:-$(echo ${y})}",
] {
let line = format!("cargo test --locked --lib && echo {expansion} ; true");
assert_eq!(
runs_with_overflow_checks(&line),
Vec::<String>::new(),
"{line}: the `; true` swallows the failure, so this is not the gate"
);
}
for expansion in ["${x:-$(echo })}", "${x:-$(echo ${y})}"] {
let line = format!("cargo test --locked --lib && echo {expansion}");
assert_eq!(
runs_with_overflow_checks(&line),
vec![line.clone()],
"{line}: the && chain ends the step, so the suite's failure is read"
);
}
assert_eq!(
runs_with_overflow_checks("cargo test --locked --lib && echo $(echo {) ; true"),
Vec::<String>::new(),
"a literal brace in a substitution does not change where the line ends"
);
let line = "cargo test --locked --lib && echo ${x:-${y}z)}";
assert_eq!(
runs_with_overflow_checks(line),
vec![line.to_string()],
"{line}: the nested expansion owns both braces and the `)`, so the && list still ends the step"
);
assert_eq!(
runs_with_overflow_checks("$(echo ${x:-${y:+)}} ; cargo test --locked --lib)"),
Vec::<String>::new(),
"a nested brace expansion's braces are its own inside a substitution too"
);
}
#[test]
fn a_paren_the_shell_reads_as_data_does_not_end_a_substitution() {
for line in [
"cat <(echo a\\) ; cargo test --locked --lib)",
"cat <(echo \"a)\" ; cargo test --locked --lib)",
"cat <(echo 'a\\)' ; cargo test --locked --lib)",
"echo $(echo a\\) ; cargo test --locked --lib)",
"echo $(echo \"a)\" ; cargo test --locked --lib)",
"cat <(echo 'a\\' ; cargo test --locked --lib)",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line}: bash keeps the cargo test inside the substitution and discards \
its status, so counting it would count a probe that does not gate the step"
);
}
}
#[test]
fn an_escaped_backtick_does_not_end_a_backtick_substitution() {
let line = "echo `echo a\\` ; cargo test --locked --lib`";
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line}: the escaped backtick keeps the cargo test inside the substitution"
);
}
#[test]
fn a_substitution_that_really_closes_still_leaves_a_gate() {
for line in [
"cat <(echo \"a)\") ; cargo test --locked --lib",
"cat <(echo 'a)') ; cargo test --locked --lib",
"cat <(echo a\\\\) ; cargo test --locked --lib",
"cat <(echo 'a\\') ; cargo test --locked --lib",
"cat <(echo a) ; cargo test --locked --lib",
] {
assert_eq!(
runs_with_overflow_checks(line),
vec![line.to_string()],
"{line}: the substitution closes and the cargo test after it is top-level, \
so its failure ends the step and it must still count"
);
}
}
#[test]
fn a_cargo_test_inside_a_command_substitution_is_not_a_gate() {
for line in [
"echo $(cargo test --locked --lib)",
"echo `cargo test --locked --lib`",
"echo \"result: $(cargo test --locked --all-targets)\"",
"cat <(cargo test --locked --all-targets)",
"diff <(cargo test --locked --lib) expected.txt",
"echo x > >(cargo test --locked --all-targets)",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line}: the substitution swallows the status, only the outer command's is read"
);
}
}
#[test]
fn a_run_that_its_list_can_skip_does_not_count() {
let lines = [
"true || cargo test --locked --lib",
"true || echo x | cargo test --locked --lib",
"test -n \"$CI\" && cargo test --locked --lib\necho done\n",
"test -n \"$CI\" && cargo test --locked --lib; echo done",
"false || false && cargo test --locked --lib\necho done\n",
"x=$(true || cargo test --locked --lib)",
];
for line in lines {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"skippable_run: {line:?} -- the list can skip the cargo test and still \
leave the step green"
);
}
assert_eq!(lines.len(), 6, "every shape above must have been examined");
for line in [
"false || cargo test --locked --lib",
"cd . && cargo test --locked --lib\necho done\n",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"over-strict on purpose: {line:?}"
);
}
}
#[test]
fn a_run_its_list_cannot_skip_silently_still_counts() {
for line in [
"test -n \"$CI\" && cargo test --locked --lib",
"cd .. && cargo test --locked --lib && echo ok",
"false || true && cargo test --locked --lib",
"echo x | cargo test --locked --lib",
"sleep 0 & cargo test --locked --lib",
"echo start; cargo test --locked --lib",
] {
assert_eq!(
runs_with_overflow_checks(line).len(),
1,
"{line:?}: a skipped run fails the step, or nothing can skip it"
);
}
}
#[test]
fn a_substitution_that_is_the_whole_command_is_a_gate() {
let lines = [
"x=$(cargo test --locked --lib)",
"OUT=$(cargo test --locked --lib)",
"x=$(echo x | cargo test --locked --lib)",
"x=`cargo test --locked --lib`",
"x=$(cargo test --locked --lib && echo ok)",
"x=$(true) y=$(cargo test --locked --lib)",
"x=a$(cargo test --locked --lib)b",
"x=$(y=$(cargo test --locked --lib))",
"x=$(cargo test --locked --lib); echo after",
"x=$((1+1)) y=$(cargo test --locked --lib)",
"x=$(cargo test --locked --lib) y=$((1+1))",
"x=$(cargo test --locked --lib) y=\"$((1+1))\"",
"x=$(cargo test --locked --lib) y=\"a$((2*3))b\"",
"x=$(cargo test --locked --lib) y=${z}",
"x=$(cargo test --locked --lib) y=${z:-$((1+1))}",
"x=$(cargo test --locked --lib;)",
];
for line in lines {
assert_eq!(
runs_with_overflow_checks(line),
vec![line.to_string()],
"whole_command_substitution: {line:?} -- the substitution's status is the \
command's, so the cargo test inside it gates the step"
);
}
assert_eq!(lines.len(), 16, "every shape above must have been examined");
}
#[test]
fn a_substitution_that_is_not_the_whole_status_is_not_a_gate() {
let lines = [
"echo $(cargo test --locked --lib)",
"$(cargo test --locked --lib)",
"x=$(cargo test --locked --lib; true)",
"x=$(cargo test --locked --lib | cat)",
"x=$(cargo test --locked --lib) y=$(true)",
"export x=$(cargo test --locked --lib)",
"env x=$(cargo test --locked --lib)",
"x=$(cargo test --locked --lib) true",
"x=$(cargo test --locked --lib) y=${z:-$(true)}",
"x=$(cargo test --locked --lib) y=\"$((1+$(true; echo 1)))\"",
"x=$(cargo test --locked --lib) N=$(( $(true; printf 1) + 1))",
"x=$(cargo test --locked --lib) y=$((1+`true; echo 1`))",
"x=$(cargo test --locked --lib) y=\"$(true)\"",
"x=$(cargo test --locked --lib) || true",
"x=$(cargo test --locked --lib) && echo ok\necho after\n",
"x=$(cargo test --locked --lib &)",
"x=<(cargo test --locked --lib)",
];
for line in lines {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line:?}: the step's status does not depend on the suite's"
);
}
assert_eq!(lines.len(), 17, "every shape above must have been examined");
for line in [
"x=\"$(cargo test --locked --lib)\"",
"$(cargo test --locked --lib > /dev/null)",
"x=$(cargo test --locked --lib) y=<(true)",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"over-strict on purpose: {line:?}"
);
}
}
#[test]
fn a_substitution_in_an_argument_leaves_the_run_counted() {
assert_eq!(
runs_with_overflow_checks("cargo test --locked --lib -- --test-threads=$(nproc)").len(),
1,
"the run is `cargo test`; the substitution is one of its arguments"
);
assert_eq!(
runs_with_overflow_checks("cargo test --locked --lib").len(),
1,
);
}
#[test]
fn an_escaped_quote_does_not_end_the_quoted_span() {
for line in [
"echo \"a \\\" && cargo test --locked --lib\"",
"echo \"quoted \\\" cargo test --locked --all-targets\"",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line} is one `echo`; the escaped quote is data, not the end of the span"
);
}
assert_eq!(
runs_with_overflow_checks("echo 'a \\' && cargo test --locked --lib").len(),
1,
"single quotes have no escapes: the span ends and a real run follows"
);
}
#[test]
fn an_and_list_that_does_not_end_the_line_has_its_failure_swallowed() {
for line in [
"cargo test --locked --lib && echo ok; echo done",
"cargo test --locked --all-targets && echo ok; ls",
"cargo test --locked --all-targets && echo done\necho \"second line\"\n",
"cargo test --locked --lib && echo ok\ncargo build --locked\n",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line}: the && list is followed by another command, so its status is replaced"
);
}
}
#[test]
fn an_and_list_that_ends_the_line_still_counts() {
for line in [
"cargo test --locked --lib && echo ok",
"cargo test --locked --lib && echo ok && echo done",
"cd .. && cargo test --locked --lib",
"set -euo pipefail\ncargo test --locked --all-targets && echo ok\n",
"cd ..\ncargo test --locked --lib && echo ok\n",
] {
assert_eq!(
runs_with_overflow_checks(line).len(),
1,
"{line}: exit 1 propagates, measured with bash -e"
);
}
}
#[test]
fn a_same_line_set_plus_e_still_disqualifies_the_script() {
for line in [
"set +o errexit; cargo test --locked --lib",
"set +e; cargo test --locked --lib",
"set +ex; cargo test --locked --lib",
] {
assert_eq!(
runs_with_overflow_checks(line),
Vec::<String>::new(),
"{line}: measured -- bash -e -c 'set +o errexit; false; echo R' exits 0"
);
}
}
#[test]
fn errexit_restored_later_is_still_refused_and_that_is_deliberate() {
let script = "set +e\nsomething || true\nset -e\ncargo test --locked --lib\n";
assert_eq!(
runs_with_overflow_checks(script),
Vec::<String>::new(),
"over-strict on purpose: the guard does not track where `set -e` is live"
);
}
#[test]
fn a_withdrawal_under_control_flow_is_still_applied() {
for script in [
"[ -n \"$CI\" ] && set +e\ncargo test --locked --lib\n",
"false && set +e\ncargo test --locked --lib\n",
"(set +e)\ncargo test --locked --lib\n",
] {
assert_eq!(
runs_with_overflow_checks(script),
Vec::<String>::new(),
"over-strict on purpose: {script:?}"
);
}
for script in [
"export EXPECT_OVERFLOW_CHECKS=1\n[ -n \"$CI\" ] && unset EXPECT_OVERFLOW_CHECKS\ncargo test --locked --lib\n",
"export EXPECT_OVERFLOW_CHECKS=1\nunset EXPECT_OVERFLOW_CHECKS | cat\ncargo test --locked --lib\n",
] {
assert_eq!(
super::debug_runs_that_prove_the_build_traps(script),
Vec::<String>::new(),
"over-strict on purpose: {script:?}"
);
}
}
#[test]
fn a_tests_flag_run_counts_which_is_what_the_trailing_space_protects() {
assert_eq!(
runs_with_overflow_checks("cargo test --locked --tests"),
vec!["cargo test --locked --tests".to_string()],
);
assert_eq!(
runs_with_overflow_checks("cargo test --locked --all-targets").len(),
1,
"`--all-targets` builds the library too"
);
}
}
mod handshake {
use super::debug_runs_that_prove_the_build_traps;
#[test]
fn a_debug_run_carrying_the_handshake_counts() {
let script = "EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n";
assert_eq!(
debug_runs_that_prove_the_build_traps(script),
vec!["EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib".to_string()],
);
}
#[test]
fn a_debug_run_without_the_handshake_does_not_count() {
let script = "cargo test --locked --lib\n";
assert_eq!(
debug_runs_that_prove_the_build_traps(script),
Vec::<String>::new(),
"the step is there but nothing checks the build it produced"
);
}
#[test]
fn the_handshake_on_a_release_run_does_not_count() {
let script = "EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --release\n";
assert_eq!(
debug_runs_that_prove_the_build_traps(script),
Vec::<String>::new(),
);
}
#[test]
fn the_handshake_quoted_in_a_comment_does_not_count() {
let script = "# EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n";
assert_eq!(
debug_runs_that_prove_the_build_traps(script),
Vec::<String>::new(),
);
}
#[test]
fn an_assignment_with_no_command_after_it_arms_nothing() {
for line in [
"EXPECT_OVERFLOW_CHECKS=1",
" EXPECT_OVERFLOW_CHECKS=1 ",
"env EXPECT_OVERFLOW_CHECKS=1",
"EXPECT_OVERFLOW_CHECKS=1 OTHER=2",
"export",
] {
assert!(
!super::line_assigns_the_handshake(line),
"{line}: nothing is exported, so no later cargo test can see the handshake"
);
}
let block = "EXPECT_OVERFLOW_CHECKS=1\ncargo test --locked --lib\n";
assert_eq!(
super::debug_runs_that_prove_the_build_traps(block),
Vec::<String>::new(),
"the assignment is on its own line, so the cargo test runs without it"
);
}
#[test]
fn the_spellings_that_really_export_it_still_arm_the_step() {
for line in [
"EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib",
"env EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib",
"EXPECT_OVERFLOW_CHECKS=1 RUST_BACKTRACE=1 cargo test --locked --lib",
"RUST_BACKTRACE=1 EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib",
"export EXPECT_OVERFLOW_CHECKS=1",
"export RUST_BACKTRACE=1 EXPECT_OVERFLOW_CHECKS=1",
] {
assert!(
super::line_assigns_the_handshake(line),
"{line} really does put the handshake in an environment a child receives"
);
}
assert!(
!super::line_assigns_the_handshake("echo EXPECT_OVERFLOW_CHECKS=1"),
"a printed handshake still arms nothing"
);
}
#[test]
fn a_printed_handshake_does_not_arm_the_step() {
for line in [
"echo \"EXPECT_OVERFLOW_CHECKS=1\"",
"echo EXPECT_OVERFLOW_CHECKS=1",
"printf '%s\\n' EXPECT_OVERFLOW_CHECKS=1",
"echo \"setting EXPECT_OVERFLOW_CHECKS=1 for this step\"",
"grep -q EXPECT_OVERFLOW_CHECKS=1 ci.yml",
] {
assert!(
!super::line_assigns_the_handshake(line),
"{line} mentions the handshake; it does not set it"
);
}
}
#[test]
fn every_spelling_that_really_assigns_it_still_arms_the_step() {
for line in [
"EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib",
"env EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib",
"RUST_BACKTRACE=1 EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib",
"export EXPECT_OVERFLOW_CHECKS=1",
"EXPECT_OVERFLOW_CHECKS=1 cargo +stable test --locked --lib",
] {
assert!(
super::line_assigns_the_handshake(line),
"{line} assigns the handshake and must arm the step"
);
}
}
#[test]
fn a_printed_handshake_beside_a_real_run_does_not_prove_anything() {
for script in [
"echo \"EXPECT_OVERFLOW_CHECKS=1\" && cargo test --locked --lib\n",
"cargo test --locked --lib -- --skip EXPECT_OVERFLOW_CHECKS=1\n",
] {
assert_eq!(
debug_runs_that_prove_the_build_traps(script),
Vec::<String>::new(),
"{script:?}: the handshake is mentioned, not assigned, so the process \
gets no variable and the runtime probe asserts nothing"
);
}
}
#[test]
fn a_handshake_that_does_not_reach_the_run_does_not_prove_anything() {
let scripts = [
"cargo test --locked --lib; export EXPECT_OVERFLOW_CHECKS=1\n",
"EXPECT_OVERFLOW_CHECKS=1 echo x; cargo test --locked --lib\n",
"cargo test --locked --lib\nexport EXPECT_OVERFLOW_CHECKS=1\n",
"EXPECT_OVERFLOW_CHECKS=1 echo x\ncargo test --locked --lib\n",
"EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --no-run; cargo test --locked --lib\n",
"(export EXPECT_OVERFLOW_CHECKS=1); cargo test --locked --lib\n",
"(\nexport EXPECT_OVERFLOW_CHECKS=1\n)\ncargo test --locked --lib\n",
"export EXPECT_OVERFLOW_CHECKS=1 | cat\ncargo test --locked --lib\n",
"export EXPECT_OVERFLOW_CHECKS=1 &\ncargo test --locked --lib\n",
"test -n \"$CI\" && export EXPECT_OVERFLOW_CHECKS=1\ncargo test --locked --lib\n",
"test -n \"$CI\" &&\nexport EXPECT_OVERFLOW_CHECKS=1\ncargo test --locked --lib\n",
"true ||\nexport EXPECT_OVERFLOW_CHECKS=1\ncargo test --locked --lib\n",
"export EXPECT_OVERFLOW_CHECKS=1\nunset EXPECT_OVERFLOW_CHECKS\ncargo test --locked --lib\n",
"export EXPECT_OVERFLOW_CHECKS=1\nexport -n EXPECT_OVERFLOW_CHECKS\ncargo test --locked --lib\n",
"export -n EXPECT_OVERFLOW_CHECKS=1\ncargo test --locked --lib\n",
"export EXPECT_OVERFLOW_CHECKS=1\nEXPECT_OVERFLOW_CHECKS=0 cargo test --locked --lib\n",
"EXPECT_OVERFLOW_CHECKS=1 EXPECT_OVERFLOW_CHECKS=0 cargo test --locked --lib\n",
"if true; then\nexport EXPECT_OVERFLOW_CHECKS=1\nfi | cat\ncargo test --locked --lib\n",
"{\nexport EXPECT_OVERFLOW_CHECKS=1\n} | cat\ncargo test --locked --lib\n",
"env FOO=1 export EXPECT_OVERFLOW_CHECKS=1\ncargo test --locked --lib\n",
"cat <<EOF\nexport EXPECT_OVERFLOW_CHECKS=1\nEOF\ncargo test --locked --lib\n",
];
for script in scripts {
assert_eq!(
debug_runs_that_prove_the_build_traps(script),
Vec::<String>::new(),
"handshake_must_reach_the_run: {script:?} -- the cargo test process never \
receives EXPECT_OVERFLOW_CHECKS=1, so the runtime probe asserts nothing"
);
}
assert_eq!(
scripts.len(),
21,
"every shape above must have been examined"
);
}
#[test]
fn a_handshake_that_reaches_the_run_still_proves_it() {
for script in [
"EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib; echo done\n",
"echo start; EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
"export EXPECT_OVERFLOW_CHECKS=1\ncargo test --locked --lib\n",
"export EXPECT_OVERFLOW_CHECKS=1; cargo test --locked --lib\n",
"set -euo pipefail\nexport RUST_BACKTRACE=1 EXPECT_OVERFLOW_CHECKS=1\ncd ..\ncargo test --locked --lib\n",
"FOO=1 export EXPECT_OVERFLOW_CHECKS=1\ncargo test --locked --lib\n",
"EXPECT_OVERFLOW_CHECKS=1 \\\n cargo test --locked --lib\n",
] {
assert_eq!(
debug_runs_that_prove_the_build_traps(script).len(),
1,
"{script:?}: the cargo test process receives EXPECT_OVERFLOW_CHECKS=1"
);
}
assert!(
super::line_assigns_the_handshake("FOO=1 export EXPECT_OVERFLOW_CHECKS=1"),
"an assignment prefix does not stop `export` being the command"
);
}
#[test]
fn a_run_inside_a_substitution_receives_only_the_exported_handshake() {
for script in [
"export EXPECT_OVERFLOW_CHECKS=1\nx=$(cargo test --locked --lib)\n",
"x=$(EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib)\n",
"export EXPECT_OVERFLOW_CHECKS=1\nx=$(unset EXPECT_OVERFLOW_CHECKS; EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib)\n",
"export EXPECT_OVERFLOW_CHECKS=1\nx=$(unset EXPECT_OVERFLOW_CHECKS; export EXPECT_OVERFLOW_CHECKS=1; cargo test --locked --lib)\n",
"x=$(export EXPECT_OVERFLOW_CHECKS=1; cargo test --locked --lib)\n",
"x=$(y=$(export EXPECT_OVERFLOW_CHECKS=1; cargo test --locked --lib))\n",
"export EXPECT_OVERFLOW_CHECKS=1\nx=$(unset EXPECT_OVERFLOW_CHECKS; y=$(export EXPECT_OVERFLOW_CHECKS=1; cargo test --locked --lib))\n",
] {
assert_eq!(
debug_runs_that_prove_the_build_traps(script).len(),
1,
"{script:?}: the cargo test process receives the variable"
);
}
for script in [
"EXPECT_OVERFLOW_CHECKS=1 x=$(cargo test --locked --lib)\n",
"x=$(export EXPECT_OVERFLOW_CHECKS=1)\ncargo test --locked --lib\n",
"export EXPECT_OVERFLOW_CHECKS=1\nx=$(unset EXPECT_OVERFLOW_CHECKS && cargo test --locked --lib)\n",
"export EXPECT_OVERFLOW_CHECKS=1\nx=$(y=$(unset EXPECT_OVERFLOW_CHECKS; cargo test --locked --lib))\n",
"export EXPECT_OVERFLOW_CHECKS=1\nx=$(unset EXPECT_OVERFLOW_CHECKS; false && export EXPECT_OVERFLOW_CHECKS=1; cargo test --locked --lib)\n",
"x=$(export EXPECT_OVERFLOW_CHECKS=1 | cat; cargo test --locked --lib)\n",
"export EXPECT_OVERFLOW_CHECKS=1\nx=$(unset EXPECT_OVERFLOW_CHECKS; y=$(export EXPECT_OVERFLOW_CHECKS=1); cargo test --locked --lib)\n",
] {
assert_eq!(
debug_runs_that_prove_the_build_traps(script),
Vec::<String>::new(),
"{script:?}: the cargo test process does not receive the variable"
);
}
}
#[test]
fn a_real_assignment_beside_a_run_on_one_line_still_counts() {
let script = "export EXPECT_OVERFLOW_CHECKS=1 && cargo test --locked --lib\n";
assert_eq!(debug_runs_that_prove_the_build_traps(script).len(), 1);
}
#[test]
fn the_env_mapping_still_arms_the_step() {
let step = super::Step {
keys: vec!["run".to_string(), "env".to_string()],
run: "cargo test --locked --lib\n".to_string(),
env: vec!["EXPECT_OVERFLOW_CHECKS=1".to_string()],
};
assert_eq!(
super::step_runs_that_prove_the_build_traps(&step),
vec!["cargo test --locked --lib".to_string()]
);
let unset = super::Step {
keys: vec!["run".to_string(), "env".to_string()],
run: "unset EXPECT_OVERFLOW_CHECKS\ncargo test --locked --lib\n".to_string(),
env: vec!["EXPECT_OVERFLOW_CHECKS=1".to_string()],
};
assert!(
super::step_runs_that_prove_the_build_traps(&unset).is_empty(),
"the run starts after the variable was unset"
);
let printed = super::Step {
keys: vec!["run".to_string()],
run: "echo \"EXPECT_OVERFLOW_CHECKS=1\"\ncargo test --locked --lib\n".to_string(),
env: Vec::new(),
};
assert!(
super::step_runs_that_prove_the_build_traps(&printed).is_empty(),
"a step whose only mention is printed is not armed"
);
}
}
mod manifest_parser {
use super::profiles_disabling_overflow_checks;
#[test]
fn the_test_profile_disabling_the_checks_is_caught() {
let manifest = "\
[profile.release]
lto = true
[profile.test]
overflow-checks = false
";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
vec!["profile.test.overflow-checks".to_string()],
);
}
#[test]
fn the_dev_profile_disabling_the_checks_is_caught() {
let manifest = "[profile.dev]\nopt-level = 1\noverflow-checks = false\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
vec!["profile.dev.overflow-checks".to_string()],
);
}
#[test]
fn the_release_profile_disabling_the_checks_is_not_flagged() {
let manifest = "[profile.release]\noverflow-checks = false\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
Vec::<String>::new(),
);
}
#[test]
fn a_commented_out_setting_is_not_a_setting() {
let manifest = "[profile.test]\n# overflow-checks = false\nopt-level = 1\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
Vec::<String>::new(),
);
}
#[test]
fn a_disabling_line_with_a_trailing_comment_is_still_caught() {
let manifest = "[profile.test]\noverflow-checks = false # speeds the suite up\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
vec!["profile.test.overflow-checks".to_string()],
);
}
#[test]
fn another_setting_being_false_is_not_this_one() {
let manifest = "[profile.test]\ndebug-assertions = false\nopt-level = 1\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
Vec::<String>::new(),
);
}
#[test]
fn a_double_quoted_key_is_the_same_key() {
let manifest = "[profile.test]\n\"overflow-checks\" = false\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
vec!["profile.test.overflow-checks".to_string()],
);
}
#[test]
fn a_literal_quoted_key_is_the_same_key() {
let manifest = "[profile.dev]\n'overflow-checks' = false\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
vec!["profile.dev.overflow-checks".to_string()],
);
}
#[test]
fn a_dotted_key_putting_the_profile_on_the_key_side_is_caught() {
let manifest = "[profile]\ntest.overflow-checks = false\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
vec!["profile.test.overflow-checks".to_string()],
);
}
#[test]
fn a_top_level_dotted_key_is_caught() {
let manifest = "profile.test.overflow-checks = false\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
vec!["profile.test.overflow-checks".to_string()],
);
}
#[test]
fn a_quoted_section_is_the_same_section() {
let manifest = "[\"profile\".'test']\noverflow-checks = false\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
vec!["profile.test.overflow-checks".to_string()],
);
}
#[test]
fn the_release_profile_is_exempt_in_the_dotted_spelling_too() {
let manifest = "[profile]\nrelease.overflow-checks = false\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
Vec::<String>::new(),
);
}
#[test]
fn enabling_the_checks_explicitly_is_not_flagged() {
let manifest = "[profile.test]\noverflow-checks = true\n";
assert_eq!(
profiles_disabling_overflow_checks(manifest),
Vec::<String>::new(),
);
}
}
mod gating {
use super::gating_runs_that_prove_the_build_traps;
#[test]
fn a_handshake_in_a_shell_comment_does_not_arm_a_step() {
for block in [
" - run: |\n # EXPECT_OVERFLOW_CHECKS=1 -- see ci_profile.rs\n cargo test --locked --lib\n",
" - run: |\n cargo test --locked --lib # EXPECT_OVERFLOW_CHECKS=1 is set in CI\n",
] {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
block,
);
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"the variable is named in a comment, so the process never receives \
it and the runtime probe asserts nothing: {block:?}"
);
}
}
#[test]
fn a_handshake_glued_to_a_terminator_does_not_arm_a_step() {
for block in [
" - run: |\n cargo test --locked --lib;# EXPECT_OVERFLOW_CHECKS=1 is set in CI\n",
" - run: |\n (cargo test --locked --lib)# EXPECT_OVERFLOW_CHECKS=1 is set in CI\n",
" - run: |\n cargo test --locked --lib && echo ok&&# EXPECT_OVERFLOW_CHECKS=1 is set in CI\n",
] {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
block,
);
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"the variable is named in a comment, so the process never receives \
it and the runtime probe asserts nothing: {block:?}"
);
}
}
#[test]
fn a_handshake_elsewhere_in_the_step_does_not_arm_the_run() {
for block in [
" - run: |\n cargo test --locked --lib; export EXPECT_OVERFLOW_CHECKS=1\n",
" - run: |\n EXPECT_OVERFLOW_CHECKS=1 echo x; cargo test --locked --lib\n",
" - run: |\n cargo test --locked --lib\n export EXPECT_OVERFLOW_CHECKS=1\n",
" - run: |\n EXPECT_OVERFLOW_CHECKS=1 echo x\n cargo test --locked --lib\n",
] {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
block,
);
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"handshake_must_reach_the_run: the cargo test process never receives the \
variable, so the runtime probe asserts nothing: {block:?}"
);
}
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
" - run: |\n export EXPECT_OVERFLOW_CHECKS=1\n cargo test --locked --lib\n",
);
assert_eq!(gating_runs_that_prove_the_build_traps(&yaml).len(), 1);
}
#[test]
fn both_real_spellings_of_the_handshake_still_arm_a_step() {
for block in [
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
" - run: cargo test --locked --lib\n env:\n EXPECT_OVERFLOW_CHECKS: \"1\"\n",
" - run: |\n # the guard is armed below, not here\n EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
] {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
block,
);
assert_eq!(
gating_runs_that_prove_the_build_traps(&yaml).len(),
1,
"this step really does ask the build to prove it traps: {block:?}"
);
}
}
const GATING: &str = "\
on:
pull_request:
branches: [main]
jobs:
test:
steps:
- run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib
";
#[test]
fn the_control_shape_gates() {
assert_eq!(
gating_runs_that_prove_the_build_traps(GATING).len(),
1,
"the control must be counted, or every test below passes for the wrong reason"
);
}
#[test]
fn a_step_carrying_if_does_not_gate() {
for condition in [
"if: false",
"if: ${{ false }}",
"if: github.event_name == 'push'",
"if: ${{ env.SOMETHING == 'yes' }}",
] {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
&format!(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n {condition}\n"
),
);
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"a step carrying `{condition}` may or may not run, so it cannot be what \
makes the gate able to see an overflow. Rejected on the key's presence \
rather than by evaluating it -- the spellings are open-ended."
);
}
}
#[test]
fn a_step_carrying_continue_on_error_does_not_gate() {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n continue-on-error: true\n",
);
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"the step runs and its failure is discarded, which is the project's own named \
defect: a step that runs and whose result nothing reads"
);
}
#[test]
fn a_job_carrying_if_does_not_gate() {
let yaml = GATING.replace(" test:\n", " test:\n if: false\n");
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"the same reasoning one level up: a job that may not run cannot gate"
);
}
#[test]
fn a_job_carrying_continue_on_error_does_not_gate() {
let yaml = GATING.replace(" test:\n", " test:\n continue-on-error: true\n");
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"a job whose failure is discarded cannot gate, however sound its steps"
);
}
#[test]
fn a_workflow_that_no_longer_runs_on_pull_request_does_not_gate() {
let yaml = GATING.replace(
" pull_request:\n branches: [main]\n",
" push:\n branches: [main]\n",
);
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"scoping the scan to ci.yml assumes ci.yml is what runs on a pull request; if its \
triggers stop including pull_request, the step gates nothing no matter how it looks"
);
}
#[test]
fn a_run_block_is_read_whole() {
let yaml = "\
on:
pull_request:
branches: [main]
jobs:
test:
steps:
- name: a block
run: |
set -euo pipefail
EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib
";
assert_eq!(
gating_runs_that_prove_the_build_traps(yaml).len(),
1,
"a command inside a `run: |` block must be seen; the kernel-gate loops live in \
blocks like this one"
);
}
#[test]
fn a_quoted_key_is_the_same_key() {
for spelling in [
"\"if\": false",
"'if': false",
"\"continue-on-error\": true",
"'continue-on-error': true",
] {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
&format!(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n {spelling}\n"
),
);
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"`{spelling}` is the same key as its bare spelling; quoting it must not \
make a skipped step count as the thing gating the merge"
);
}
}
#[test]
fn a_quoted_key_on_the_job_is_the_same_key() {
for spelling in ["\"if\": false", "\"continue-on-error\": true"] {
let yaml = GATING.replace(" test:\n", &format!(" test:\n {spelling}\n"));
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"`{spelling}` on the job is the same key as its bare spelling"
);
}
}
#[test]
fn a_commented_out_pull_request_trigger_does_not_gate() {
let commented_with_another_trigger_left = GATING.replace(
" pull_request:\n branches: [main]\n",
" # pull_request:\n # branches: [main]\n push:\n branches: [main]\n",
);
let only_a_comment_naming_it = GATING.replace(
" pull_request:\n branches: [main]\n",
" # pull_request disabled while we investigate flaky runners\n push:\n branches: [main]\n",
);
for yaml in [
&commented_with_another_trigger_left,
&only_a_comment_naming_it,
] {
assert!(
gating_runs_that_prove_the_build_traps(yaml).is_empty(),
"a trigger named only in a comment is not a trigger; the parser drops \
comments before anything compares a name, so there is no `#` to strip \
and none to forget:\n{yaml}"
);
}
}
#[test]
fn a_trigger_that_merely_begins_with_pull_request_does_not_gate() {
let yaml = GATING.replace(
" pull_request:\n branches: [main]\n",
" pull_request_review:\n types: [submitted]\n",
);
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"pull_request_review is not pull_request; a substring match cannot tell \
them apart and this comparison must"
);
}
#[test]
fn pull_request_target_does_not_gate() {
let yaml = GATING.replace(
" pull_request:\n branches: [main]\n",
" pull_request_target:\n branches: [main]\n",
);
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"pull_request_target runs with the base repository's token and secrets \
and checks out the base ref; it is not proof that the merge is gated"
);
}
#[test]
fn a_sequence_of_triggers_is_read() {
for spelling in [
"on: [push, pull_request]\n",
"on:\n - push\n - pull_request\n",
] {
let yaml = GATING.replace("on:\n pull_request:\n branches: [main]\n", spelling);
assert_eq!(
gating_runs_that_prove_the_build_traps(&yaml).len(),
1,
"this workflow triggers on a pull request as surely as the mapping \
spelling does:\n{yaml}"
);
}
}
#[test]
fn every_block_scalar_style_is_read_whole() {
for style in ["|", "|-", "|+", ">", ">-", "|2"] {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
&format!(
" - name: a block\n run: {style}\n EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n"
),
);
assert_eq!(
gating_runs_that_prove_the_build_traps(&yaml).len(),
1,
"`run: {style}` is a legal block scalar carrying the gating command; \
failing here is the guard refusing a correct workflow:\n{yaml}"
);
}
}
#[test]
fn a_debug_run_quoted_in_a_yaml_comment_does_not_gate() {
let yaml = "\
on:
pull_request:
branches: [main]
jobs:
test:
steps:
# Do not remove this as a duplicate of the runs above it:
# - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib
- run: cargo test --locked --release
";
assert!(
gating_runs_that_prove_the_build_traps(yaml).is_empty(),
"the gating command appears only inside a comment, and the step that \
remains is a release run"
);
}
#[test]
#[should_panic(expected = "not valid YAML")]
fn a_workflow_that_does_not_parse_is_a_failure() {
super::parse_workflow("jobs:\n test:\n - broken: [unclosed\n");
}
#[test]
fn a_workflow_carrying_both_triggers_still_gates() {
let yaml = GATING.replace(
" pull_request:\n branches: [main]\n",
" pull_request:\n branches: [main]\n pull_request_target:\n branches: [main]\n",
);
assert_ne!(yaml, GATING, "the mutation must actually apply");
assert_eq!(
gating_runs_that_prove_the_build_traps(&yaml).len(),
1,
"the workflow still triggers on pull_request, so it still gates; refusing it \
because pull_request_target is also present would be the over-correction"
);
}
#[test]
fn the_handshake_declared_in_an_env_mapping_counts() {
for value in ["\"1\"", "1", "'1'"] {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
&format!(
" - run: cargo test --locked --lib\n env:\n EXPECT_OVERFLOW_CHECKS: {value}\n"
),
);
assert_eq!(
gating_runs_that_prove_the_build_traps(&yaml).len(),
1,
"`EXPECT_OVERFLOW_CHECKS: {value}` in an env mapping is the same \
instruction to Actions as the inline prefix, and on a Windows \
matrix it is the only one that works:\n{yaml}"
);
}
}
#[test]
fn the_handshake_in_an_env_mapping_does_not_count_on_a_release_run() {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
" - run: cargo test --locked --release --lib\n env:\n EXPECT_OVERFLOW_CHECKS: \"1\"\n",
);
assert!(
gating_runs_that_prove_the_build_traps(&yaml).is_empty(),
"a release run cannot prove the build traps, however it is labelled"
);
}
#[test]
fn a_step_carrying_an_env_mapping_still_gates() {
let yaml = GATING.replace(
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n",
" - run: EXPECT_OVERFLOW_CHECKS=1 cargo test --locked --lib\n env:\n SOMETHING_ELSE: \"1\"\n",
);
assert_eq!(
gating_runs_that_prove_the_build_traps(&yaml).len(),
1,
"`env:` says nothing about whether the step's result is read"
);
}
}