Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions crates/socket-patch-cli/tests/common/hermetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ pub enum Extra {
/// Every `POETRY_*` var: Poetry reads any config key from
/// `POETRY_<KEY>` (`POETRY_VIRTUALENVS_PATH`, repository URLs, ...).
Poetry,
/// Every `COMPOSER_*` var, then `COMPOSER_HOME` pinned to a missing
/// directory. The crawler resolves a project's vendor directory from
/// `COMPOSER_VENDOR_DIR`, else the user-level `config.json` in the
/// Composer home, which it finds through `COMPOSER_HOME` or an
/// absolute `XDG_CONFIG_HOME`; any of them moves the crawl off the
/// fixture's `vendor/`. The pin outranks `XDG_CONFIG_HOME` and holds
/// no config. Run it before the suite sets its own `COMPOSER_*` values.
Composer,
}

/// Apply each of `extras` to `cmd` (see [`Extra`]). Run it before
Expand Down Expand Up @@ -279,6 +287,14 @@ pub fn scrub_extra(cmd: &mut Command, extras: &[Extra]) {
}
}
}
Extra::Composer => {
for (key, _) in std::env::vars_os() {
if key.to_string_lossy().starts_with("COMPOSER_") {
cmd.env_remove(&key);
}
}
cmd.env("COMPOSER_HOME", "/nonexistent/composer-home");
}
}
}
}
27 changes: 12 additions & 15 deletions crates/socket-patch-cli/tests/e2e_cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@

#[path = "common/mod.rs"]
mod common;
use common::{binary, git_sha256};
use common::{binary, git_sha256, hermetic};

use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::process::Output;

use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
Expand Down Expand Up @@ -46,27 +46,24 @@ async fn start_proxy() -> MockServer {
/// Run the binary as a blocking subprocess (off the async runtime so the
/// in-test proxy can service its requests concurrently), pinned to `proxy_url`.
///
/// `SOCKET_API_TOKEN` is stripped so the binary deterministically takes the
/// public-proxy path (an ambient token would flip it onto the authenticated
/// API, bypassing `--proxy-url`), and every other variable that could
/// redirect the API elsewhere or disable it is scrubbed so an ambient value
/// can't quietly change what the scan reports.
/// The child gets the hermetic `SOCKET_*` environment (no token, so the
/// binary deterministically takes the public-proxy path `--proxy-url`
/// pins). The scans are unscoped, so an activated venv is scrubbed and
/// `HOME` points at a stand-in (`jvm_env::isolate_cli`): the developer's
/// venv, `~/.m2` or user site-packages would add packages to the counts
/// these tests assert. `CARGO_HOME` is the fixture's own `.cargo`.
async fn run(args: &[&str], cwd: &Path, proxy_url: &str) -> Output {
let mut args: Vec<String> = args.iter().map(|s| s.to_string()).collect();
args.extend(["--proxy-url".to_string(), proxy_url.to_string()]);
let cwd = cwd.to_path_buf();
tokio::task::spawn_blocking(move || {
let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect();
Command::new(binary())
.args(&arg_refs)
let mut cmd = hermetic::command(&binary());
hermetic::scrub_extra(&mut cmd, &[hermetic::Extra::Venv]);
common::jvm_env::isolate_cli(&mut cmd);
cmd.args(&arg_refs)
.current_dir(&cwd)
.env("CARGO_HOME", cwd.join(".cargo"))
.env_remove("SOCKET_API_TOKEN")
.env_remove("SOCKET_CLI_API_TOKEN")
.env_remove("SOCKET_API_URL")
.env_remove("SOCKET_OFFLINE")
.env_remove("SOCKET_PROXY_URL")
.env_remove("SOCKET_BATCH_SIZE")
.output()
.expect("Failed to run socket-patch binary")
})
Expand Down
30 changes: 17 additions & 13 deletions crates/socket-patch-cli/tests/e2e_composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

#[path = "common/mod.rs"]
mod common;
use common::binary;
use common::{binary, hermetic};

use std::process::{Command, Output};
use std::process::Output;

use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
Expand All @@ -43,24 +43,28 @@ async fn start_proxy() -> MockServer {

/// Run the binary as a blocking subprocess (off the async runtime so the
/// in-test proxy can service its requests concurrently), pinned to
/// `proxy_url`. `SOCKET_API_TOKEN` is stripped so the binary
/// deterministically takes the public-proxy path, and every other variable
/// that could redirect the API elsewhere or disable it is scrubbed.
/// `proxy_url`. The child gets the hermetic `SOCKET_*` environment (no
/// token, so the binary deterministically takes the public-proxy path
/// `--proxy-url` pins). The scans are unscoped, so an activated venv is
/// scrubbed and `HOME` points at a stand-in (`jvm_env::isolate_cli`): the
/// developer's venv, `~/.m2` or user site-packages would add packages to
/// the counts these tests assert. `Extra::Composer` keeps an ambient
/// `COMPOSER_VENDOR_DIR` or user-level Composer config from moving the
/// vendor directory the crawler reads off the fixture's.
async fn run(args: &[&str], cwd: &std::path::Path, proxy_url: &str) -> Output {
let mut args: Vec<String> = args.iter().map(|s| s.to_string()).collect();
args.extend(["--proxy-url".to_string(), proxy_url.to_string()]);
let cwd = cwd.to_path_buf();
tokio::task::spawn_blocking(move || {
let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect();
Command::new(binary())
.args(&arg_refs)
let mut cmd = hermetic::command(&binary());
hermetic::scrub_extra(
&mut cmd,
&[hermetic::Extra::Venv, hermetic::Extra::Composer],
);
common::jvm_env::isolate_cli(&mut cmd);
cmd.args(&arg_refs)
.current_dir(&cwd)
.env_remove("SOCKET_API_TOKEN")
.env_remove("SOCKET_CLI_API_TOKEN")
.env_remove("SOCKET_API_URL")
.env_remove("SOCKET_OFFLINE")
.env_remove("SOCKET_PROXY_URL")
.env_remove("SOCKET_BATCH_SIZE")
.output()
.expect("Failed to run socket-patch binary")
})
Expand Down
24 changes: 14 additions & 10 deletions crates/socket-patch-cli/tests/e2e_composer_version_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
//! `scan --redirect` repoints the lock entry. Each test runs the built binary
//! against a wiremock API that serves only the padded spelling.

#[path = "common/hermetic.rs"]
mod hermetic;
#[path = "prebuilt_common/mod.rs"]
mod prebuilt_common;

Expand Down Expand Up @@ -180,17 +182,19 @@ async fn mount_api(server: &MockServer, prebuilt: bool) {
.await;
}

/// The built binary with every ambient `SOCKET_*` variable scrubbed and
/// telemetry off, so the assertions reflect only the argv.
/// The built binary in the hermetic `SOCKET_*` environment, so the
/// assertions reflect only the argv. Its scans and `vex` runs crawl every
/// ecosystem, so an activated venv is scrubbed and `HOME` points at a
/// stand-in (`jvm_env::isolate_cli`), and `Extra::Composer` keeps an
/// ambient `COMPOSER_VENDOR_DIR` or user-level Composer config from moving
/// the vendor directory off the fixture's.
fn cli() -> Command {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_socket-patch"));
for (key, _) in std::env::vars_os() {
let name = key.to_string_lossy();
if name.starts_with("SOCKET_") && name != "SOCKET_NO_CONFIG" {
cmd.env_remove(&key);
}
}
cmd.env("SOCKET_TELEMETRY_DISABLED", "1");
let mut cmd = hermetic::binary_command();
hermetic::scrub_extra(
&mut cmd,
&[hermetic::Extra::Venv, hermetic::Extra::Composer],
);
prebuilt_common::jvm_env::isolate_cli(&mut cmd);
cmd
}

Expand Down
36 changes: 33 additions & 3 deletions crates/socket-patch-cli/tests/spawn_env_hygiene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ use serial_test::serial;
/// scrubs by hand.
const PENDING_RAW_SPAWNS: &[&str] = &[
"apply/apply_network.rs",
"e2e_cargo.rs",
"e2e_composer.rs",
"e2e_composer_version_identity.rs",
"e2e_gem.rs",
"e2e_hosted_production.rs",
"e2e_maven.rs",
Expand Down Expand Up @@ -457,6 +454,39 @@ fn bundle_extra_sweeps_every_bundler_override() {
);
}

#[test]
#[serial]
fn composer_extra_sweeps_overrides_and_pins_an_empty_home() {
with_ambient(
&[
("COMPOSER_VENDOR_DIR", "third"),
("COMPOSER_HOME", "/ambient/composer"),
("COMPOSER_CACHE_DIR", "/ambient/composer-cache"),
("XDG_CONFIG_HOME", "/ambient/config"),
],
|| {
let mut cmd = Command::new("socket-patch");
hermetic::scrub_extra(&mut cmd, &[Extra::Composer]);
let env = effective_env(&cmd);
assert_eq!(get(&env, "COMPOSER_VENDOR_DIR"), None);
assert_eq!(get(&env, "COMPOSER_CACHE_DIR"), None);
// The pin outranks XDG_CONFIG_HOME in the Composer home lookup.
assert_eq!(
get(&env, "COMPOSER_HOME"),
Some("/nonexistent/composer-home")
);
assert_eq!(get(&env, "XDG_CONFIG_HOME"), Some("/ambient/config"));

// A suite's own COMPOSER_HOME, set after the scrub, survives.
cmd.env("COMPOSER_HOME", "/fixture/home");
assert_eq!(
get(&effective_env(&cmd), "COMPOSER_HOME"),
Some("/fixture/home")
);
},
);
}

#[test]
#[serial]
fn extra_seeds_are_scrubbed_whatever_the_ambient_value() {
Expand Down
Loading