Skip to content
Open
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
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"./examples/lint/oxlint",
"./examples/monitoring",
"./examples/rfc-9421-test",
"./examples/test-examples",
"./test/smoke/harness"
],
"imports": {
Expand Down
6 changes: 6 additions & 0 deletions examples/test-examples/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"tasks": {
"test": "deno test --allow-all .",
"start": "deno run --allow-all mod.ts"
}
}
46 changes: 46 additions & 0 deletions examples/test-examples/mod.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Registry-consistency tests for the example test runner.
*
* Every example directory under examples/ must be registered in exactly one
* of the registries in mod.ts (SERVER_EXAMPLES, SCRIPT_EXAMPLES,
* MULTI_HANDLE_EXAMPLES, or SKIPPED_EXAMPLES). These tests fail when a new
* example is added without being registered, so it cannot silently miss
* automated checks.
*
* Usage (from repository root):
* deno test --allow-all examples/test-examples
*/
import { fromFileUrl } from "@std/path";
import assert from "node:assert/strict";
import test from "node:test";

import { getRegisteredExampleNames, scanUnregisteredExamples } from "./mod.ts";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Update the import to use getRegisteredExampleDirs instead of getRegisteredExampleNames to support checking directory names rather than logical example names.

Suggested change
import { getRegisteredExampleNames, scanUnregisteredExamples } from "./mod.ts";
import { getRegisteredExampleDirs, scanUnregisteredExamples } from "./mod.ts";


const EXAMPLES_DIR = fromFileUrl(new URL("../", import.meta.url));

test("Every example in the examples/ directory is registered in test-examples", async () => {
const unregistered = await scanUnregisteredExamples();
assert.deepEqual(
unregistered,
[],
`Unregistered example directories found: ${unregistered.join(", ")}`,
);
});

test("Every registered example has a matching examples/ directory", async () => {
const directories = new Set<string>();
for await (const entry of Deno.readDir(EXAMPLES_DIR)) {
if (entry.isDirectory) directories.add(entry.name);
}
const unmatchedExamples = [...getRegisteredExampleNames()]
.filter((name) => !directories.has(name))
.sort();

assert.deepEqual(
unmatchedExamples,
[],
`Registered examples without a matching directory found: ${
unmatchedExamples.join(", ")
}`,
);
});
Comment thread
2chanhaeng marked this conversation as resolved.
65 changes: 46 additions & 19 deletions examples/test-examples/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ interface MultiHandleExample {
name: string;
dir: string;
/** Command prefix—the handle is appended as the final argument. */
cmdPrefix?: string[];
cmd: string[];
/** Handles to try, in order. Pass if any succeeds. */
handles: string[];
description: string;
Expand Down Expand Up @@ -294,6 +294,19 @@ const SERVER_EXAMPLES: ServerExample[] = [
readyUrl: "http://localhost:3000/",
readyTimeout: 30_000,
},
{
// SolidStart sample using @fedify/solidstart; actor path is
// /users/{identifier}. Built with vinxi build; served with vinxi start
// on port 3000.
name: "solidstart",
dir: "solidstart",
buildCmd: ["pnpm", "build"],
startCmd: ["pnpm", "start"],
port: 3000,
actor: "demo",
readyUrl: "http://localhost:3000/",
readyTimeout: 30_000,
},
];

const SCRIPT_EXAMPLES: ScriptExample[] = [
Expand Down Expand Up @@ -350,6 +363,32 @@ const SKIPPED_EXAMPLES: SkippedExample[] = [
},
];

export function getRegisteredExampleNames(): Set<string> {
return new Set([
...SERVER_EXAMPLES,
...SCRIPT_EXAMPLES,
...MULTI_HANDLE_EXAMPLES,
...SKIPPED_EXAMPLES,
].map((e) => e.name));
}

/**
* Scans the examples/ directory and returns the names of sub-directories
* that are not registered in any of the registries above.
*/
export async function scanUnregisteredExamples(): Promise<string[]> {
const registeredNames = getRegisteredExampleNames();
const unregistered: string[] = [];
for await (const entry of Deno.readDir(EXAMPLES_DIR)) {
if (!entry.isDirectory) continue;
if (entry.name === "test-examples") continue;
if (!registeredNames.has(entry.name)) {
unregistered.push(entry.name);
}
}
return unregistered;
}
Comment thread
2chanhaeng marked this conversation as resolved.

// ─── ANSI Colors ──────────────────────────────────────────────────────────────

const c = {
Expand Down Expand Up @@ -805,24 +844,8 @@ async function main(): Promise<void> {
}

// ── Unregistered examples ────────────────────────────────────────────────
// Collect every example name that appears in any registry.
const registeredNames = new Set([
...SERVER_EXAMPLES.map((e) => e.name),
...SCRIPT_EXAMPLES.map((e) => e.name),
...MULTI_HANDLE_EXAMPLES.map((e) => e.name),
...SKIPPED_EXAMPLES.map((e) => e.name),
]);

// Scan the examples/ directory for sub-directories that are not registered.
const unregistered: string[] = [];
for await (const entry of Deno.readDir(EXAMPLES_DIR)) {
if (!entry.isDirectory) continue;
// The test-examples directory is the test runner itself—skip it.
if (entry.name === "test-examples") continue;
if (!registeredNames.has(entry.name)) {
unregistered.push(entry.name);
}
}
const unregistered = await scanUnregisteredExamples();

// ── Summary ──────────────────────────────────────────────────────────────
const passed = results.filter((r) => r.status === "pass");
Expand Down Expand Up @@ -871,4 +894,8 @@ async function main(): Promise<void> {
Deno.exit(0);
}

await main();
// Run the test runner only when this module is executed directly, so that
// mod.test.ts can import the registry helpers above without side effects.
if (import.meta.main) {
await main();
}