diff --git a/.github/workflows/agent-notification-tests.yml b/.github/workflows/agent-notification-tests.yml deleted file mode 100644 index 6adbed73f989..000000000000 --- a/.github/workflows/agent-notification-tests.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Agent notification semantics -on: - pull_request: - paths: - - Packages/macOS/CmuxAgentJournal/** - - CLI/*Notification*.swift - - CLI/CMUXCLI+AgentJournalEmission.swift - - CLI/CMUXCLI+VMTui.swift - - CLI/VMRemoteWorkspaceResolver.swift - - CLI/cmux.swift - - Sources/AgentNotification*.swift - - Sources/TerminalNotification*.swift - - Sources/AgentFeedSemanticInput.swift - - Sources/Feed/** - # The files that define a suite in unit_test_suites below. ci.yml's shards - # run all of cmuxTests, so a broader pattern only adds a second run. - - cmuxTests/AgentDeliveryTTYBindingTests.swift - - cmuxTests/AgentJournalLifecycleCenterTests.swift - - cmuxTests/AgentNotificationLiveRetargetTests.swift - - cmuxTests/AgentNotificationMoveRaceTests.swift - - cmuxTests/AgentNotificationMutationBoundaryTests.swift - - cmuxTests/AgentRelayTTYOwnershipTests.swift - - cmuxTests/AgentSemanticNotificationDeliveryTests.swift - - cmuxTests/ClaudeBackgroundWorkNotifyTests.swift - - cmuxTests/FeedWaiterRegistryTests.swift - - cmuxTests/MemoryPressureNotificationTests.swift - - cmuxTests/NativeNotificationFallbackCommandTests.swift - - cmuxTests/OpenCodeHookRegressionTests.swift - # Shared helpers those suite files use. - - cmuxTests/AgentHookTestNotificationPipeline.swift - - cmuxTests/AgentJournalTestSupport.swift - - cmuxTests/BundledCLILinkageTests.swift - - cmuxTests/ClaudeHookLiveDeliveryTargetTestSupport.swift - - cmuxTests/ClaudeHookSurfaceResolutionSwiftTests.swift - - cmuxTests/ClaudeNotificationStatusLifecycleTests.swift - - cmuxTests/TerminalControllingTTYWaiter.swift - - Packages/macOS/CMUXAgentLaunch/** - - .github/workflows/test-depot.yml - - scripts/ci/classify-app-host-test-output.py - - Sources/AgentJournalLifecycleCenter*.swift - - .github/workflows/agent-notification-tests.yml -# A new push cancels the previous run for the same pull request. Other events -# key on the run id, so they never cancel each other. -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -permissions: - contents: read -jobs: - agent-notification-tests: - runs-on: ${{ vars.MACOS_RUNNER_15 || 'blacksmith-6vcpu-macos-15' }} - timeout-minutes: 15 - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - - name: Test semantic lifecycle and durable notification admission - run: swift test --package-path Packages/macOS/CmuxAgentJournal -Xswiftc -warnings-as-errors - - name: Test immutable Feed ingestion - run: swift test --package-path Packages/macOS/CMUXAgentLaunch --filter Workstream -Xswiftc -warnings-as-errors - - semantic-delivery-integration: - needs: agent-notification-tests - uses: ./.github/workflows/test-depot.yml - with: - ref: ${{ github.event.pull_request.head.sha }} - skip_ui_tests: true - # CLICodexResumeNotificationTests is red on main independently of this - # change: its seeded Stop predates the Codex turn ledger's foreground - # admission, the mock never answers agent.resolve_delivery_target, and its - # fixture timestamp has aged past the seven-day session prune. - unit_test_suites: AgentNotificationRegressionTests,AgentJournalLifecycleCenterTests,FeedWaiterRegistryTests,ClaudeBackgroundWorkNotifyTests,OpenCodeHookRegressionTests diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bc22c016665..131a7f0a742f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -405,6 +405,9 @@ jobs: - name: Validate app-host xcodebuild attempt budget run: ./tests/test_ci_app_host_xcodebuild_attempts.sh + - name: Validate notification semantics gates + run: python3 tests/test_ci_notification_semantics.py + - name: Validate app-host test failure classification run: python3 tests/test_ci_app_host_test_output.py @@ -1191,7 +1194,6 @@ jobs: -xctestrun "$CMUX_APP_HOST_XCTESTRUN" \ -destination "platform=macOS" \ CMUX_SKIP_ZIG_BUILD=1 \ - -only-testing:cmuxTests/AgentNotificationRegressionTests \ -only-testing:cmuxTests/DockNotificationAttentionTests \ -only-testing:cmuxTests/ClaudeHookLifecycleCleanupTests \ -only-testing:cmuxTests/ClaudeHookLiveDeliveryTargetTests \ @@ -1846,6 +1848,35 @@ jobs: with: bun-version: "1.3.6" + - name: Run agent notification semantics + if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) }} + run: | + set -euo pipefail + # Reuse compile admission's product instead of rebuilding the app in + # agent-notification-tests.yml. Keep each suite in a fresh host and + # require positive execution, outside the tolerant broad batches. + command -v node + bun_path="$(command -v bun)" + bun_install="$(dirname "$(dirname "$bun_path")")" + for suite in \ + AgentNotificationRegressionTests \ + AgentJournalLifecycleCenterTests \ + FeedWaiterRegistryTests \ + ClaudeBackgroundWorkNotifyTests \ + OpenCodeHookRegressionTests + do + test_log="$RUNNER_TEMP/cmux-notification-${suite}.txt" + scripts/ci/run-in-console-session.sh \ + env "TEST_RUNNER_PATH=$PATH" "TEST_RUNNER_BUN_INSTALL=$bun_install" \ + scripts/ci/run-app-host-xcodebuild.sh \ + -xctestrun "$CMUX_APP_HOST_XCTESTRUN" \ + -destination "platform=macOS" \ + CMUX_SKIP_ZIG_BUILD=1 \ + -only-testing:"cmuxTests/$suite" \ + test-without-building 2>&1 | tee "$test_log" + scripts/ci/require_selected_test_execution.sh "$test_log" "cmuxTests/$suite" + done + - name: Install pinned nushell (shell-integration tests) if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) }} run: | @@ -2357,6 +2388,14 @@ jobs: exit 1 fi echo "::group::swift test $pkgdir" + swift_test_args=(--package-path "$pkgdir") + # Preserve the notification workflow's warning gate without a + # second package build or changing the existing startup retry. + case "$pkg" in + CMUXAgentLaunch|CmuxAgentJournal) + swift_test_args+=(-Xswiftc -warnings-as-errors) + ;; + esac case "$pkg" in # CmuxFoundation has several process-tree suites whose child # fixtures share global process resources; run each suite in its @@ -2366,7 +2405,7 @@ jobs: ;; CmuxTerminal|CmuxTerminalCore) test_status=0 - output="$(swift test --package-path "$pkgdir" 2>&1)" || test_status=$? + output="$(swift test "${swift_test_args[@]}" 2>&1)" || test_status=$? printf '%s\n' "$output" if [ "$test_status" -ne 0 ]; then if printf '%s\n' "$output" | grep -Eq 'Test run with [0-9]+ tests( in [0-9]+ suites)? passed' \ @@ -2385,7 +2424,7 @@ jobs: # test failure: retry exactly once, and only when no test # output was emitted. test_status=0 - output="$(swift test --package-path "$pkgdir" 2>&1)" || test_status=$? + output="$(swift test "${swift_test_args[@]}" 2>&1)" || test_status=$? printf '%s\n' "$output" if [ "$test_status" -ne 0 ] \ && printf '%s\n' "$output" | grep -Fq 'Build complete!' \ @@ -2393,7 +2432,7 @@ jobs: && ! printf '%s\n' "$output" | grep -Eq '^(Test Suite|Test Case|◇ |↳ |✔ |✘ )'; then echo "Test runner crashed at startup (runner flake); retrying $pkg once." test_status=0 - output="$(swift test --package-path "$pkgdir" 2>&1)" || test_status=$? + output="$(swift test "${swift_test_args[@]}" 2>&1)" || test_status=$? printf '%s\n' "$output" fi if [ "$test_status" -ne 0 ]; then diff --git a/scripts/ci/cmux_unit_test_shard.py b/scripts/ci/cmux_unit_test_shard.py index 80cc6386f44d..afce2185c302 100755 --- a/scripts/ci/cmux_unit_test_shard.py +++ b/scripts/ci/cmux_unit_test_shard.py @@ -35,6 +35,11 @@ DEFAULT_TIMINGS_PATH = Path(__file__).resolve().parent / "cmux-unit-test-timings.json" FALLBACK_TEST_MS = 200 FOCUSED_GATE_SELECTORS = { + "cmuxTests/AgentNotificationRegressionTests", + "cmuxTests/AgentJournalLifecycleCenterTests", + "cmuxTests/FeedWaiterRegistryTests", + "cmuxTests/ClaudeBackgroundWorkNotifyTests", + "cmuxTests/OpenCodeHookRegressionTests", "cmuxTests/AgentRestoreLiveOwnerAdmissionTests", "cmuxTests/BrowserSystemProxyMirrorTests", "cmuxTests/CLISSHSessionAttachAnchorTests", diff --git a/scripts/ci/run-app-host-xcodebuild.sh b/scripts/ci/run-app-host-xcodebuild.sh index 42593254b835..909bf3399e7f 100755 --- a/scripts/ci/run-app-host-xcodebuild.sh +++ b/scripts/ci/run-app-host-xcodebuild.sh @@ -57,6 +57,15 @@ fi if [ -n "${GITHUB_ACTIONS:-}" ]; then app_host_test_runner_environment+=("TEST_RUNNER_GITHUB_ACTIONS=$GITHUB_ACTIONS") fi +# Focused app-host suites invoke Node/Bun-backed helpers from the test process. +# Xcode does not inherit these driver variables, so carry them through the +# TEST_RUNNER_ channel when the caller supplied them. +if [ -n "${TEST_RUNNER_PATH:-}" ]; then + app_host_test_runner_environment+=("TEST_RUNNER_PATH=$TEST_RUNNER_PATH") +fi +if [ -n "${TEST_RUNNER_BUN_INSTALL:-}" ]; then + app_host_test_runner_environment+=("TEST_RUNNER_BUN_INSTALL=$TEST_RUNNER_BUN_INSTALL") +fi app_host_home="" app_host_key="" app_host_receipt_dir="" diff --git a/tests/test_ci_app_host_xcodebuild_retry.sh b/tests/test_ci_app_host_xcodebuild_retry.sh index 9fb810c24087..9cc6dc4e0909 100755 --- a/tests/test_ci_app_host_xcodebuild_retry.sh +++ b/tests/test_ci_app_host_xcodebuild_retry.sh @@ -28,6 +28,10 @@ if [ "${CMUX_MOCK_XCODEBUILD_PROCESS:-0}" = "1" ]; then "${TEST_RUNNER_CMUX_APP_HOST_KEY:-}" \ "${TEST_RUNNER_CMUX_APP_HOST_RECEIPT_DIR:-}" \ >> "$CMUX_CAPTURE_TEST_RUNNER_HOME_ENV" + if [ -n "${CMUX_CAPTURE_TEST_RUNNER_TOOL_ENV:-}" ]; then + printf '%s|%s\n' "${TEST_RUNNER_PATH-}" "${TEST_RUNNER_BUN_INSTALL-}" \ + >> "$CMUX_CAPTURE_TEST_RUNNER_TOOL_ENV" + fi config_home="${TEST_RUNNER_HOME:-${HOME:-/tmp}}" config_category=default config_message="reading configuration file" @@ -250,12 +254,15 @@ CMUX_CAPTURE_XCODEBUILD_ARGS="$TMP_DIR/xcodebuild-args.log" \ CMUX_CAPTURE_TEST_RUNNER_ENV="$TMP_DIR/test-runner-env.log" \ CMUX_CAPTURE_XCODEBUILD_PARENT_ENV="$TMP_DIR/xcodebuild-parent-env.log" \ CMUX_CAPTURE_TEST_RUNNER_HOME_ENV="$TMP_DIR/test-runner-home-env.log" \ +CMUX_CAPTURE_TEST_RUNNER_TOOL_ENV="$TMP_DIR/test-runner-tool-env.log" \ CMUX_MOCK_XCODEBUILD_PROCESS=1 \ CMUX_APP_HOST_XCODEBUILD_ATTEMPTS=2 \ CMUX_XCODEBUILD_NONINTERACTIVE_IDLE_TIMEOUT_SECONDS=0.1 \ CMUX_CI_APP_HOST_ISOLATION_REQUIRED=1 \ CMUX_APP_HOST_HOME="$APP_HOST_HOME" \ CMUX_APP_HOST_XDG_CONFIG_HOME="$APP_HOST_XDG_CONFIG_HOME" \ +TEST_RUNNER_PATH="/ci/node/bin:/usr/bin" \ +TEST_RUNNER_BUN_INSTALL="/ci/bun" \ CFFIXED_USER_HOME="$XCODE_PARENT_FIXED_HOME" \ XDG_CONFIG_HOME="$XCODE_PARENT_XDG_CONFIG_HOME" \ bash "$ROOT_DIR/scripts/ci/run-app-host-xcodebuild.sh" test >"$TMP_DIR/output.log" 2>&1 @@ -289,6 +296,12 @@ if [ "$runner_marker_count" -eq 0 ] || [ "$runner_marker_count" -ne "$invocation exit 1 fi +if [ "$(grep -Fxc '/ci/node/bin:/usr/bin|/ci/bun' "$TMP_DIR/test-runner-tool-env.log" || true)" -ne "$invocation_count" ]; then + cat "$TMP_DIR/test-runner-tool-env.log" + echo "FAIL: focused test-runner tool paths must reach every app-host launch" + exit 1 +fi + isolated_parent_count="$(awk -F '|' -v isolated="$APP_HOST_HOME" ' $1 == isolated { count += 1 } END { print count + 0 } diff --git a/tests/test_ci_notification_semantics.py b/tests/test_ci_notification_semantics.py new file mode 100644 index 000000000000..35677192301e --- /dev/null +++ b/tests/test_ci_notification_semantics.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Execute the notification gates with controlled test-runner outcomes.""" + +import json +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + +import yaml + + +ROOT = Path(__file__).resolve().parents[1] +SUITES = [ + "AgentNotificationRegressionTests", + "AgentJournalLifecycleCenterTests", + "FeedWaiterRegistryTests", + "ClaudeBackgroundWorkNotifyTests", + "OpenCodeHookRegressionTests", +] + + +def step_script(job, name): + workflow = yaml.safe_load((ROOT / ".github/workflows/ci.yml").read_text()) + return next(s["run"] for s in workflow["jobs"][job]["steps"] if s.get("name") == name) + + +class NotificationSemanticsTests(unittest.TestCase): + def run_gate(self, outcome="pass", fail_suite=SUITES[1]): + script = step_script("app-host-unit-tests", "Run agent notification semantics") + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + helpers = root / "scripts/ci" + helpers.mkdir(parents=True) + console = helpers / "run-in-console-session.sh" + console.write_text('#!/bin/bash\nexec "$@"\n') + console.chmod(0o755) + runner = helpers / "run-app-host-xcodebuild.sh" + runner.write_text("""#!/usr/bin/env python3 +import json, os, sys +args = sys.argv[1:] +with open(os.environ['CALLS'], 'a') as f: + f.write(json.dumps({'args': args, 'path': os.getenv('TEST_RUNNER_PATH'), + 'bun': os.getenv('TEST_RUNNER_BUN_INSTALL')}) + '\\n') +suite = next(a.split('/', 1)[1] for a in args if a.startswith('-only-testing:')) +outcome = os.environ['OUTCOME'] if suite == os.environ['FAIL_SUITE'] else 'pass' +if outcome == 'crash': + print('The test runner timed out while preparing to run tests.') + sys.exit(65) +if outcome == 'zero': + print('Executed 0 tests, with 0 failures (0 unexpected) in 0.0 seconds') +elif outcome == 'failure': + print('Executed 3 tests, with 1 failure (0 unexpected) in 0.1 seconds') + sys.exit(65) +elif suite == 'OpenCodeHookRegressionTests': + print('Executed 3 tests, with 0 failures (0 unexpected) in 0.1 seconds') +else: + print('Test run with 4 tests in 1 suite passed after 0.1 seconds.') +""") + runner.chmod(0o755) + shutil.copy2(ROOT / "scripts/ci/require_selected_test_execution.sh", helpers) + bindir = root / "bin" + bindir.mkdir() + for command in ("node", "bun"): + executable = bindir / command + executable.write_text("#!/bin/bash\nexit 0\n") + executable.chmod(0o755) + calls = root / "calls.jsonl" + env = dict(os.environ, PATH=f"{bindir}:{os.environ['PATH']}", + RUNNER_TEMP=str(root), CALLS=str(calls), OUTCOME=outcome, + FAIL_SUITE=fail_suite, CMUX_APP_HOST_XCTESTRUN="/products/cmux-unit.xctestrun") + result = subprocess.run(["/bin/bash", "-c", script], cwd=root, env=env, + text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + invocations = [json.loads(line) for line in calls.read_text().splitlines()] if calls.exists() else [] + return result, invocations + + def test_every_suite_runs_once_from_the_compiled_product(self): + result, calls = self.run_gate() + self.assertEqual(result.returncode, 0, result.stdout) + self.assertEqual(len(calls), len(SUITES)) + for suite, call in zip(SUITES, calls): + args = call["args"] + self.assertIn(f"-only-testing:cmuxTests/{suite}", args) + self.assertEqual(args[args.index("-xctestrun") + 1], "/products/cmux-unit.xctestrun") + self.assertIn("test-without-building", args) + self.assertNotIn("test", args) + self.assertNotIn("build-for-testing", args) + self.assertTrue(call["path"]) + self.assertTrue(call["bun"]) + + def test_assertion_failure_cannot_pass(self): + result, _ = self.run_gate("failure") + self.assertEqual(result.returncode, 65, result.stdout) + + def test_host_failure_cannot_pass(self): + result, _ = self.run_gate("crash") + self.assertEqual(result.returncode, 65, result.stdout) + + def test_empty_filter_cannot_pass(self): + for suite in SUITES: + with self.subTest(suite=suite): + result, _ = self.run_gate("zero", suite) + self.assertNotEqual(result.returncode, 0, result.stdout) + + def run_packages(self, warning_package=""): + script = step_script("swift-package-tests", "Run Swift package unit tests") + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + for package in (ROOT / "Packages").glob("*/*"): + if package.is_dir(): + fake = root / package.relative_to(ROOT) + fake.mkdir(parents=True) + # The step's package selector only lists directories that hold a manifest. + (fake / "Package.swift").write_text("") + helpers = root / "scripts/ci" + helpers.mkdir(parents=True) + shutil.copy(ROOT / "scripts/ci/select_package_tests.py", helpers) + runner_temp = root / "runner-temp" + runner_temp.mkdir() + isolated = helpers / "run-swift-testing-suites.sh" + isolated.write_text('#!/bin/bash\nexec swift test --package-path "$1"\n') + isolated.chmod(0o755) + bindir = root / "bin" + bindir.mkdir() + cargo = bindir / "cargo" + cargo.write_text("#!/bin/bash\nexit 0\n") + cargo.chmod(0o755) + swift = bindir / "swift" + swift.write_text("""#!/usr/bin/env python3 +import json, os, sys +from pathlib import Path +args = sys.argv[1:] +with open(os.environ['CALLS'], 'a') as f: + f.write(json.dumps(args) + '\\n') +package = Path(args[args.index('--package-path') + 1]).name +if package == os.environ['WARNING_PACKAGE'] and '-warnings-as-errors' in args: + print('error: compiler warning promoted to an error') + sys.exit(1) +print('Test run with 4 tests in 1 suite passed after 0.1 seconds.') +""") + swift.chmod(0o755) + calls = root / "calls.jsonl" + env = dict(os.environ, PATH=f"{bindir}:{os.environ['PATH']}", + CALLS=str(calls), WARNING_PACKAGE=warning_package, + RUNNER_TEMP=str(runner_temp)) + # An unknown diff selects every package, so both warning gates run. + env.pop("CHANGED_FILES", None) + result = subprocess.run(["/bin/bash", "-c", script], cwd=root, env=env, + text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + return result, [json.loads(line) for line in calls.read_text().splitlines()] + + def test_package_warning_gates_run_once(self): + result, calls = self.run_packages() + self.assertEqual(result.returncode, 0, result.stdout) + for package in ("CMUXAgentLaunch", "CmuxAgentJournal"): + matching = [args for args in calls if args[args.index('--package-path') + 1].endswith('/' + package)] + self.assertEqual(len(matching), 1) + args = matching[0] + self.assertEqual(args[args.index('-Xswiftc') + 1], '-warnings-as-errors') + + def test_package_warning_is_still_fatal(self): + for package in ("CMUXAgentLaunch", "CmuxAgentJournal"): + with self.subTest(package=package): + result, _ = self.run_packages(package) + self.assertNotEqual(result.returncode, 0, result.stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_ci_self_hosted_guard.sh b/tests/test_ci_self_hosted_guard.sh index 9f1e85fe9a07..296a59f3cdc4 100755 --- a/tests/test_ci_self_hosted_guard.sh +++ b/tests/test_ci_self_hosted_guard.sh @@ -1245,61 +1245,6 @@ check_signing_intermediate_imports check_signing_intermediate_helper_behavior check_sentry_cli_install_portability check_sentry_cli_helper_behavior -check_agent_notification_paths_cover_its_suites() { - # The workflow reruns suites that ci.yml's shards already run, so it should - # start only for changes that can affect them: every file that defines one of - # its suites, and every helper file those name, must match a path trigger, - # and no cmuxTests trigger may match any other file. - ROOT_DIR="$ROOT_DIR" python3 - <<'PY' -import fnmatch, os, re, sys -from pathlib import Path - -root = Path(os.environ["ROOT_DIR"]) -text = (root / ".github/workflows/agent-notification-tests.yml").read_text(encoding="utf-8") -paths = re.findall(r"^\s+- (cmuxTests/\S+)\s*$", text, flags=re.M) -suites = re.search(r"^\s*unit_test_suites:\s*(\S+)", text, flags=re.M).group(1).split(",") -errors = [] -if "cmuxTests/**" in paths: - errors.append("must not trigger on all of cmuxTests/**") -sources = {f: f.read_text(encoding="utf-8", errors="ignore") for f in sorted((root / "cmuxTests").glob("*.swift"))} -suite_files = set() -for suite in suites: - decl = re.compile(rf"^\s*(?:@\w+(?:\([^)]*\))?\s+)*(?:\w+\s+)*(?:class|struct|actor|extension)\s+{re.escape(suite)}\b", re.M) - files = [f for f, source in sources.items() if decl.search(source)] - if not files: - errors.append(f"runs {suite}, which no file in cmuxTests defines") - suite_files.update(files) - for f in files: - rel = f"cmuxTests/{f.name}" - if not any(fnmatch.fnmatchcase(rel, p) for p in paths): - errors.append(f"runs {suite} but {rel} matches no path trigger") -# A helper is a file whose top-level type the suite files name. Nested and -# private types are skipped: another file cannot reach them, and several test -# files declare a private type of the same name. -suite_text = "\n".join(sources[f] for f in suite_files) -helper_files = set() -top_level = re.compile(r"^(?:@\w+(?:\([^)]*\))?\s+)*(?:(?:final|internal|public|open)\s+)*(?:class|struct|enum|actor|protocol)\s+(\w+)", re.M) -for f, source in sources.items(): - if f in suite_files: - continue - used = sorted(n for n in set(top_level.findall(source)) if re.search(rf"\b{re.escape(n)}\b", suite_text)) - rel = f"cmuxTests/{f.name}" - if used: - helper_files.add(f) - if used and not any(fnmatch.fnmatchcase(rel, p) for p in paths): - errors.append(f"suites use {', '.join(used)} from {rel}, which matches no path trigger") -# A trigger that also matches unrelated test files starts a second run of -# suites that ci.yml already ran. -for p in paths: - extra = sorted(f.name for f in sources if fnmatch.fnmatchcase(f"cmuxTests/{f.name}", p) and f not in suite_files | helper_files) - if extra: - errors.append(f"trigger {p} also matches unrelated files: {', '.join(extra[:5])}") -for e in errors: - print(f"FAIL: agent-notification-tests.yml {e}") -sys.exit(1 if errors else 0) -PY - echo "PASS: agent notification paths cover every suite file and helper the workflow runs" -} pr_workflow_events() { # Prints the pull request events a workflow triggers on, for the mapping, @@ -1441,6 +1386,5 @@ check_no_ci_swift_package_skips check_web_db_behavior_tests check_web_test_runner_behavior check_tmux_terminal_nightly_isolation -check_agent_notification_paths_cover_its_suites check_pr_macos_workflows_cancel_superseded_runs check_no_paid_overflow_fallbacks