mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 05:29:45 +00:00
Compose bounded E2E acquisition for completed exact-source proof
Preserve the returned dependency setup repair and its original parents while qualifying it with current assigned main. This changes only disposable hosted browser acquisition; repositories, authentication, product/runtime bytes and test verdicts stay unchanged. Earlier pre-execution capacity failures remain unexecuted, not passing proof. Change-source: pulse-maintainer
This commit is contained in:
@@ -12,6 +12,8 @@ on:
|
||||
- 'tests/integration/**'
|
||||
- 'Dockerfile'
|
||||
- '.github/workflows/test-e2e.yml'
|
||||
- 'scripts/playwright-apt.conf'
|
||||
- 'scripts/tests/test_playwright_acquisition.py'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -24,6 +26,8 @@ on:
|
||||
- 'tests/integration/**'
|
||||
- 'Dockerfile'
|
||||
- '.github/workflows/test-e2e.yml'
|
||||
- 'scripts/playwright-apt.conf'
|
||||
- 'scripts/tests/test_playwright_acquisition.py'
|
||||
workflow_dispatch:
|
||||
|
||||
# Let branch runs finish so busy main and release branches retain verdicts.
|
||||
@@ -51,6 +55,9 @@ jobs:
|
||||
- name: Check shipped docs before dependency work
|
||||
run: python3 scripts/check_docs_mirror.py
|
||||
|
||||
- name: Validate browser dependency acquisition policy
|
||||
run: python3 scripts/tests/test_playwright_acquisition.py
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
@@ -103,13 +110,21 @@ jobs:
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
# Apply only to this disposable hosted runner, never the maintainer host.
|
||||
# Keep the provider's repositories and apt authentication unchanged.
|
||||
- name: Bound browser OS dependency acquisition
|
||||
run: sudo install -m 0644 scripts/playwright-apt.conf /etc/apt/apt.conf.d/99pulse-playwright-acquire
|
||||
|
||||
- name: Install locked dependencies
|
||||
run: |
|
||||
npm ci --prefix tests/integration
|
||||
npm ci --prefix frontend-modern
|
||||
npm ci --prefix internal/cloudcp/portal/frontend
|
||||
cd tests/integration
|
||||
npx playwright install --with-deps chromium
|
||||
|
||||
- name: Install Playwright browser and OS dependencies
|
||||
timeout-minutes: 10
|
||||
working-directory: tests/integration
|
||||
run: npx playwright install --with-deps chromium
|
||||
- name: Validate offline issuer boundaries
|
||||
working-directory: tests/integration
|
||||
run: >-
|
||||
@@ -156,7 +171,13 @@ jobs:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: tests/integration/package-lock.json
|
||||
|
||||
# Apply only to this disposable hosted runner, never the maintainer host.
|
||||
# Keep the provider's repositories and apt authentication unchanged.
|
||||
- name: Bound browser OS dependency acquisition
|
||||
run: sudo install -m 0644 scripts/playwright-apt.conf /etc/apt/apt.conf.d/99pulse-playwright-acquire
|
||||
|
||||
- name: Install Playwright dependencies
|
||||
timeout-minutes: 10
|
||||
working-directory: tests/integration
|
||||
run: |
|
||||
npm ci
|
||||
@@ -315,7 +336,13 @@ jobs:
|
||||
node-version: '24'
|
||||
package-manager-cache: false
|
||||
|
||||
# Apply only to this disposable hosted runner, never the maintainer host.
|
||||
# Keep the provider's repositories and apt authentication unchanged.
|
||||
- name: Bound browser OS dependency acquisition
|
||||
run: sudo install -m 0644 scripts/playwright-apt.conf /etc/apt/apt.conf.d/99pulse-playwright-acquire
|
||||
|
||||
- name: Install Playwright dependencies
|
||||
timeout-minutes: 10
|
||||
working-directory: tests/integration
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Bound apt transport inside disposable GitHub E2E runners. Do not replace
|
||||
// repositories, disable TLS/signatures or accept an incomplete index as success.
|
||||
Acquire::http::Timeout "30";
|
||||
Acquire::https::Timeout "30";
|
||||
Acquire::Retries "2";
|
||||
APT::Update::Error-Mode "any";
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep failed browser setup out of the E2E test budget, without stale apt success."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
WORKFLOW = ROOT / ".github/workflows/test-e2e.yml"
|
||||
POLICY = ROOT / "scripts/playwright-apt.conf"
|
||||
INSTALL_POLICY = (
|
||||
"sudo install -m 0644 scripts/playwright-apt.conf "
|
||||
"/etc/apt/apt.conf.d/99pulse-playwright-acquire"
|
||||
)
|
||||
|
||||
|
||||
def job_block(workflow, job):
|
||||
return re.split(r"(?m)^ \S", workflow.split(f"\n {job}:\n", 1)[1], maxsplit=1)[0]
|
||||
|
||||
|
||||
def step_blocks(job):
|
||||
return re.split(r"(?m)^ - name: ", job)[1:]
|
||||
|
||||
|
||||
class PlaywrightAcquisitionTest(unittest.TestCase):
|
||||
def assert_workflow_policy(self, workflow):
|
||||
for job in ("offline-org-provisioning", "e2e", "agent-registration"):
|
||||
with self.subTest(job=job):
|
||||
block = job_block(workflow, job)
|
||||
steps = step_blocks(block)
|
||||
policies = [i for i, step in enumerate(steps) if INSTALL_POLICY in step]
|
||||
installers = [
|
||||
(i, step) for i, step in enumerate(steps)
|
||||
if "npx playwright install --with-deps " in step
|
||||
]
|
||||
self.assertEqual(len(policies), 1)
|
||||
self.assertEqual(len(installers), 1)
|
||||
i, install = installers[0]
|
||||
self.assertLess(policies[0], i)
|
||||
for step in (steps[policies[0]], install):
|
||||
self.assertNotIn("continue-on-error", step)
|
||||
self.assertNotIn("if:", step)
|
||||
self.assertNotIn("||", step)
|
||||
self.assertIn("timeout-minutes: 10", install)
|
||||
self.assertIn("working-directory: tests/integration", install)
|
||||
browsers = "chromium webkit" if job == "e2e" else "chromium"
|
||||
self.assertIn(f"npx playwright install --with-deps {browsers}\n", install)
|
||||
# Browser setup must finish successfully before real journeys.
|
||||
self.assertLess(i, next(
|
||||
n for n, step in enumerate(steps)
|
||||
if step.startswith("Run stable-tier E2E suite")
|
||||
or step.startswith("Prove authenticated default")
|
||||
or step.startswith("Run agent registration lifecycle")
|
||||
))
|
||||
|
||||
def test_all_browser_jobs_bound_setup_without_weakening_gates(self):
|
||||
self.assert_workflow_policy(WORKFLOW.read_text())
|
||||
|
||||
def test_policy_and_tests_trigger_both_pr_and_main_validation(self):
|
||||
workflow = WORKFLOW.read_text()
|
||||
for event in ("pull_request", "push"):
|
||||
block = re.split(r"(?m)^ \S", workflow.split(f" {event}:\n", 1)[1], maxsplit=1)[0]
|
||||
self.assertIn("'scripts/playwright-apt.conf'", block)
|
||||
self.assertIn("'scripts/tests/test_playwright_acquisition.py'", block)
|
||||
selection = job_block(workflow, "tier-selection")
|
||||
self.assertLess(
|
||||
selection.index("python3 scripts/tests/test_playwright_acquisition.py"),
|
||||
selection.index("npm ci"),
|
||||
)
|
||||
|
||||
def test_unbounded_and_ignored_setup_mutants_are_rejected(self):
|
||||
workflow = WORKFLOW.read_text()
|
||||
# Each mutation targets the fault, not an unrelated YAML assertion.
|
||||
for original, replacement in (
|
||||
(" timeout-minutes: 10\n", ""),
|
||||
(INSTALL_POLICY, "echo no acquisition policy"),
|
||||
("run: npx playwright install --with-deps chromium\n",
|
||||
"run: npx playwright install --with-deps chromium || true\n"),
|
||||
):
|
||||
with self.subTest(mutation=replacement):
|
||||
# subTest failures cannot be caught as an ordinary assertion,
|
||||
# so collect the mutant's real unittest result independently.
|
||||
class Mutant(PlaywrightAcquisitionTest):
|
||||
def runTest(inner):
|
||||
inner.assert_workflow_policy(workflow.replace(original, replacement))
|
||||
|
||||
result = unittest.TestResult()
|
||||
Mutant().run(result)
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertTrue(result.failures)
|
||||
self.assertFalse(result.errors)
|
||||
|
||||
def test_real_apt_parser_loads_only_the_four_expected_controls(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
config = Path(temp) / "apt.conf"
|
||||
config.write_text(
|
||||
'Dir::Etc::parts "-";\nDir::Etc::main "-";\n' + POLICY.read_text()
|
||||
)
|
||||
parsed = subprocess.run(
|
||||
["apt-config", "dump"], env={**os.environ, "APT_CONFIG": str(config)},
|
||||
capture_output=True, text=True, timeout=10, check=True,
|
||||
).stdout
|
||||
expected = {
|
||||
"Acquire::http::Timeout": "30",
|
||||
"Acquire::https::Timeout": "30",
|
||||
"Acquire::Retries": "2",
|
||||
"APT::Update::Error-Mode": "any",
|
||||
}
|
||||
entries = dict(re.findall(r'^(\S+) "([^"\n]*)";', POLICY.read_text(), re.M))
|
||||
self.assertEqual(entries, expected)
|
||||
for key, value in expected.items():
|
||||
self.assertIn(f'{key} "{value}";', parsed)
|
||||
self.assertNotIn("AllowUnauthenticated", parsed)
|
||||
self.assertNotIn("Verify-Peer", parsed)
|
||||
|
||||
def test_incomplete_index_is_a_real_apt_failure_not_a_warning_pass(self):
|
||||
# An unlistening reserved loopback port gives a definite transport
|
||||
# failure. No external request, privileged apt operation, host list,
|
||||
# post-update hook or authentication bypass is involved.
|
||||
with socket.socket() as sock, tempfile.TemporaryDirectory() as temp:
|
||||
sock.bind(("127.0.0.1", 0))
|
||||
root = Path(temp)
|
||||
(root / "lists/partial").mkdir(parents=True)
|
||||
(root / "cache/archives/partial").mkdir(parents=True)
|
||||
sources = root / "sources.list"
|
||||
sources.write_text(f"deb http://127.0.0.1:{sock.getsockname()[1]} stable main\n")
|
||||
config = root / "apt.conf"
|
||||
prefix = (
|
||||
'Dir::Etc::parts "-";\nDir::Etc::main "-";\n'
|
||||
f'Dir::Etc::sourcelist "{sources}";\nDir::Etc::sourceparts "-";\n'
|
||||
f'Dir::State::lists "{root / "lists"}";\n'
|
||||
f'Dir::Cache "{root / "cache"}";\n'
|
||||
'Dir::State::status "/dev/null";\nDebug::NoLocking "true";\n'
|
||||
)
|
||||
for strict, expected_exit in ((True, 100), (False, 0)):
|
||||
policy = POLICY.read_text()
|
||||
if not strict:
|
||||
policy = policy.replace('APT::Update::Error-Mode "any";', "")
|
||||
config.write_text(prefix + policy)
|
||||
result = subprocess.run(
|
||||
["apt-get", "update"],
|
||||
env={**os.environ, "APT_CONFIG": str(config), "LC_ALL": "C"},
|
||||
capture_output=True, text=True, timeout=20,
|
||||
)
|
||||
with self.subTest(strict=strict):
|
||||
self.assertEqual(result.returncode, expected_exit, result.stderr)
|
||||
self.assertIn("Failed to fetch http://127.0.0.1:", result.stderr)
|
||||
self.assertIn("Connection refused", result.stdout + result.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user