forked from rosa/hakurei
Compare commits
13 Commits
3cd2e8c555
...
0eee3450ff
| Author | SHA1 | Date | |
|---|---|---|---|
|
0eee3450ff
|
|||
|
fd85d6cb4f
|
|||
|
5ca5289faf
|
|||
|
18b8d15edf
|
|||
|
d884d6e7e4
|
|||
|
316a2d43fa
|
|||
|
ee847c8b86
|
|||
|
cdcc3af55a
|
|||
|
2a0507dddc
|
|||
|
2c9d87706d
|
|||
|
31a9e9d014
|
|||
|
8cf2421f30
|
|||
|
6bc11098b1
|
@@ -29,6 +29,8 @@ func serveStaticContent(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFileFS(w, r, content, "ui/static/test.js")
|
||||
case "/static/test.css":
|
||||
http.ServeFileFS(w, r, content, "ui/static/test.css")
|
||||
case "/static/all_tests.js":
|
||||
http.ServeFileFS(w, r, content, "ui/static/all_tests.js")
|
||||
case "/static/test_tests.js":
|
||||
http.ServeFileFS(w, r, content, "ui/static/test_tests.js")
|
||||
default:
|
||||
|
||||
1
cmd/pkgserver/ui/static/all_tests.ts
Normal file
1
cmd/pkgserver/ui/static/all_tests.ts
Normal file
@@ -0,0 +1 @@
|
||||
import "./test_tests.js";
|
||||
@@ -2,6 +2,6 @@
|
||||
// Many editors have terminal emulators built in, so running tests with NodeJS
|
||||
// provides faster iteration, especially for those acclimated to test-driven
|
||||
// development.
|
||||
import "./test_tests.js";
|
||||
import "./all_tests.js";
|
||||
import { run, StreamReporter } from "./test.js";
|
||||
run(new StreamReporter({ writeln: console.log }));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// =============================================================================
|
||||
// DSL
|
||||
|
||||
type TestTree = { name: string } & (TestGroup | Test);
|
||||
type TestGroup = { children: TestTree[] };
|
||||
type Test = { test: (TestController) => void };
|
||||
type TestTree = TestGroup | Test;
|
||||
type TestGroup = { name: string, children: TestTree[] };
|
||||
type Test = { name: string, test: (TestController) => void };
|
||||
|
||||
let TESTS: ({ name: string } & TestGroup)[] = [];
|
||||
|
||||
@@ -34,17 +34,26 @@ function checkDuplicates(parent: string, names: { name: string }[]) {
|
||||
|
||||
class FailNowSentinel {}
|
||||
|
||||
class TestController {
|
||||
#logBuf: string[];
|
||||
export type Journal = ({ method: "fail" } | { method: "log", message: string })[];
|
||||
function formatJournal(journal: Journal): string {
|
||||
return journal
|
||||
.filter((e) => e.method === "log")
|
||||
.map((e) => e.message)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
export class TestController {
|
||||
journal: Journal;
|
||||
#failed: boolean;
|
||||
|
||||
constructor() {
|
||||
this.#logBuf = [];
|
||||
this.journal = [];
|
||||
this.#failed = false;
|
||||
}
|
||||
|
||||
fail() {
|
||||
this.#failed = true;
|
||||
this.journal.push({ method: "fail" });
|
||||
}
|
||||
|
||||
failed(): boolean {
|
||||
@@ -57,7 +66,7 @@ class TestController {
|
||||
}
|
||||
|
||||
log(message: string) {
|
||||
this.#logBuf.push(message);
|
||||
this.journal.push({ method: "log", message });
|
||||
}
|
||||
|
||||
error(message: string) {
|
||||
@@ -69,10 +78,6 @@ class TestController {
|
||||
this.log(message);
|
||||
this.failNow();
|
||||
}
|
||||
|
||||
getLog(): string {
|
||||
return this.#logBuf.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -80,35 +85,34 @@ class TestController {
|
||||
|
||||
export interface TestResult {
|
||||
success: boolean;
|
||||
output: string;
|
||||
journal: Journal;
|
||||
}
|
||||
|
||||
function runTests(reporter: Reporter, parents: string[], tree: TestTree) {
|
||||
const path = [...parents, tree.name];
|
||||
if ("children" in tree) {
|
||||
for (const c of tree.children) runTests(reporter, path, c);
|
||||
export function run(reporter: Reporter) {
|
||||
reporter.register(TESTS);
|
||||
for (const suite of TESTS) {
|
||||
for (const c of suite.children) runTests(reporter, [suite.name], c);
|
||||
}
|
||||
reporter.finalize();
|
||||
}
|
||||
|
||||
function runTests(reporter: Reporter, parents: string[], node: TestTree) {
|
||||
const path = [...parents, node.name];
|
||||
if ("children" in node) {
|
||||
for (const c of node.children) runTests(reporter, path, c);
|
||||
return;
|
||||
}
|
||||
let controller = new TestController();
|
||||
let excStr: string;
|
||||
try {
|
||||
tree.test(controller);
|
||||
node.test(controller);
|
||||
} catch (e) {
|
||||
if (!(e instanceof FailNowSentinel)) {
|
||||
controller.fail();
|
||||
excStr = extractExceptionString(e);
|
||||
}
|
||||
}
|
||||
const log = controller.getLog();
|
||||
const output = (log && excStr) ? `${log}\n${excStr}` : `${log}${excStr ?? ''}`;
|
||||
reporter.update(path, { success: !controller.failed(), output });
|
||||
}
|
||||
|
||||
export function run(reporter: Reporter) {
|
||||
for (const suite of TESTS) {
|
||||
for (const c of suite.children) runTests(reporter, [suite.name], c);
|
||||
}
|
||||
reporter.finalize();
|
||||
if (excStr !== undefined) controller.error(excStr);
|
||||
reporter.update(path, { success: !controller.failed(), journal: controller.journal });
|
||||
}
|
||||
|
||||
function extractExceptionString(e: any): string {
|
||||
@@ -126,6 +130,7 @@ function extractExceptionString(e: any): string {
|
||||
// Reporting
|
||||
|
||||
export interface Reporter {
|
||||
register(suites: TestGroup[]): void
|
||||
update(path: string[], result: TestResult): void;
|
||||
finalize(): void;
|
||||
}
|
||||
@@ -149,6 +154,8 @@ export class StreamReporter implements Reporter {
|
||||
this.counts = { successes: 0, failures: 0 };
|
||||
}
|
||||
|
||||
register(suites: TestGroup[]) {}
|
||||
|
||||
update(path: string[], result: TestResult) {
|
||||
if (path.length === 0) throw new RangeError("path is empty");
|
||||
const pathStr = path.join(SEP);
|
||||
@@ -192,10 +199,11 @@ export class StreamReporter implements Reporter {
|
||||
|
||||
#writeOutput(test: { name: string } & TestResult, prefix: string, nested: boolean) {
|
||||
let output = "";
|
||||
if (test.output) {
|
||||
const lines = test.output.split("\n");
|
||||
let logOutput = formatJournal(test.journal);
|
||||
if (logOutput) {
|
||||
const lines = logOutput.split("\n");
|
||||
if (lines.length <= 1) {
|
||||
output = `: ${test.output}`;
|
||||
output = `: ${logOutput}`;
|
||||
} else {
|
||||
const padding = nested ? " " : " ";
|
||||
output = ":\n" + lines.map((line) => padding + line).join("\n");
|
||||
@@ -206,6 +214,8 @@ export class StreamReporter implements Reporter {
|
||||
}
|
||||
|
||||
export class DOMReporter implements Reporter {
|
||||
register(suites: TestGroup[]) {}
|
||||
|
||||
update(path: string[], result: TestResult) {
|
||||
if (path.length === 0) throw new RangeError("path is empty");
|
||||
const counter = document.getElementById(result.success ? "successes" : "failures");
|
||||
@@ -237,9 +247,10 @@ export class DOMReporter implements Reporter {
|
||||
}
|
||||
const p = document.createElement("p");
|
||||
p.classList.add("test-desc");
|
||||
if (result.output) {
|
||||
const logOutput = formatJournal(result.journal);
|
||||
if (logOutput) {
|
||||
const pre = document.createElement("pre");
|
||||
pre.appendChild(document.createTextNode(result.output));
|
||||
pre.appendChild(document.createTextNode(logOutput));
|
||||
p.appendChild(pre);
|
||||
} else {
|
||||
p.classList.add("italic");
|
||||
@@ -250,3 +261,32 @@ export class DOMReporter implements Reporter {
|
||||
|
||||
finalize() {}
|
||||
}
|
||||
|
||||
interface GoNode {
|
||||
name: string;
|
||||
subtests?: GoNode[];
|
||||
}
|
||||
|
||||
// Used to display results via `go test`, via some glue code from the Go side.
|
||||
export class GoTestReporter implements Reporter {
|
||||
// Convert a test tree into the one expected by the Go code.
|
||||
static serialize(node: TestTree): GoNode {
|
||||
if (!("children" in node)) return { name: node.name };
|
||||
return {
|
||||
name: node.name,
|
||||
subtests: node.children.map(GoTestReporter.serialize),
|
||||
};
|
||||
}
|
||||
|
||||
register(suites: TestGroup[]) {
|
||||
console.log(JSON.stringify(suites.map(GoTestReporter.serialize)));
|
||||
}
|
||||
|
||||
update(path: string[], result: TestResult) {
|
||||
console.log(JSON.stringify({ "path": path, ...result }));
|
||||
}
|
||||
|
||||
finalize() {
|
||||
console.log("null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div id="root">
|
||||
</div>
|
||||
|
||||
<script type="module" src="./static/test_tests.js"></script>
|
||||
<script type="module" src="./static/all_tests.js"></script>
|
||||
<script type="module">
|
||||
import { DOMReporter, run } from "./static/test.js";
|
||||
run(new DOMReporter());
|
||||
|
||||
Reference in New Issue
Block a user