Skip to content

Commit 453591c

Browse files
committed
rename custom provider to bash
1 parent f94cdca commit 453591c

11 files changed

Lines changed: 143 additions & 72 deletions

File tree

abx_plugins/plugins/apt/tests/test_apt_provider.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def test_hook_detects_apt(self):
7373
if apt_available():
7474
assert "apt not available" not in result.stderr
7575
else:
76-
assert result.returncode == 1
76+
assert result.returncode == 0
7777
assert (
7878
"AptProvider.INSTALLER_BIN is not available on this host"
7979
in result.stderr
@@ -114,7 +114,7 @@ def test_detect_existing_binary(self):
114114
)
115115

116116
if not apt_available():
117-
assert result.returncode == 1
117+
assert result.returncode == 0
118118
assert (
119119
"AptProvider.INSTALLER_BIN is not available on this host"
120120
in result.stderr

abx_plugins/plugins/bash/on_BinaryRequest__14_bash.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
# ]
1111
# ///
1212
#
13-
# Install a binary using a custom shell command defined in overrides.
13+
# Install a binary using a bash shell command defined in overrides.
1414
# This provider runs arbitrary shell commands to install binaries that don't fit
1515
# into standard package managers, outputting a Binary JSONL record to stdout.
1616
#
1717
# Usage:
18-
# ./on_BinaryRequest__14_custom.py [...] > events.jsonl
18+
# ./on_BinaryRequest__14_bash.py [...] > events.jsonl
1919

2020
import sys
2121
import json
@@ -46,25 +46,19 @@ def main(
4646
"""Install binary using bash shell commands defined in overrides."""
4747

4848
allowed_providers = {provider.strip() for provider in binproviders.split(",")}
49-
if (
50-
binproviders != "*"
51-
and "custom" not in allowed_providers
52-
and "bash" not in allowed_providers
53-
):
54-
click.echo(f"custom provider not allowed for {name}", err=True)
49+
if binproviders != "*" and "bash" not in allowed_providers:
50+
click.echo(f"bash provider not allowed for {name}", err=True)
5551
sys.exit(0)
5652

5753
context = click.get_current_context(silent=True)
5854
extra_kwargs = parse_extra_hook_args(context.args if context else [])
5955
raw_overrides = json.loads(overrides)
6056
if not isinstance(raw_overrides, dict):
6157
click.echo(
62-
"custom provider requires overrides to decode to an object",
58+
"bash provider requires overrides to decode to an object",
6359
err=True,
6460
)
6561
sys.exit(1)
66-
if "bash" not in raw_overrides and "custom" in raw_overrides:
67-
raw_overrides = {**raw_overrides, "bash": raw_overrides["custom"]}
6862

6963
provider = BashProvider()
7064
binary = Binary.model_validate(
@@ -79,19 +73,19 @@ def main(
7973
bash_overrides = binary.overrides.get("bash", {})
8074
if "install" not in bash_overrides:
8175
click.echo(
82-
"custom provider requires overrides.custom.install or overrides.bash.install",
76+
"bash provider requires overrides.bash.install",
8377
err=True,
8478
)
8579
sys.exit(1)
8680

8781
try:
8882
binary = binary.load_or_install()
8983
except Exception as e:
90-
click.echo(f"custom install failed: {e}", err=True)
84+
click.echo(f"bash install failed: {e}", err=True)
9185
sys.exit(1)
9286

9387
if not binary.abspath:
94-
click.echo(f"{name} not found after custom install", err=True)
88+
click.echo(f"{name} not found after bash install", err=True)
9589
sys.exit(1)
9690

9791
# Output Binary JSONL record to stdout
@@ -100,7 +94,7 @@ def main(
10094
abspath=str(binary.abspath),
10195
version=str(binary.version) if binary.version else "",
10296
sha256=binary.sha256 or "",
103-
binprovider="custom",
97+
binprovider="bash",
10498
)
10599

106100
# Log human-readable info to stderr
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<span class="abx-output-icon abx-output-icon--custom" title="Custom"><svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 7h14"/><path d="M5 12h14"/><path d="M5 17h14"/><circle cx="9" cy="7" r="1.5"/><circle cx="15" cy="12" r="1.5"/><circle cx="11" cy="17" r="1.5"/></svg></span>
1+
<span class="abx-output-icon abx-output-icon--bash" title="Bash"><svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 7h14"/><path d="M5 12h14"/><path d="M5 17h14"/><circle cx="9" cy="7" r="1.5"/><circle cx="15" cy="12" r="1.5"/><circle cx="11" cy="17" r="1.5"/></svg></span>

abx_plugins/plugins/bash/tests/test_bash_provider.py

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
"""
2-
Tests for the custom binary provider plugin.
2+
Tests for the bash binary provider plugin.
33
4-
Tests the custom bash binary installer with safe commands.
4+
Tests the bash command installer with safe commands.
55
"""
66

77
import json
88
import os
9+
import shutil
910
import subprocess
1011
import tempfile
1112
from pathlib import Path
1213

1314
import pytest
1415

1516

16-
# Get the path to the custom provider hook
17+
# Get the path to the bash provider hook
1718
PLUGIN_DIR = Path(__file__).parent.parent
18-
INSTALL_HOOK = next(PLUGIN_DIR.glob("on_BinaryRequest__*_custom.py"), None)
19+
INSTALL_HOOK = next(PLUGIN_DIR.glob("on_BinaryRequest__*_bash.py"), None)
20+
BASH_ZX_INSTALL = (
21+
'npm install --quiet --prefix "$INSTALL_ROOT/npm" zx '
22+
'&& ln -sf "$INSTALL_ROOT/npm/node_modules/.bin/zx" "$BIN_DIR/bash-zx"'
23+
)
1924

2025

21-
class TestCustomProviderHook:
22-
"""Test the custom binary provider hook."""
26+
class TestBashProviderHook:
27+
"""Test the bash binary provider hook."""
2328

2429
def setup_method(self, _method=None):
2530
"""Set up test environment."""
@@ -35,17 +40,17 @@ def test_hook_script_exists(self):
3540
"""Hook script should exist."""
3641
assert INSTALL_HOOK and INSTALL_HOOK.exists(), f"Hook not found: {INSTALL_HOOK}"
3742

38-
def test_hook_skips_when_custom_not_allowed(self):
39-
"""Hook should skip when custom not in allowed binproviders."""
43+
def test_hook_skips_when_bash_not_allowed(self):
44+
"""Hook should skip when bash not in allowed binproviders."""
4045
env = os.environ.copy()
4146
env["SNAP_DIR"] = self.temp_dir
42-
overrides = json.dumps({"custom": {"install": "echo hello"}})
47+
overrides = json.dumps({"bash": {"install": "echo hello"}})
4348

4449
result = subprocess.run(
4550
[
4651
str(INSTALL_HOOK),
4752
"--name=echo",
48-
"--binproviders=pip,apt", # custom not allowed
53+
"--binproviders=pip,apt", # bash not allowed
4954
f"--overrides={overrides}",
5055
],
5156
capture_output=True,
@@ -54,33 +59,34 @@ def test_hook_skips_when_custom_not_allowed(self):
5459
env=env,
5560
)
5661

57-
# Should exit cleanly (code 0) when custom not allowed
62+
# Should exit cleanly (code 0) when bash not allowed
5863
assert result.returncode == 0
59-
assert "custom provider not allowed" in result.stderr
64+
assert "bash provider not allowed" in result.stderr
65+
66+
def test_hook_runs_bash_command_and_finds_binary(self):
67+
"""Hook should run a real bash install command and emit the installed binary."""
68+
if not shutil.which("npm"):
69+
pytest.skip("npm is required for the real bash provider install test")
6070

61-
def test_hook_runs_custom_command_and_finds_binary(self):
62-
"""Hook should run custom command and find the binary in PATH."""
6371
env = os.environ.copy()
6472
env["SNAP_DIR"] = self.temp_dir
73+
env["HOME"] = self.temp_dir
6574
overrides = json.dumps(
66-
{"custom": {"install": 'echo "custom install simulation"'}},
75+
{"bash": {"install": BASH_ZX_INSTALL}},
6776
)
6877

69-
# Use a simple echo command that doesn't actually install anything
70-
# Then check for 'echo' which is already in PATH
7178
result = subprocess.run(
7279
[
7380
str(INSTALL_HOOK),
74-
"--name=echo",
81+
"--name=bash-zx",
7582
f"--overrides={overrides}",
7683
],
7784
capture_output=True,
7885
text=True,
79-
timeout=30,
86+
timeout=120,
8087
env=env,
8188
)
8289

83-
# Should succeed since echo is in PATH
8490
assert result.returncode == 0, f"Hook failed: {result.stderr}"
8591

8692
# Parse JSONL output
@@ -89,20 +95,24 @@ def test_hook_runs_custom_command_and_finds_binary(self):
8995
if line.startswith("{"):
9096
try:
9197
record = json.loads(line)
92-
if record.get("type") == "Binary" and record.get("name") == "echo":
93-
assert record["binprovider"] == "custom"
98+
if (
99+
record.get("type") == "Binary"
100+
and record.get("name") == "bash-zx"
101+
):
102+
assert record["binprovider"] == "bash"
94103
assert record["abspath"]
104+
assert Path(record["abspath"]).exists()
95105
return
96106
except json.JSONDecodeError:
97107
continue
98108

99109
pytest.fail("No Binary JSONL record found in output")
100110

101111
def test_hook_fails_for_missing_binary_after_command(self):
102-
"""Hook should fail if binary not found after running custom command."""
112+
"""Hook should fail if binary not found after running bash command."""
103113
env = os.environ.copy()
104114
env["SNAP_DIR"] = self.temp_dir
105-
overrides = json.dumps({"custom": {"install": 'echo "failed install"'}})
115+
overrides = json.dumps({"bash": {"install": 'echo "failed install"'}})
106116

107117
result = subprocess.run(
108118
[
@@ -118,13 +128,15 @@ def test_hook_fails_for_missing_binary_after_command(self):
118128

119129
# Should fail since binary not found after command
120130
assert result.returncode == 1
121-
assert "not found" in result.stderr.lower()
131+
assert "unable to load or install binary nonexistent_binary_xyz123" in (
132+
result.stderr.lower()
133+
)
122134

123135
def test_hook_fails_for_failing_command(self):
124-
"""Hook should fail if custom command returns non-zero exit code."""
136+
"""Hook should fail if bash command returns non-zero exit code."""
125137
env = os.environ.copy()
126138
env["SNAP_DIR"] = self.temp_dir
127-
overrides = json.dumps({"custom": {"install": "exit 1"}})
139+
overrides = json.dumps({"bash": {"install": "exit 1"}})
128140

129141
result = subprocess.run(
130142
[

abx_plugins/plugins/chrome/chrome_utils.js

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,12 +1899,56 @@ function getExtensionTargets(browser) {
18991899
function findChromium() {
19001900
const { execFileSync } = require('child_process');
19011901

1902+
const resolveWrappedBinaryTarget = (binaryPath) => {
1903+
if (!binaryPath || !fs.existsSync(binaryPath)) return binaryPath;
1904+
try {
1905+
const stat = fs.statSync(binaryPath);
1906+
if (!stat.isFile() || stat.size > 4096) return binaryPath;
1907+
const contents = fs.readFileSync(binaryPath, 'utf8');
1908+
if (!contents.startsWith('#!')) return binaryPath;
1909+
const execMatch = contents.match(/^exec\s+(.+?)\s+"\$@"\s*$/m);
1910+
if (!execMatch) return binaryPath;
1911+
const rawTarget = execMatch[1].trim();
1912+
const unquotedTarget = rawTarget.replace(/^['"]|['"]$/g, '');
1913+
return unquotedTarget || binaryPath;
1914+
} catch (e) {
1915+
return binaryPath;
1916+
}
1917+
};
1918+
1919+
const resolveMacAppBundle = (binaryPath) => {
1920+
const targetPath = resolveWrappedBinaryTarget(binaryPath);
1921+
if (!targetPath) return null;
1922+
const appMarker = `.app${path.sep}`;
1923+
const appIndex = targetPath.indexOf(appMarker);
1924+
if (appIndex === -1) return null;
1925+
return targetPath.slice(0, appIndex + 4);
1926+
};
1927+
1928+
const validateMacBrowserBundle = (binaryPath) => {
1929+
if (process.platform !== 'darwin') return true;
1930+
const appBundle = resolveMacAppBundle(binaryPath);
1931+
if (!appBundle) return true;
1932+
try {
1933+
execFileSync('spctl', ['-a', '-vv', appBundle], {
1934+
encoding: 'utf8',
1935+
timeout: 5000,
1936+
stdio: 'pipe',
1937+
});
1938+
return true;
1939+
} catch (e) {
1940+
const targetPath = resolveWrappedBinaryTarget(binaryPath);
1941+
console.error(`[!] Warning: rejecting unusable macOS browser bundle: ${targetPath}`);
1942+
return false;
1943+
}
1944+
};
1945+
19021946
// Helper to validate a binary by running --version
19031947
const validateBinary = (binaryPath) => {
19041948
if (!binaryPath) return false;
19051949
try {
19061950
execFileSync(binaryPath, ['--version'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' });
1907-
return true;
1951+
return validateMacBrowserBundle(binaryPath);
19081952
} catch (e) {
19091953
return false;
19101954
}
@@ -1973,11 +2017,8 @@ function findChromium() {
19732017
const versionDir = path.join(versionRoot, version);
19742018
const candidates = [
19752019
path.join(versionDir, 'chrome-mac-arm64/Chromium.app/Contents/MacOS/Chromium'),
1976-
path.join(versionDir, 'chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing'),
19772020
path.join(versionDir, 'chrome-mac/Chromium.app/Contents/MacOS/Chromium'),
1978-
path.join(versionDir, 'chrome-mac/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing'),
19792021
path.join(versionDir, 'chrome-mac-x64/Chromium.app/Contents/MacOS/Chromium'),
1980-
path.join(versionDir, 'chrome-mac-x64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing'),
19812022
path.join(versionDir, 'chrome-linux64/chrome'),
19822023
path.join(versionDir, 'chrome-linux/chrome'),
19832024
];
@@ -2017,6 +2058,10 @@ function findChromium() {
20172058
const fallbackLocations = [
20182059
// System Chromium
20192060
'/Applications/Chromium.app/Contents/MacOS/Chromium',
2061+
// Last-resort system Chrome-family fallbacks for macOS when
2062+
// Puppeteer-managed Chromium builds are present but unusable.
2063+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
2064+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
20202065
'/usr/bin/chromium',
20212066
'/usr/bin/chromium-browser',
20222067
// Puppeteer cache

abx_plugins/plugins/chrome/tests/chrome_test_helpers.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1089,8 +1089,13 @@ def install_chromium_with_hooks(env: dict, timeout: int = 300) -> str:
10891089
f"Chromium binary not found after install: {chromium_path}",
10901090
)
10911091

1092-
env["CHROME_BINARY"] = chromium_path
10931092
apply_machine_updates(records, env)
1093+
env["CHROME_BINARY"] = chromium_path
1094+
1095+
resolved = _resolve_existing_chromium(env)
1096+
if resolved:
1097+
env["CHROME_BINARY"] = resolved
1098+
return resolved
10941099
return chromium_path
10951100

10961101

@@ -1529,6 +1534,7 @@ def chrome_session(
15291534
test_url: str = "about:blank",
15301535
navigate: bool = True,
15311536
timeout: int = 15,
1537+
env_overrides: dict[str, str] | None = None,
15321538
):
15331539
"""Context manager for the full crawl -> snapshot -> optional navigate flow.
15341540
@@ -1556,6 +1562,7 @@ def chrome_session(
15561562
test_url: URL to navigate to (if navigate=True)
15571563
navigate: Whether to navigate to the URL after creating tab
15581564
timeout: Seconds to wait for Chrome to start
1565+
env_overrides: Runtime env values to preserve from caller setup
15591566
15601567
Yields:
15611568
Tuple of (chrome_launch_process, chrome_pid, snapshot_chrome_dir, env)
@@ -1589,6 +1596,8 @@ def chrome_session(
15891596
xdg_cache_home = home_dir / ".cache"
15901597
xdg_data_home = home_dir / ".local" / "share"
15911598
env = os.environ.copy()
1599+
if env_overrides:
1600+
env.update(env_overrides)
15921601

15931602
# Prefer an already-provisioned NODE_MODULES_DIR (set by session-level chrome fixture)
15941603
# so we don't force per-test reinstall under tmp LIB_DIR paths.

abx_plugins/plugins/env/tests/test_env_provider.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def test_hook_runs_before_other_binary_provider_hooks(self):
3939
"""Env discovery should sort before install-capable provider hooks."""
4040
other_provider_hooks = [
4141
next((PLUGIN_DIR.parent / provider).glob("on_BinaryRequest__*.py"), None)
42-
for provider in ("npm", "pip", "brew", "apt", "custom")
42+
for provider in ("npm", "pip", "brew", "apt", "bash")
4343
]
4444

4545
assert INSTALL_HOOK is not None, "Env hook should exist"

abx_plugins/plugins/redirects/tests/test_redirects.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,19 @@ def test_redirects_captures_navigation(self, chrome_test_urls):
8888
env=env,
8989
)
9090

91+
marker_path = Path(env["SNAP_DIR"]) / "redirects" / "prenav.json"
92+
for _ in range(50):
93+
if marker_path.exists():
94+
marker = json.loads(marker_path.read_text())
95+
if marker.get("status") == "ready":
96+
break
97+
time.sleep(0.1)
98+
else:
99+
stdout, stderr = result.communicate(timeout=20)
100+
pytest.fail(
101+
f"redirects hook never became ready\nstdout:\n{stdout}\nstderr:\n{stderr}",
102+
)
103+
91104
nav_result = subprocess.run(
92105
[
93106
str(CHROME_NAVIGATE_HOOK),

0 commit comments

Comments
 (0)