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")
|
http.ServeFileFS(w, r, content, "ui/static/test.js")
|
||||||
case "/static/test.css":
|
case "/static/test.css":
|
||||||
http.ServeFileFS(w, r, content, "ui/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":
|
case "/static/test_tests.js":
|
||||||
http.ServeFileFS(w, r, content, "ui/static/test_tests.js")
|
http.ServeFileFS(w, r, content, "ui/static/test_tests.js")
|
||||||
default:
|
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
|
// Many editors have terminal emulators built in, so running tests with NodeJS
|
||||||
// provides faster iteration, especially for those acclimated to test-driven
|
// provides faster iteration, especially for those acclimated to test-driven
|
||||||
// development.
|
// development.
|
||||||
import "./test_tests.js";
|
import "./all_tests.js";
|
||||||
import { run, StreamReporter } from "./test.js";
|
import { run, StreamReporter } from "./test.js";
|
||||||
run(new StreamReporter({ writeln: console.log }));
|
run(new StreamReporter({ writeln: console.log }));
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
// DSL
|
// DSL
|
||||||
|
|
||||||
type TestTree = { name: string } & (TestGroup | Test);
|
type TestTree = TestGroup | Test;
|
||||||
type TestGroup = { children: TestTree[] };
|
type TestGroup = { name: string, children: TestTree[] };
|
||||||
type Test = { test: (TestController) => void };
|
type Test = { name: string, test: (TestController) => void };
|
||||||
|
|
||||||
let TESTS: ({ name: string } & TestGroup)[] = [];
|
let TESTS: ({ name: string } & TestGroup)[] = [];
|
||||||
|
|
||||||
@@ -34,17 +34,26 @@ function checkDuplicates(parent: string, names: { name: string }[]) {
|
|||||||
|
|
||||||
class FailNowSentinel {}
|
class FailNowSentinel {}
|
||||||
|
|
||||||
class TestController {
|
export type Journal = ({ method: "fail" } | { method: "log", message: string })[];
|
||||||
#logBuf: 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;
|
#failed: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.#logBuf = [];
|
this.journal = [];
|
||||||
this.#failed = false;
|
this.#failed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
this.#failed = true;
|
this.#failed = true;
|
||||||
|
this.journal.push({ method: "fail" });
|
||||||
}
|
}
|
||||||
|
|
||||||
failed(): boolean {
|
failed(): boolean {
|
||||||
@@ -57,7 +66,7 @@ class TestController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log(message: string) {
|
log(message: string) {
|
||||||
this.#logBuf.push(message);
|
this.journal.push({ method: "log", message });
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message: string) {
|
error(message: string) {
|
||||||
@@ -69,10 +78,6 @@ class TestController {
|
|||||||
this.log(message);
|
this.log(message);
|
||||||
this.failNow();
|
this.failNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
getLog(): string {
|
|
||||||
return this.#logBuf.join("\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -80,35 +85,34 @@ class TestController {
|
|||||||
|
|
||||||
export interface TestResult {
|
export interface TestResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
output: string;
|
journal: Journal;
|
||||||
}
|
}
|
||||||
|
|
||||||
function runTests(reporter: Reporter, parents: string[], tree: TestTree) {
|
export function run(reporter: Reporter) {
|
||||||
const path = [...parents, tree.name];
|
reporter.register(TESTS);
|
||||||
if ("children" in tree) {
|
for (const suite of TESTS) {
|
||||||
for (const c of tree.children) runTests(reporter, path, c);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
let controller = new TestController();
|
let controller = new TestController();
|
||||||
let excStr: string;
|
let excStr: string;
|
||||||
try {
|
try {
|
||||||
tree.test(controller);
|
node.test(controller);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!(e instanceof FailNowSentinel)) {
|
if (!(e instanceof FailNowSentinel)) {
|
||||||
controller.fail();
|
|
||||||
excStr = extractExceptionString(e);
|
excStr = extractExceptionString(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const log = controller.getLog();
|
if (excStr !== undefined) controller.error(excStr);
|
||||||
const output = (log && excStr) ? `${log}\n${excStr}` : `${log}${excStr ?? ''}`;
|
reporter.update(path, { success: !controller.failed(), journal: controller.journal });
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractExceptionString(e: any): string {
|
function extractExceptionString(e: any): string {
|
||||||
@@ -126,6 +130,7 @@ function extractExceptionString(e: any): string {
|
|||||||
// Reporting
|
// Reporting
|
||||||
|
|
||||||
export interface Reporter {
|
export interface Reporter {
|
||||||
|
register(suites: TestGroup[]): void
|
||||||
update(path: string[], result: TestResult): void;
|
update(path: string[], result: TestResult): void;
|
||||||
finalize(): void;
|
finalize(): void;
|
||||||
}
|
}
|
||||||
@@ -149,6 +154,8 @@ export class StreamReporter implements Reporter {
|
|||||||
this.counts = { successes: 0, failures: 0 };
|
this.counts = { successes: 0, failures: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register(suites: TestGroup[]) {}
|
||||||
|
|
||||||
update(path: string[], result: TestResult) {
|
update(path: string[], result: TestResult) {
|
||||||
if (path.length === 0) throw new RangeError("path is empty");
|
if (path.length === 0) throw new RangeError("path is empty");
|
||||||
const pathStr = path.join(SEP);
|
const pathStr = path.join(SEP);
|
||||||
@@ -192,10 +199,11 @@ export class StreamReporter implements Reporter {
|
|||||||
|
|
||||||
#writeOutput(test: { name: string } & TestResult, prefix: string, nested: boolean) {
|
#writeOutput(test: { name: string } & TestResult, prefix: string, nested: boolean) {
|
||||||
let output = "";
|
let output = "";
|
||||||
if (test.output) {
|
let logOutput = formatJournal(test.journal);
|
||||||
const lines = test.output.split("\n");
|
if (logOutput) {
|
||||||
|
const lines = logOutput.split("\n");
|
||||||
if (lines.length <= 1) {
|
if (lines.length <= 1) {
|
||||||
output = `: ${test.output}`;
|
output = `: ${logOutput}`;
|
||||||
} else {
|
} else {
|
||||||
const padding = nested ? " " : " ";
|
const padding = nested ? " " : " ";
|
||||||
output = ":\n" + lines.map((line) => padding + line).join("\n");
|
output = ":\n" + lines.map((line) => padding + line).join("\n");
|
||||||
@@ -206,6 +214,8 @@ export class StreamReporter implements Reporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DOMReporter implements Reporter {
|
export class DOMReporter implements Reporter {
|
||||||
|
register(suites: TestGroup[]) {}
|
||||||
|
|
||||||
update(path: string[], result: TestResult) {
|
update(path: string[], result: TestResult) {
|
||||||
if (path.length === 0) throw new RangeError("path is empty");
|
if (path.length === 0) throw new RangeError("path is empty");
|
||||||
const counter = document.getElementById(result.success ? "successes" : "failures");
|
const counter = document.getElementById(result.success ? "successes" : "failures");
|
||||||
@@ -237,9 +247,10 @@ export class DOMReporter implements Reporter {
|
|||||||
}
|
}
|
||||||
const p = document.createElement("p");
|
const p = document.createElement("p");
|
||||||
p.classList.add("test-desc");
|
p.classList.add("test-desc");
|
||||||
if (result.output) {
|
const logOutput = formatJournal(result.journal);
|
||||||
|
if (logOutput) {
|
||||||
const pre = document.createElement("pre");
|
const pre = document.createElement("pre");
|
||||||
pre.appendChild(document.createTextNode(result.output));
|
pre.appendChild(document.createTextNode(logOutput));
|
||||||
p.appendChild(pre);
|
p.appendChild(pre);
|
||||||
} else {
|
} else {
|
||||||
p.classList.add("italic");
|
p.classList.add("italic");
|
||||||
@@ -250,3 +261,32 @@ export class DOMReporter implements Reporter {
|
|||||||
|
|
||||||
finalize() {}
|
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 id="root">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="./static/test_tests.js"></script>
|
<script type="module" src="./static/all_tests.js"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { DOMReporter, run } from "./static/test.js";
|
import { DOMReporter, run } from "./static/test.js";
|
||||||
run(new DOMReporter());
|
run(new DOMReporter());
|
||||||
|
|||||||
Reference in New Issue
Block a user