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
2 changes: 1 addition & 1 deletion scripts/calibrate-capacity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ function isClassroomComplete(classroom: ClassroomMeasurement | null, expectedUse
}

function buildQueueRecommendation(classroom: ClassroomMeasurement | null, complete: boolean, config: CalibrationCliConfig): Recommendation<number> {
if (complete && classroom && classroom.queueWaitMs.length === config.expectedUsers) {
if (complete && classroom?.queueWaitMs.length === config.expectedUsers) {
return recommendQueueTimeout(classroom.queueP95Ms, classroom.queueP99Ms, classroom.queueMaxMs, config.maxUserWaitSec);
}
return {
Expand Down
4 changes: 2 additions & 2 deletions scripts/capacity-calibration-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export function selectActiveRecommendation(
const stable = measurements.filter((measurement) => activeIsStable(measurement, options));
const target = stable.filter((measurement) => (measurement.cpuP95Percent ?? Infinity) <= options.targetCpuPercent);
if (target.length > 0) {
const selected = target.reduce((best, current) => current.requested > best.requested ? current : best);
const selected = target.reduce((best, current) => current.requested > best.requested ? current : best, target[0]);
return {
value: selected.requested,
status: "recommended",
Expand All @@ -181,7 +181,7 @@ export function selectActiveRecommendation(
};
}
if (stable.length > 0) {
const selected = stable.reduce((best, current) => current.requested > best.requested ? current : best);
const selected = stable.reduce((best, current) => current.requested > best.requested ? current : best, stable[0]);
return {
value: selected.requested,
status: "recommended",
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-bundle-budget.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const largest = Math.max(...sizes.map((file) => file.bytes), 0);
// 700 kB limit reflected Vite 5 chunk placement. Recalibrated together with the initial-path check below.
const limits = { total: 6_000_000, largest: 4_500_000, initial: 710_000 };
const initial = sizes
.filter((file) => /^index-/.test(file.name))
.filter((file) => file.name.startsWith("index-"))
.reduce((sum, file) => sum + file.bytes, 0);

// Initial load path: static import closure of the HTML entry, from the Vite build manifest.
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-node-version.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFile } from "node:fs/promises";
const expected = (await readFile(".nvmrc", "utf8")).trim();
const declared = JSON.parse(await readFile("package.json", "utf8")).engines?.node;
if (!declared || !declared.includes(expected)) {
if (!declared?.includes(expected)) {
console.error(`Node mismatch: expected ${expected}, engines=${declared}, runtime=${process.version}`);
process.exit(1);
}
Expand Down
14 changes: 12 additions & 2 deletions scripts/deployment/deployment-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ export async function httpsRequest(
});
}

function describeError(error: unknown): string {
return error instanceof Error ? String(error) : JSON.stringify(error);
}

export async function waitFor(
description: string,
probe: () => Promise<boolean>,
Expand All @@ -154,7 +158,7 @@ export async function waitFor(
}
await sleep(500);
}
const suffix = lastError ? `: ${String(lastError)}` : "";
const suffix = lastError ? `: ${describeError(lastError)}` : "";
throw new Error(`Timed out waiting for ${description}${suffix}`);
}

Expand Down Expand Up @@ -236,6 +240,12 @@ export function openWebSocket(
});
}

/** `ws` delivers text frames as Buffer, ArrayBuffer or fragment list depending on its options. */
function rawDataToString(raw: WebSocket.RawData): string {
if (Array.isArray(raw)) return Buffer.concat(raw).toString("utf8");
return Buffer.from(raw).toString("utf8");
}

export function nextWebSocketMessage(
socket: WebSocket,
predicate: (message: Record<string, any>) => boolean,
Expand All @@ -248,7 +258,7 @@ export function nextWebSocketMessage(
}, timeoutMs);
const onMessage = (raw: WebSocket.RawData) => {
try {
const message = JSON.parse(raw.toString()) as Record<string, any>;
const message = JSON.parse(rawDataToString(raw)) as Record<string, any>;
if (!predicate(message)) return;
clearTimeout(timer);
socket.removeListener("message", onMessage);
Expand Down
4 changes: 2 additions & 2 deletions scripts/tutor-quality-real-provider-eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,10 @@ export function isTutorQualityRelevantUntrackedPath(relativePath: string, output
export function readTutorQualityGitState(cwd: string, outputDir: string): TutorQualityGitState {
const sha = execFileSync(resolveGitExecutable(), ["rev-parse", "HEAD"], { cwd, encoding: "utf8" }).trim();
const lines = statusLines(cwd);
const trackedClean = lines.every((line) => line.slice(0, 2) === "??");
const trackedClean = lines.every((line) => line.startsWith("??"));
const outputRelative = path.relative(cwd, path.resolve(cwd, outputDir));
const relevantUntrackedClean = lines.every((line) => {
if (line.slice(0, 2) !== "??") return true;
if (!line.startsWith("??")) return true;
return !isTutorQualityRelevantUntrackedPath(line.slice(3).trim(), outputRelative);
});
return { sha, trackedClean, relevantUntrackedClean };
Expand Down
2 changes: 1 addition & 1 deletion scripts/validate-tutor-course-content.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from "node:path";
import { validateTutorCourseContentDirectory } from "../server/services/course-content/tutor-quality-directory-validator.ts";

const directory = process.argv[2];
if (directory === undefined || directory.length === 0) {
if (!directory) {
console.error("Usage: npm run validate:tutor-course-content -- <course-content-directory>");
process.exitCode = 2;
} else {
Expand Down
2 changes: 1 addition & 1 deletion server/services/tutor/curriculum/sketch-facts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const TYPE_ALIASES: Readonly<Record<string, SketchType>> = {
};

const TYPE_PATTERN = /\b(?:(?:unsigned|signed)\s+)?(?:char|byte|uint8_t|int|long|float)\b/g;
const ARRAY_PATTERN = /\b(char|byte|uint8_t|int|long|float)\s+([A-Za-z_]\w*)\s*\[\s*([^\]]*)\s*\]/g;
const ARRAY_PATTERN = /\b(char|byte|uint8_t|int|long|float)\s+([A-Za-z_]\w*)\s*\[([^\]]*)\]/g;
const INITIALIZER_PATTERN = /^\s*=\s*\{([^}]*)\}/;
const SERIAL_PATTERN = /\bSerial\s*\.\s*(print|println|write)\s*\(/gi;

Expand Down
2 changes: 1 addition & 1 deletion tests/client/examples-menu.behavior.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe("ExamplesMenu behavior", () => {
expect(screen.getByText("Load Example")).toHaveClass("ui-type-menu-title");
fireEvent.click(screen.getByRole("button", { name: "Built-in" }));
expect(
screen.queryByRole("button", { name: "Built-in", exact: true }),
screen.getByRole("button", { name: "Built-in", exact: true }),
).toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Other" }),
Expand Down
2 changes: 1 addition & 1 deletion tests/client/experimental-workspace-layout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,6 @@ describe("experimental workspace layout", () => {
{ credential: "volatile-key" },
{ credential: "volatile-key" },
]);
expect(localStorage.length).toBe(0);
expect(localStorage).toHaveLength(0);
});
});
2 changes: 1 addition & 1 deletion tests/client/tutor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe("useTutor", () => {
difficulty: 30,
});
expect(result.current.question?.question).toBe("Welche Ausgabe erwartest du?");
expect(localStorage.length).toBe(0);
expect(localStorage).toHaveLength(0);
});

it("resets a pinned dialog when the active Course Content revision changes", async () => {
Expand Down
10 changes: 5 additions & 5 deletions tests/integration/concurrent-50-clients.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,10 +421,10 @@ void loop() { digitalWrite(13, HIGH); Serial.println("LED ON"); delay(500); digi
const errored = results.filter((r) => r.errors.length > 0);

// All must compile
expect(compiled.length).toBe(N);
expect(compiled).toHaveLength(N);

// All must connect via WS
expect(connected.length).toBe(N);
expect(connected).toHaveLength(N);

// All must receive serial output (the key user-visible requirement)
if (gotOutput.length < N) {
Expand All @@ -439,10 +439,10 @@ void loop() { digitalWrite(13, HIGH); Serial.println("LED ON"); delay(500); digi
}

// All must see simulation end
expect(gotStopped.length).toBe(N);
expect(gotStopped).toHaveLength(N);

// No errors
expect(errored.length).toBe(0);
expect(errored).toHaveLength(0);
}, 30_000);

// ─── Per-client isolation: different code per client ───────────────────
Expand All @@ -464,7 +464,7 @@ void loop() { digitalWrite(13, HIGH); Serial.println("LED ON"); delay(500); digi
const gotOutput = results.filter(
(r) => r.compiled && r.receivedSerialOutput,
);
expect(gotOutput.length).toBe(N);
expect(gotOutput).toHaveLength(N);

// Each client MUST see its own marker in the serial output.
// This fails if the server uses a global shared lastCompiledCode
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/runtime-output-storm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ maybeDescribe("runtime output storm", () => {
expect(floodLines).toBeLessThan(1_000);
expect(errors.filter((line) => line.includes("Runtime output rate limit"))).toHaveLength(1);
// Without aggregation every line produced its own diagnostic log entry.
expect(logCalls.filter((message) => message.includes("STDERR")).length).toBe(0);
expect(logCalls.filter((message) => message.includes("STDERR"))).toHaveLength(0);
expect(logCalls.length).toBeLessThan(200);
}, 90_000);
});
2 changes: 1 addition & 1 deletion tests/server/services/compiler-diagnostics-limit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe("compiler diagnostics limits", () => {
const chunk = "x".repeat(100_000);
for (let index = 0; index < 10; index += 1) appendCompilerDiagnostics(buffer, chunk);

expect(buffer.value.length).toBe(MAX_COMPILER_DIAGNOSTIC_CHARS);
expect(buffer.value).toHaveLength(MAX_COMPILER_DIAGNOSTIC_CHARS);
expect(buffer.omittedChars).toBe(1_000_000 - MAX_COMPILER_DIAGNOSTIC_CHARS);
expect(limitCompilerDiagnostics(buffer.value, buffer.omittedChars)).toMatch(/\.\.\. 737856 more characters/);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ describe("Tutor Quality real-provider CLI contract", () => {
expect(transcripts.find(({ scenario }) => scenario.id === "TQ-REG-001")?.stateAfter)
.toEqual(transcripts.find(({ scenario }) => scenario.id === "TQ-REG-001")?.stateBefore);
const strategyTranscripts = transcripts.filter(({ scenario }) => scenario.id.startsWith("strategy-"));
expect(strategyTranscripts.length).toBe(scenarios.filter(({ id }) => id.startsWith("strategy-")).length);
expect(strategyTranscripts).toHaveLength(scenarios.filter(({ id }) => id.startsWith("strategy-")).length);
expect(strategyTranscripts.every(({ turns }) => turns.every(({ deterministicChecks }) => deterministicChecks.some(
({ name, passed }) => name === "expected-learning-phase" && passed,
)))).toBe(true);
Expand Down
2 changes: 1 addition & 1 deletion tests/shared/source-project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe("source project validation and include resolution", () => {
].join("\n"),
}));

expect(result.lineOrigins.length).toBe(result.source.split("\n").length);
expect(result.lineOrigins).toHaveLength(result.source.split("\n").length);
expect(result.lineOrigins).toEqual([
{ file: "a.h", line: 1 },
{ file: "sub/b.h", line: 1 },
Expand Down
Loading