Skip to content

Commit 8485886

Browse files
committed
Guard against new private test helper copies
A ratchet in the cli test binary fails when a test file outside tests/common defines its own binary() or git_sha256, unless it is on the pending list (files open PRs change, plus the vlt_* shared modules). Stale entries don't fail, so migrating one never turns another PR red. Assisted-by: Claude Code:claude-opus-5-5
1 parent 8012b42 commit 8485886

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

‎crates/socket-patch-cli/tests/cli/main.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ mod covgap_commands_list;
1818
mod covgap_output;
1919
mod interactive_prompts_e2e;
2020
mod output_modes_e2e;
21+
mod shared_helper_copies;
2122
mod telemetry_e2e;
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
//! One `binary()` and one `git_sha256` for every CLI test target (#824).
2+
//!
3+
//! `common/mod.rs` owns both helpers, and its own tests pin `git_sha256`
4+
//! to the canonical Git-blob hash and to production's
5+
//! `compute_git_sha256_from_bytes` (the two shapes the per-file copies
6+
//! took). This ratchet keeps new private copies out: a file may define its
7+
//! own only while it is listed below.
8+
9+
use std::path::Path;
10+
11+
/// Files that still define a private `binary()` or `git_sha256`. Each is
12+
/// changed by an open PR, or is a shared module whose includers don't all
13+
/// declare `mod common`. Migrate a file onto `common` and delete its entry;
14+
/// a stale entry is not an error, so a PR that migrates one never turns
15+
/// another red.
16+
const PENDING_PRIVATE_HELPERS: &[&str] = &[
17+
"apply/apply_network.rs",
18+
"apply/in_process_npm_multicopy.rs",
19+
"cli/api_client_errors_e2e.rs",
20+
"cli/covgap_api_client.rs",
21+
"cli/output_modes_e2e.rs",
22+
"cli/telemetry_e2e.rs",
23+
"cli_scan_silent.rs",
24+
"covgap_commands_scan_mod.rs",
25+
"diff_created_file_e2e.rs",
26+
"docker_e2e_npm.rs",
27+
"docker_e2e_nuget.rs",
28+
"docker_e2e_pypi.rs",
29+
"e2e_embedded_vex.rs",
30+
"e2e_gem.rs",
31+
"e2e_hosted_production.rs",
32+
"e2e_npm.rs",
33+
"e2e_pypi.rs",
34+
"e2e_redirect_gem_build.rs",
35+
"e2e_redirect_gradle_build.rs",
36+
"e2e_redirect_npm_build.rs",
37+
"e2e_redirect_pnpm_build.rs",
38+
"e2e_scan.rs",
39+
"e2e_vendor_composer_build.rs",
40+
"e2e_vendor_npm_build.rs",
41+
"e2e_vendor_pnpm_build.rs",
42+
"e2e_vendor_pypi_build.rs",
43+
"e2e_vex_lockfile/cargo.rs",
44+
"e2e_vex_lockfile/maven.rs",
45+
"e2e_vex_lockfile/uv.rs",
46+
"e2e_vex_lockfile/yarn.rs",
47+
"e2e_vex_redirect.rs",
48+
"e2e_vex_vendor.rs",
49+
"get/get_batch_paths_e2e.rs",
50+
"get/global_packages_e2e.rs",
51+
"in_process_agent_reapply.rs",
52+
"in_process_alternate_installers.rs",
53+
"in_process_cargo_apply.rs",
54+
"in_process_edge_cases.rs",
55+
"in_process_gem_apply.rs",
56+
"in_process_gem_multi_platform.rs",
57+
"in_process_get_manifest_path.rs",
58+
"in_process_pypi_apply.rs",
59+
"in_process_pypi_multi_release.rs",
60+
"in_process_remote_ecosystems_apply.rs",
61+
"in_process_remove_repair_lifecycle.rs",
62+
"mode_migration_cargo.rs",
63+
"mode_migration_npm.rs",
64+
"remove/covgap_commands_remove.rs",
65+
"remove_rollback_api_overrides.rs",
66+
"repair/coverage_fix_repair_vendor_predelete.rs",
67+
"repair/covgap_commands_repair.rs",
68+
"repair/covgap_commands_repair_vendor.rs",
69+
"repair/repair_invariants.rs",
70+
"repair/repair_vendor_e2e.rs",
71+
"repair/repair_vendor_flavors_e2e.rs",
72+
"rollback/rollback_invariants.rs",
73+
"rollback/rollback_multicopy_blob_gate.rs",
74+
"scan/covgap_commands_fetch_stage.rs",
75+
"scan/covgap_commands_scan_vendor_flow.rs",
76+
"scan/scan_invariants.rs",
77+
"scan/scan_paths_e2e.rs",
78+
"scan/scan_sync_e2e.rs",
79+
"scan/scan_vendor_step_error_e2e.rs",
80+
"scan_rollout_e2e.rs",
81+
"scan_vendor_e2e.rs",
82+
"vendor_ecosystem_fixtures/mod.rs",
83+
"vendor_jvm_cli.rs",
84+
"vex_e2e_common/mod.rs",
85+
"vlt_e2e_common/mod.rs",
86+
"vlt_hosted_common/mod.rs",
87+
"vlt_vendor_common/mod.rs",
88+
];
89+
90+
/// Every `.rs` file under `tests/`, as (`/`-joined relative path, text).
91+
fn test_sources() -> Vec<(String, String)> {
92+
fn walk(dir: &Path, root: &Path, out: &mut Vec<(String, String)>) {
93+
let mut entries: Vec<_> = std::fs::read_dir(dir)
94+
.unwrap_or_else(|e| panic!("read {}: {e}", dir.display()))
95+
.map(|e| e.unwrap().path())
96+
.collect();
97+
entries.sort();
98+
for path in entries {
99+
if path.is_dir() {
100+
walk(&path, root, out);
101+
} else if path.extension().is_some_and(|e| e == "rs") {
102+
let rel = path
103+
.strip_prefix(root)
104+
.unwrap()
105+
.components()
106+
.map(|c| c.as_os_str().to_string_lossy().into_owned())
107+
.collect::<Vec<_>>()
108+
.join("/");
109+
let text = std::fs::read_to_string(&path)
110+
.unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
111+
out.push((rel, text));
112+
}
113+
}
114+
}
115+
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests");
116+
let mut out = Vec::new();
117+
walk(&root, &root, &mut out);
118+
out
119+
}
120+
121+
/// Whether `text` defines a `binary()` or `git_sha256(..)` of its own.
122+
fn defines_private_helper(text: &str) -> bool {
123+
text.lines().any(|line| {
124+
let line = line.trim_start();
125+
let line = line
126+
.strip_prefix("pub(crate) ")
127+
.or_else(|| line.strip_prefix("pub "))
128+
.unwrap_or(line);
129+
line.starts_with("fn binary()") || line.starts_with("fn git_sha256(")
130+
})
131+
}
132+
133+
#[test]
134+
fn no_new_private_binary_or_git_sha256() {
135+
let unexpected: Vec<String> = test_sources()
136+
.into_iter()
137+
.filter(|(rel, text)| {
138+
!rel.starts_with("common/")
139+
&& !PENDING_PRIVATE_HELPERS.contains(&rel.as_str())
140+
&& defines_private_helper(text)
141+
})
142+
.map(|(rel, _)| rel)
143+
.collect();
144+
assert!(
145+
unexpected.is_empty(),
146+
"these test files define their own binary() or git_sha256: {unexpected:?}. \
147+
Use `common::binary` / `common::git_sha256` instead (declare \
148+
`#[path = \"common/mod.rs\"] mod common;`, or `use crate::common::..` in a \
149+
directory test binary). Do not add them to PENDING_PRIVATE_HELPERS."
150+
);
151+
}
152+
153+
#[test]
154+
fn the_detector_sees_every_former_copy_shape() {
155+
for copy in [
156+
"fn binary() -> PathBuf {\n env!(\"CARGO_BIN_EXE_socket-patch\").into()\n}",
157+
"fn binary() -> &'static str {\n env!(\"CARGO_BIN_EXE_socket-patch\")\n}",
158+
" fn binary() -> PathBuf {",
159+
"pub fn git_sha256(bytes: &[u8]) -> String {",
160+
"fn git_sha256(content: &[u8]) -> String {",
161+
] {
162+
assert!(defines_private_helper(copy), "missed: {copy}");
163+
}
164+
for not_a_copy in [
165+
"use common::{binary, git_sha256};",
166+
"let out = Command::new(binary()).output();",
167+
"pub fn git_sha256_file(path: &Path) -> String {",
168+
] {
169+
assert!(
170+
!defines_private_helper(not_a_copy),
171+
"false positive: {not_a_copy}"
172+
);
173+
}
174+
}

0 commit comments

Comments
 (0)