Skip to content

Commit 8ebc916

Browse files
Make Ubuntu Node CI pass snapshot tests and finish in budget.
Why: The Alpha CI job on ubuntu-latest failed four broker-core snapshot tests because they called host xcrun simctl. The same run spent 17 minutes in verify:public-surface spawning one git cat-file per tracked file, so the 20-minute job budget could not cover the remaining suites. Changed: The four app-snapshot tests now pass the fixture simctl adapter through runtimeOptions. The default public-surface scan reads index blobs only for dirty or missing worktree files. CI and release jobs use a 30-minute budget. Front-door tests require timeout-minutes of at least 30. Verification: npm run agent:verify -- --profile spec-only --paths broker-core/test/broker-core.test.mjs,client/public-surface.mjs,client/test/public-surface.test.mjs,.github/workflows/ci.yml,.github/workflows/release.yml,docs/test/front-door.test.mjs,spec/build-and-test.md,CHANGELOG.md --session-dir task-sessions/20260818-alpha-ci-ubuntu-fix npm run agent:verify -- --profile implementation --paths broker-core/test/broker-core.test.mjs,client/public-surface.mjs,client/test/public-surface.test.mjs,.github/workflows/ci.yml,.github/workflows/release.yml,docs/test/front-door.test.mjs,spec/build-and-test.md,CHANGELOG.md --session-dir task-sessions/20260818-alpha-ci-ubuntu-fix Affected: broker-core/test/broker-core.test.mjs client/public-surface.mjs client/test/public-surface.test.mjs .github/workflows/ci.yml .github/workflows/release.yml docs/test/front-door.test.mjs spec/build-and-test.md CHANGELOG.md Refs: #6 https://github.com/fiveonecode/simulator-broker/actions/runs/32114159922 spec/build-and-test.md Session: task-sessions/20260818-alpha-ci-ubuntu-fix
1 parent b114df3 commit 8ebc916

8 files changed

Lines changed: 65 additions & 12 deletions

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
# Public Node test surface. This job is intentionally cheap: Ubuntu, no
2-
# npm install (the repo has no runtime dependencies), and no macOS app suite.
1+
# Public Node test surface. Ubuntu, no npm install (the repo has no runtime
2+
# dependencies), and no macOS app suite. timeout-minutes is 30 because a
3+
# prior 20-minute budget was consumed by per-file git cat-file in
4+
# verify:public-surface on a clean checkout.
35
name: Node tests
46

57
on:
@@ -18,7 +20,7 @@ jobs:
1820
node:
1921
name: broker-core client harness-adoption public-surface
2022
runs-on: ubuntu-latest
21-
timeout-minutes: 20
23+
timeout-minutes: 30
2224
steps:
2325
- uses: actions/checkout@v4
2426

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
release:
1616
name: attach CLI tarball
1717
runs-on: ubuntu-latest
18-
timeout-minutes: 20
18+
timeout-minutes: 30
1919
steps:
2020
- uses: actions/checkout@v4
2121

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ downloadable CLI tarball.
1616
- Public Node test workflow on GitHub-hosted Ubuntu for
1717
`verify:public-surface`, `test:broker-core`, `test:client`, and
1818
`test:harness-adoption`. That job does not run the macOS app suite.
19+
Snapshot tests inject the fixture `simctl` adapter so the suite does not
20+
call host `xcrun`. The default public-surface scan skips identical index
21+
blobs on a clean worktree, and the job budget is 30 minutes.
1922
- `scripts/package_cli.sh` (`npm run package:cli`) builds a versioned CLI
2023
tarball without XcodeGen or an app build.
2124
- Tag-driven GitHub Release workflow that attaches the CLI tarball and its

broker-core/test/broker-core.test.mjs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9350,7 +9350,7 @@ test("events and app snapshots honor a zero event limit", () => {
93509350
});
93519351

93529352
assert.equal(readEventsBroker(resolvedPaths, { limit: 0 }).events.length, 0);
9353-
assert.equal(appSnapshotBroker(resolvedPaths, { eventLimit: 0 }).recentEvents.length, 0);
9353+
assert.equal(appSnapshotBroker(resolvedPaths, runtimeOptions(paths, { eventLimit: 0 })).recentEvents.length, 0);
93549354
});
93559355

93569356
test("app snapshot reuses one process sample for active lease checks", () => {
@@ -9379,13 +9379,13 @@ test("app snapshot reuses one process sample for active lease checks", () => {
93799379
const liveSampler = liveProcessSampler({ command: "node broker-core.test.mjs", pid: process.pid });
93809380
let sampleCount = 0;
93819381

9382-
const snapshot = appSnapshotBroker(resolvedPaths, {
9382+
const snapshot = appSnapshotBroker(resolvedPaths, runtimeOptions(paths, {
93839383
processExists: (pid) => pid === process.pid,
93849384
processSampler: () => {
93859385
sampleCount += 1;
93869386
return liveSampler();
93879387
},
9388-
});
9388+
}));
93899389

93909390
assert.equal(snapshot.activeLeases.length, 2);
93919391
assert.equal(sampleCount, 1);
@@ -9422,7 +9422,7 @@ test("app snapshot reads only a bounded event tail for recent events", (t) => {
94229422
return originalReadFileSync.call(this, target, ...args);
94239423
};
94249424

9425-
const snapshot = appSnapshotBroker(resolvedPaths, { eventLimit: 3 });
9425+
const snapshot = appSnapshotBroker(resolvedPaths, runtimeOptions(paths, { eventLimit: 3 }));
94269426

94279427
assert.deepEqual(snapshot.recentEvents.map((event) => event.eventId), [
94289428
"event-119",
@@ -9661,7 +9661,7 @@ test("broker-owned state files are restricted to the current user", () => {
96619661
purposeId: "agent-ui-session",
96629662
simctlAdapter: paths.simctl.adapter,
96639663
});
9664-
writeAppSnapshotArtifact(resolvedPaths);
9664+
writeAppSnapshotArtifact(resolvedPaths, runtimeOptions(paths));
96659665

96669666
const statePaths = [
96679667
resolvedPaths.stateRoot,

client/public-surface.mjs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ function defaultCandidateFiles(root) {
7979
return output.split("\0").filter(Boolean);
8080
}
8181

82+
// Clean checkouts match the index, so skip per-file `git cat-file`. A prior
83+
// Ubuntu CI run spent 17 minutes spawning one process per tracked file.
84+
function dirtyWorktreeFiles(root) {
85+
try {
86+
const output = execGit([
87+
"diff-files",
88+
"-z",
89+
"--name-only",
90+
], {
91+
cwd: root,
92+
encoding: "utf8",
93+
});
94+
return new Set(output.split("\0").filter(Boolean));
95+
} catch {
96+
return null;
97+
}
98+
}
99+
82100
function defaultCandidateIndexModes(root) {
83101
const output = execGit([
84102
"ls-files",
@@ -318,6 +336,7 @@ export function scanPublicSurface({
318336
const candidateFiles = files ?? defaultCandidateFiles(resolvedRoot);
319337
const scanIndexBlobs = files === undefined;
320338
const indexModes = scanIndexBlobs ? defaultCandidateIndexModes(resolvedRoot) : new Map();
339+
const dirtyFiles = scanIndexBlobs ? dirtyWorktreeFiles(resolvedRoot) : new Set();
321340
const resolvedDenylistPath = denylistPath ?? path.join(resolvedRoot, LOCAL_DENYLIST_NAME);
322341
const denylistRules = localDenylistRules(resolvedDenylistPath);
323342
const builtInRules = [
@@ -421,6 +440,7 @@ export function scanPublicSurface({
421440
if (!absoluteFile.startsWith(`${resolvedRoot}${path.sep}`)) {
422441
continue;
423442
}
443+
let worktreeMissing = false;
424444
try {
425445
const fileStats = fs.lstatSync(absoluteFile);
426446
if (fileStats.isSymbolicLink()) {
@@ -438,8 +458,9 @@ export function scanPublicSurface({
438458
if (error?.code !== "ENOENT") {
439459
throw error;
440460
}
461+
worktreeMissing = true;
441462
}
442-
if (scanIndexBlobs) {
463+
if (scanIndexBlobs && (dirtyFiles === null || dirtyFiles.has(relativeFile) || worktreeMissing)) {
443464
scanText(normalizedRelativeFile, indexBlobContent(resolvedRoot, relativeFile));
444465
}
445466
}

client/test/public-surface.test.mjs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,29 @@ test("default public surface candidates ignore untracked scratch files", () => {
546546
}]);
547547
});
548548

549+
test("default public surface scan inspects staged blobs when the worktree file is missing", () => {
550+
const root = makeTempDir();
551+
const localHome = path.join(root, "private-home");
552+
execFileSync("git", ["init"], { cwd: root, stdio: "ignore" });
553+
fs.writeFileSync(path.join(root, "README.md"), `machine path: ${localHome}/state\n`);
554+
execFileSync("git", ["add", "README.md"], { cwd: root, stdio: "ignore" });
555+
fs.rmSync(path.join(root, "README.md"));
556+
557+
const report = scanPublicSurface({
558+
homePath: localHome,
559+
root,
560+
});
561+
562+
assert.equal(report.ok, false);
563+
assert.equal(report.filesScanned, 1);
564+
assert.deepEqual(report.issues, [{
565+
line: 1,
566+
path: "README.md",
567+
rule: "local-home-path",
568+
}]);
569+
assert.equal(JSON.stringify(report).includes(localHome), false);
570+
});
571+
549572
test("default public surface scan inspects staged blobs even after worktree cleanup", () => {
550573
const root = makeTempDir();
551574
const localHome = path.join(root, "private-home");

docs/test/front-door.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ test("public CI runs the Node suites on Ubuntu and skips the macOS app suite", (
153153
const ci = readRepoFile(".github/workflows/ci.yml");
154154

155155
assert.ok(ci.includes("runs-on: ubuntu-latest"));
156+
assert.match(ci, /timeout-minutes:\s*([3-9]\d|\d{3,})/);
156157
assert.ok(ci.includes("npm run verify:public-surface"));
157158
assert.ok(ci.includes("npm run test:broker-core"));
158159
assert.ok(ci.includes("npm run test:client"));

spec/build-and-test.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ A first extracted implementation slice now exists:
3939
tarball without XcodeGen or an app build
4040
- public GitHub-hosted Ubuntu CI runs `verify:public-surface`,
4141
`test:broker-core`, `test:client`, and `test:harness-adoption`; it does not
42-
run `test:app`
42+
run `test:app`. The job budget is 30 minutes. Broker tests that build an
43+
app snapshot must inject the fixture `simctl` adapter. The default
44+
public-surface scan reads index blobs only for dirty or missing worktree
45+
files so a clean checkout does not spawn one `git cat-file` per file.
4346
- tagged versions such as `v0.1.0-alpha.1` attach the CLI tarball to a GitHub
4447
Release through `.github/workflows/release.yml`
4548
- local-debug portable bundle support through a zip bundle plus package-smoke verification of the bundled install path and installed-app launch proof
@@ -288,7 +291,7 @@ Add stronger profiles next for:
288291
- the installer prints the installed CLI path, app path when an app was installed, env helper path, any current-shell PATH warning, PATH persist result, and the next command (`command -v simbroker` after persist, or `source "<env-helper>"` when persist is skipped)
289292
- `bash scripts/install_local.sh --cli-only` installs the CLI runtime without invoking `xcodegen` or `xcodebuild` and without requiring an app bundle
290293
- `npm run package:cli` writes `artifacts/cli/simulator-broker-<version>-cli.tar.gz` plus a SHA-256 checksum and does not invoke XcodeGen or `xcodebuild`
291-
- `.github/workflows/ci.yml` runs the public Node suites on `ubuntu-latest` and does not run `npm run test:app`
294+
- `.github/workflows/ci.yml` runs the public Node suites on `ubuntu-latest` with a 30-minute budget and does not run `npm run test:app`
292295
- `host init --bootstrap-config` writes a warning that real Simulator devices will be created before it calls `simctl` create
293296
- `npm run test:install-smoke` proves a fresh-machine-style install can bootstrap host config, scaffold a repo, start the service, acquire a lease, generate an app snapshot from the installed CLI, launch the installed app bundle against the smoke fixture, assert the `SimulatorBrokerApp` process stays alive, restore any preexisting default install metadata including symlink target contents, and clean up only the simulators provisioned by the smoke run afterward
294297
- `npm run package:distribution` builds the app in `Release`, requires operator-supplied `SIMBROKER_DISTRIBUTION_TEAM_ID` plus `SIMBROKER_DISTRIBUTION_SIGNING_IDENTITY`, optionally consumes `SIMBROKER_NOTARYTOOL_PROFILE`, and writes a machine-readable readiness summary under `artifacts/distribution/`

0 commit comments

Comments
 (0)