Skip to content

Commit

Permalink
Add Ceefax tests (#445)
Browse files Browse the repository at this point in the history
Closes #92 - I carefully checked the output and it looks good, so this is more of a regression test than anything else
  • Loading branch information
mattgodbolt authored Sep 15, 2024
1 parent 7ebaff7 commit 6d3299a
Show file tree
Hide file tree
Showing 9 changed files with 603 additions and 3 deletions.
486 changes: 483 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
"lint-staged": "^15.2.10",
"mini-css-extract-plugin": "^2.9.1",
"npm-run-all": "^4.1.5",
"pixelmatch": "^6.0.0",
"prettier": "^3.3.3",
"sharp": "^0.33.5",
"source-map-loader": "^5.0.0",
"style-loader": "^4.0.0",
"terser-webpack-plugin": "^5.3.10",
Expand Down
100 changes: 100 additions & 0 deletions tests/integration/ceefax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { describe, it } from "vitest";
import { TestMachine } from "../test-machine.js";
import assert from "assert";
import { Video } from "../../video.js";
import sharp from "sharp";
import pixelmatch from "pixelmatch";

class CapturingVideo extends Video {
constructor() {
super(false, new Uint32Array(1024 * 1024), () => {});
this.paint_ext = (left, top, right, bottom) => this._onPaint(left, top, right, bottom);
this._capturing = false;
this._captureSharp = null;
}

_onPaint(left, top, right, bottom) {
if (this._capturing) {
const width = right - left;
const height = bottom - top;
const bufferCopy = new Uint8Array(this.fb32.buffer.slice(0));
this._captureSharp = sharp(bufferCopy, {
raw: { width: 1024, height: 1024, channels: 4 },
}).extract({ left: left, top: top, width, height });
this._capturing = false;
}
}

async capture(testMachine) {
this._capturing = true;
await testMachine.runUntilVblank();
if (this._capturing) throw new Error("Should have captured by now");
return this._captureSharp;
}
}

async function setupTestMachine(video) {
const testMachine = new TestMachine(null, { video: video });
await testMachine.initialise();
await testMachine.runUntilInput();
await testMachine.loadDisc("discs/eng_test.ssd");
await testMachine.type("*EXEC !BOOT");
await testMachine.runFor(3 * 1000 * 1000);
return testMachine;
}

const rootDir = "tests/integration/ceefax";
const outputDir = `tests/integration/output`;

async function compare(video, testMachine, expectedName) {
const outputName = `${expectedName.replace("expected", "actual")}`;
const captureSharp = await video.capture(testMachine);
const outputFile = `${outputDir}/${outputName}`;
await captureSharp.removeAlpha().toFile(outputFile);
const expectedFile = `${rootDir}/${expectedName}`;
const diffFile = `${outputDir}/${outputName.replace(".png", ".diff.png")}`;

const { data: expectedData, info } = await sharp(expectedFile)
.ensureAlpha()
.raw()
.toBuffer({ resolveWithObject: true });
const actualData = await sharp(outputFile).ensureAlpha().raw().toBuffer();
const diffData = new Uint8Array(info.width * info.height * info.channels);

const numDiffPixels = pixelmatch(expectedData, actualData, diffData, info.width, info.height, {
threshold: 0.1,
});
await sharp(diffData, { raw: info }).removeAlpha().toFile(diffFile);
assert.equal(
numDiffPixels,
0,
`Images do not match - expected ${expectedFile}, got ${outputFile}, diffs: ${diffFile}}`,
);
}

describe("Test Ceefax test page", () => {
it("should match the Ceefax test page (no flash)", async () => {
const video = new CapturingVideo();
const testMachine = await setupTestMachine(video);
await compare(video, testMachine, `expected_flash_0.png`);
});
it("should match the Ceefax test page (flash)", async () => {
const video = new CapturingVideo();
const testMachine = await setupTestMachine(video);
await testMachine.runFor(1500000);
await compare(video, testMachine, `expected_flash_1.png`);
});
it("should match the Ceefax test page after reveal (no flash)", async () => {
const video = new CapturingVideo();
const testMachine = await setupTestMachine(video);
await testMachine.type(" ");
await compare(video, testMachine, `expected_reveal_flash_0.png`);
});
it("should match the Ceefax test page after reveal (flash)", async () => {
const video = new CapturingVideo();
const testMachine = await setupTestMachine(video);
await testMachine.type(" ");
await testMachine.runFor(1500000);
await compare(video, testMachine, `expected_reveal_flash_1.png`);
});
});
Binary file added tests/integration/ceefax/expected_flash_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/integration/ceefax/expected_flash_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tests/integration/output/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*
17 changes: 17 additions & 0 deletions tests/test-machine.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ export class TestMachine {
});
}

async runUntilVblank() {
let hit = false;
if (this.processor.isMaster) throw new Error("Not yet implemented");
const hook = this.processor.debugInstruction.add((addr) => {
if (addr === 0xdd15) {
hit = true;
return true;
}
});
await this.runFor(10 * 1000 * 1000);
hook.remove();
assert(hit, "did not hit appropriate breakpoint in time");
}

async runUntilInput(secs) {
if (!secs) secs = 120;
console.log("Running until keyboard input requested");
Expand Down Expand Up @@ -111,6 +125,9 @@ export class TestMachine {
} else if (ch === "*") {
ch = utils.keyCodes.APOSTROPHE;
shift = true;
} else if (ch === "!") {
ch = utils.keyCodes.K1;
shift = true;
} else if (ch === ".") {
ch = utils.keyCodes.PERIOD;
} else ch = ch.toUpperCase().charCodeAt(0);
Expand Down

0 comments on commit 6d3299a

Please sign in to comment.