Files
8bitworkshop/test/unit/testlinking.ts
T

111 lines
5.7 KiB
TypeScript

import assert from "assert";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { describe, it } from "mocha";
import { compileSourceFile, preload } from "../../src/tools/testlib";
// A hand-written ca65 program for the VCS is a plain 4K cartridge: it supplies
// its own reset code and vectors, so it must not be linked against the C
// runtime (crt0.o + atari2600.lib) or laid out with the bank-switched
// atari2600.cfg that vcslib's C programs use -- crt0 has a VECTORS segment of
// its own, and two of them overflow the $FFFA..$FFFF the config pins them to.
describe('vcs link configuration', () => {
it('should link a ca65 project as a plain 4K cart', async () => {
await preload('ca65', 'vcs');
const result: any = await compileSourceFile('ca65', 'vcs', 'presets/vcs/skeleton.ca65', 'skeleton.s');
assert.deepStrictEqual(result.errors || [], []);
assert.strictEqual(result.output.length, 4096);
});
it('should link a cc65 project with the bank-switched config', async () => {
await preload('cc65', 'vcs');
const result: any = await compileSourceFile('cc65', 'vcs', 'presets/vcs/skeleton.cc65', 'skeleton.c');
assert.deepStrictEqual(result.errors || [], []);
assert.strictEqual(result.output.length, 18432);
});
});
// sdcc's linker also emits records for whatever it placed outside the ROM --
// here a GSINIT area that landed in the _DATA area's RAM at $e5b0. Those bytes
// are not part of the cartridge, and checking them for an overlap used to read
// past the end of the ROM image and report one that wasn't there.
describe('sdcc IHX output', () => {
it('should ignore records outside the ROM image', async () => {
await preload('sdcc', 'vector-z80color');
const result: any = await compileSourceFile('sdcc', 'vector-z80color', 'presets/vector-z80color/game.c');
assert.deepStrictEqual(result.errors || [], []);
assert.strictEqual(result.output.length, 32768);
});
});
// oscar64 compiles and links a whole program in one invocation. A linked
// source ("//#link") must be handed to that same invocation -- a .c file would
// otherwise be routed to cc65, which never runs because oscar64 emits the
// final binary first, leaving the symbol undefined. oscar64's own
// "#pragma compile("file.c")" is handled by the compiler, so that file only
// has to be a build dependency.
async function compileOscar64With(directive: string, libSource: string): Promise<any> {
await preload('oscar64', 'c64');
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'oscar64-'));
try {
const main = path.join(dir, 'main.cpp');
fs.writeFileSync(main,
'#include <stdio.h>\n' +
'extern int addnums(int a, int b);\n' +
directive + '\n' +
'int main(void) { printf("%d", addnums(2, 3)); return 0; }\n');
fs.writeFileSync(path.join(dir, 'addnums.c'), libSource);
return await compileSourceFile('oscar64', 'c64', main);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
}
describe('oscar64 linked sources', () => {
it('should compile a //#link .c file as oscar64', async () => {
const result = await compileOscar64With('//#link "addnums.c"',
'int addnums(int a, int b) { return a + b; }\n');
assert.deepStrictEqual(result.errors || [], []);
assert.strictEqual(result.success, true);
assert.ok(result.output.length > 0);
});
it('should compile a #pragma compile .c file as oscar64', async () => {
const result = await compileOscar64With('#pragma compile("addnums.c")',
'int addnums(int a, int b) { return a + b; }\n');
assert.deepStrictEqual(result.errors || [], []);
assert.strictEqual(result.success, true);
assert.ok(result.output.length > 0);
});
it('should report errors in the linked file, not the main file', async () => {
const result = await compileOscar64With('//#link "addnums.c"',
'int addnums(int a, int b) {\n return a + undefined_symbol;\n}\n');
assert.strictEqual(result.success, false);
assert.strictEqual(result.errors.length, 1);
assert.strictEqual(result.errors[0].path, 'addnums.c');
assert.strictEqual(result.errors[0].line, 2);
});
it('should report errors in a #pragma compile file, not the main file', async () => {
const result = await compileOscar64With('#pragma compile("addnums.c")',
'int addnums(int a, int b) {\n return a + undefined_symbol;\n}\n');
assert.strictEqual(result.success, false);
assert.strictEqual(result.errors.length, 1);
assert.strictEqual(result.errors[0].path, 'addnums.c');
assert.strictEqual(result.errors[0].line, 2);
});
// The linked file is compiled into the same listing as the main file; it
// has to carry its own line markers (tagged with its path) so the editor
// and source breakpoints can map addresses back to the linked source.
it('should emit source lines for a linked file', async () => {
const result = await compileOscar64With('//#link "addnums.c"',
'int addnums(int a, int b) {\n' +
' if (a <= 0) return b;\n' +
' return addnums(a-1, b+a) + addnums(a-2, b-a) + 1;\n' +
'}\n');
assert.deepStrictEqual(result.errors || [], []);
const lines = result.listings['main.lst'].lines;
const liblines = lines.filter(l => l.path === 'addnums.c');
assert.ok(liblines.length > 0, 'expected source lines for the linked file');
assert.ok(liblines.some(l => l.line === 3), 'expected line 3 of the linked file');
});
});