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
9 changes: 2 additions & 7 deletions .github/workflows/cmux-next.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ on:
- tests/test_cmux_next_checks_workflow.py
- tests/test_cmux_next_generated_catch_up.py
- .github/workflows/cmux-next-generated-catch-up.yml
- tests/test_cmux_next_regenerate_bundles_workflow.py
- .github/workflows/cmux-next-regenerate-bundles.yml
- tests/test_next_batch.py
- .github/workflows/cmux-next-batch.yml
- tests/test_cmux_next_tree_notify.py
Expand Down Expand Up @@ -205,8 +203,6 @@ on:
- tests/test_cmux_next_checks_workflow.py
- tests/test_cmux_next_generated_catch_up.py
- .github/workflows/cmux-next-generated-catch-up.yml
- tests/test_cmux_next_regenerate_bundles_workflow.py
- .github/workflows/cmux-next-regenerate-bundles.yml
- tests/test_next_batch.py
- .github/workflows/cmux-next-batch.yml
- tests/test_cmux_next_tree_notify.py
Expand Down Expand Up @@ -757,14 +753,13 @@ jobs:
python3 tests/test_cmux_next_route.py
python3 tests/test_cmux_next_route_replay.py

# The companion workflows' structure: generated-files catch-up, the bundle
# regeneration dispatch and the next-batch landing queue.
# The companion workflows' structure: generated-files catch-up and the
# next-batch landing queue.
- name: Companion workflows
id: companion-workflows
continue-on-error: true
run: |
python3 tests/test_cmux_next_generated_catch_up.py
python3 tests/test_cmux_next_regenerate_bundles_workflow.py
python3 tests/test_next_batch.py

# A pull request that changes the router replays the last 30 merged PRs' changed files
Expand Down
4 changes: 0 additions & 4 deletions tests/test-execution.toml
Original file line number Diff line number Diff line change
Expand Up @@ -996,10 +996,6 @@ lane = "linux-guard"
path = "tests/test_cmux_next_route.py"
lane = "linux-guard"

[[test]]
path = "tests/test_cmux_next_regenerate_bundles_workflow.py"
lane = "linux-guard"

[[test]]
path = "tests/test_gh_merge_green.py"
lane = "linux-guard"
Expand Down
39 changes: 26 additions & 13 deletions tests/test_build_web_bundles_from.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,52 @@
# source key (cmux-next.yml's Linux web-bundles job caches them by that key) and stamps them,
# so a Mac job that restores them skips the build. A tree missing a bundle is refused and the
# checkout is left alone.
#
# The bundles are build output, not committed (41b907dce7b), so the test runs a copy of the
# script in a scratch repository with a synthetic prebuilt tree. It never touches this
# checkout's generated bundles or its .web-bundles.key.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SCRIPT="$ROOT/scripts/cmux-next/build-web-bundles.sh"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
cd "$ROOT"

REPO="$WORK/repo"
mkdir -p "$REPO/scripts/cmux-next"
cp "$ROOT/scripts/cmux-next/build-web-bundles.sh" "$ROOT/scripts/cmux-next/web-bundle-key.py" "$REPO/scripts/cmux-next/"
SCRIPT="$REPO/scripts/cmux-next/build-web-bundles.sh"
git -C "$REPO" init -q

PANE="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/Resources/agent-pane"
PAGES="Packages/macOS/CmuxNext/Sources/CmuxNextPages/Resources/pages"
ACTIVITY="Packages/macOS/CmuxNext/Sources/CmuxNextAgentActivity/Resources/agent-activity"
PALETTE="Packages/macOS/CmuxNext/Sources/CmuxNextPalette/Resources/palette-ranker.js"
APP="Resources/markdown-viewer/webviews-app"
INSPECTOR="Native/OptChat/optchat-chief/inspector"

# The palette ranker lands in a Resources directory that holds tracked files in a checkout.
mkdir -p "$REPO/$(dirname "$PALETTE")"

# A prebuilt tree: the committed bundles stand in for the cached build of this source key.
for path in "$PANE" "$PAGES" "$ACTIVITY" "$APP"; do
mkdir -p "$WORK/out/$(dirname "$path")"
cp -R "$path" "$WORK/out/$path"
# A prebuilt tree for this source key, as the Linux web-bundles job caches it.
for path in "$PANE" "$PAGES" "$ACTIVITY" "$APP" "$INSPECTOR"; do
mkdir -p "$WORK/out/$path"
printf '<!doctype html><title>%s</title>\n' "$path" > "$WORK/out/$path/index.html"
done
mkdir -p "$WORK/out/$(dirname "$PALETTE")"
cp "$PALETTE" "$WORK/out/$PALETTE"
printf 'export const rank = () => 0;\n' > "$WORK/out/$PALETTE"

rm -f .web-bundles.key
"$SCRIPT" --from "$WORK/out" > "$WORK/log" 2>&1 || { cat "$WORK/log"; echo "FAIL: --from must install a complete prebuilt tree"; exit 1; }
"$SCRIPT" --verify > /dev/null 2>&1 || { echo "FAIL: after --from the bundles must be current without a build"; exit 1; }
git diff --quiet -- "$PANE" "$PAGES" "$ACTIVITY" "$PALETTE" "$APP" \
|| { echo "FAIL: --from changed the committed bundles"; exit 1; }
for path in "$PANE" "$PAGES" "$ACTIVITY" "$APP" "$INSPECTOR"; do
diff -r "$WORK/out/$path" "$REPO/$path" > /dev/null || { echo "FAIL: --from did not install $path as built"; exit 1; }
done
cmp -s "$WORK/out/$PALETTE" "$REPO/$PALETTE" || { echo "FAIL: --from did not install $PALETTE as built"; exit 1; }

rm -rf "$WORK/out/$PAGES"
rm -f .web-bundles.key
rm -f "$REPO/.web-bundles.key"
if "$SCRIPT" --from "$WORK/out" > "$WORK/log" 2>&1; then
echo "FAIL: --from must refuse a tree that is missing a bundle"; exit 1
fi
[ -d "$PAGES" ] || { echo "FAIL: a refused --from must leave the checkout's bundles alone"; exit 1; }
[ ! -f .web-bundles.key ] || { echo "FAIL: a refused --from must not stamp the bundles current"; exit 1; }
[ -f "$REPO/$PAGES/index.html" ] || { echo "FAIL: a refused --from must leave the checkout's bundles alone"; exit 1; }
[ ! -f "$REPO/.web-bundles.key" ] || { echo "FAIL: a refused --from must not stamp the bundles current"; exit 1; }

echo "PASS: build-web-bundles.sh --from installs and stamps a prebuilt tree, and refuses an incomplete one"
14 changes: 14 additions & 0 deletions tests/test_cmux_next_checks_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,20 @@ def test_rust_ratchet_is_its_own_step(self):
self.assertEqual(sum("--only rust" in run for run in godfile_runs), 1, godfile_runs)


class ReferencedFilesExist(unittest.TestCase):
"""Every test a checks step runs, and every test or workflow a path filter names, exists.
The bundle removal (ce0b9e76a9b) deleted tests/test_cmux_next_regenerate_bundles_workflow.py
and its workflow, but the Companion workflows step still ran the test, so every pull
request's checks went red (run 37646119996)."""

def test_named_tests_and_workflows_exist(self):
text = WORKFLOW.read_text(encoding="utf-8")
named = set(re.findall(r"(?:python3|bash) (tests/[A-Za-z0-9_./-]+\.(?:py|sh))", text))
named |= set(re.findall(r"^\s+- ((?:tests|\.github/workflows)/[A-Za-z0-9_./-]+\.(?:py|sh|yml))$", text, re.M))
missing = sorted(path for path in named if not (ROOT / path).exists())
self.assertEqual(missing, [])


class GodfileScopes(unittest.TestCase):
"""`--only swift` and `--only rust` each check their half, at unchanged budgets."""

Expand Down
8 changes: 7 additions & 1 deletion tests/test_cmux_next_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,14 @@ def test_cmux_tui_runs_the_daemon_tier_and_the_scheme_compile(self):
self.assertEqual(result["swift_targets"], "")

def test_a_file_a_test_reads_selects_that_test(self):
# CmuxNextSettingsTests reads the schema. Since the bundles are build output
# (ce0b9e76a9b) the schema is also a web-bundle input, so the targets that ship
# a bundle, and the app that embeds them, are selected with it, and nothing else.
result = tiers(["schemas/settings/settings-schema.json"])
self.assertEqual(result["swift_targets"], "CmuxNextSettingsTests")
self.assertEqual(result["swift_targets"].split(), sorted([
"CmuxNextSettingsTests", "CmuxNextAppTests",
"CmuxNextAgentPaneTests", "CmuxNextPagesTests", "CmuxNextAgentActivityTests", "CmuxNextPaletteTests",
]))

def test_a_local_package_selects_the_targets_that_use_it(self):
result = tiers(["Packages/Shared/CmuxHomeCore/Sources/CmuxHomeCore/Thread.swift"])
Expand Down
2 changes: 1 addition & 1 deletion webviews/src/agent-session/acpmux/ModelPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ export function ModelPicker(props: ModelPickerProps) {
<span>{entry.name}</span>
{folderNote(entry) && <span className="acpmux-menu-description">{folderNote(entry)}</span>}
{entry.ids.includes(harness ?? "") && <CheckIcon />}
</PickerOption>
</PickerOption>,
])}
</div>
</PickerOptionList>
Expand Down
8 changes: 6 additions & 2 deletions webviews/src/agent-session/acpmux/composerPickers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,16 @@ describe("acpmux composer pickers", () => {
// The highlight opens on the current mode, the last one.
await key(mode, "ArrowDown");
expect(
[...doc.querySelectorAll<HTMLElement>("[role=menuitemradio]")].find((row) => row.textContent?.includes("Full access")),
[...doc.querySelectorAll<HTMLElement>("[role=menuitemradio]")].find((row) =>
row.textContent?.includes("Full access"),
),
).toBeTruthy();
// A live update drops that option while it is highlighted.
await render(snapshot({ modes: { ...full, availableModes: [full.availableModes[0]!] } }));
expect(
[...doc.querySelectorAll<HTMLElement>("[role=menuitemradio]")].find((row) => row.textContent?.includes("Ask for approval")),
[...doc.querySelectorAll<HTMLElement>("[role=menuitemradio]")].find((row) =>
row.textContent?.includes("Ask for approval"),
),
).toBeTruthy();
await key(mode, " ");
expect(mode.getAttribute("aria-expanded")).toBe("true");
Expand Down
11 changes: 10 additions & 1 deletion webviews/test/gallery-coverage.allowlist.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"agent-session/acpmux/ComposerPickers.tsx#ChevronIcon",
"agent-session/acpmux/ComposerPickers.tsx#ChevronRightIcon",
"agent-session/acpmux/ComposerPickers.tsx#ContextRing",
"agent-session/acpmux/ComposerPickers.tsx#LockIcon",
"agent-session/acpmux/ComposerPickers.tsx#MicIcon",
"agent-session/acpmux/ComposerPickers.tsx#PaperclipIcon",
"agent-session/acpmux/ComposerPickers.tsx#Picker",
Expand All @@ -33,12 +34,12 @@
"agent-session/acpmux/Keycap.tsx#Keycap",
"agent-session/acpmux/MenuLevel.tsx#MenuLevel",
"agent-session/acpmux/MenuRow.tsx#MenuRow",
"agent-session/acpmux/ModelPicker.tsx#ModelPicker",
"agent-session/acpmux/ModelPickerCascade.tsx#ModelPickerCascade",
"agent-session/acpmux/ModelPickerDrill.tsx#ModelPickerDrill",
"agent-session/acpmux/ModelPickerShell.tsx#ModelPickerShell",
"agent-session/acpmux/NewTabPage.tsx#KindIcon",
"agent-session/acpmux/NewTabPage.tsx#NewTabPage",
"agent-session/acpmux/ProjectBadge.tsx#ProjectBadge",
"agent-session/acpmux/QuickKeyHints.tsx#QuickKeyHints",
"agent-session/acpmux/QuickSurface.tsx#QuickSurface",
"agent-session/acpmux/SessionSidebar.tsx#SessionSidebar",
Expand Down Expand Up @@ -114,6 +115,7 @@
"agent-session/acpmux/icons/Icon.tsx#Icon",
"agent-session/acpmux/icons/IconsContext.tsx#IconsProvider",
"agent-session/acpmux/permissions/Panel.tsx#PermissionPanel",
"agent-session/acpmux/question/QuestionCard.tsx#QuestionCard",
"agent-session/acpmux/shell/MoveRow.tsx#MoveRow",
"agent-session/acpmux/shell/ShellRow.tsx#ShellActionsContext",
"agent-session/acpmux/shell/ShellRow.tsx#ShellRow",
Expand All @@ -134,6 +136,7 @@
"pages/markdown/linkOverlays.tsx#LinkCardView",
"pages/markdown/linkOverlays.tsx#LinkOverlayHost",
"pages/markdown/linkOverlays.tsx#LinkPopoverView",
"pages/settings/editors/ChatRootsEditor.tsx#ChatRootsEditor",
"pages/settings/editors/OrderedChoicesEditor.tsx#OrderedChoicesEditor",
"pages/shared/PageMenu.tsx#PageMenu",
"ui/Breadcrumbs.tsx#Breadcrumbs",
Expand All @@ -154,6 +157,12 @@
"ui/Menu.tsx#MenuRadioItem",
"ui/Menu.tsx#MenuSeparator",
"ui/Menu.tsx#Submenu",
"ui/MinimapTick.tsx#MinimapTick",
"ui/PickerPrimitives.tsx#PickerButton",
"ui/PickerPrimitives.tsx#PickerComboboxInput",
"ui/PickerPrimitives.tsx#PickerDialog",
"ui/PickerPrimitives.tsx#PickerOption",
"ui/PickerPrimitives.tsx#PickerOptionList",
"ui/Popover.tsx#Popover",
"ui/Select.tsx#Select",
"ui/Sheet.tsx#Sheet",
Expand Down
17 changes: 12 additions & 5 deletions webviews/test/palette-ranker.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { describe, expect, test } from "bun:test";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { rankPalette, rankPaletteEmpty, type PaletteFrecency, type PaletteRankEntry } from "../src/palette/ranker";

const now = 800_000_000;
Expand Down Expand Up @@ -153,12 +156,16 @@ describe("shared palette ranker", () => {
expect(result[0]?.rows[1]?.highlights).toEqual([]);
});

test("the checked-in JavaScriptCore bridge exposes the same ranker", async () => {
const bundle = await Bun.file(
"../Packages/macOS/CmuxNext/Sources/CmuxNextPalette/Resources/palette-ranker.js",
).text();
test("the built JavaScriptCore bridge exposes the same ranker", async () => {
// The bridge is build output (scripts/cmux-next/build-web-bundles.sh), not a committed file:
// build it into a scratch directory, as the app build does into the package resources.
const out = mkdtempSync(join(tmpdir(), "palette-ranker-"));
const built = Bun.spawnSync(["sh", "../scripts/cmux-next/build-palette-ranker.sh", "--out", out]);
expect(built.exitCode).toBe(0);
const bundlePath = join(out, "palette-ranker.js");
const bundle = await Bun.file(bundlePath).text();
expect(bundle.length).toBeGreaterThan(0);
await import("../../Packages/macOS/CmuxNext/Sources/CmuxNextPalette/Resources/palette-ranker.js");
await import(bundlePath);
const bridge = (globalThis as typeof globalThis & { __cmuxPaletteRank?: (request: string) => string })
.__cmuxPaletteRank;
expect(typeof bridge).toBe("function");
Expand Down
Loading