import assert from "assert"; import { describe, it } from "mocha"; import { TOOL_META, getToolMeta, getToolMetaForFilename, getPreloadFSName, getSkeletonName, getIncludePatterns, getLinkPatterns, matchDependencyPatterns, getSystemIncludePatterns, SYSTEM_INCLUDE_PATTERNS, getSharedFileSystemName, getToolHelpURL, getPlatformToolHelpURL, getToolVersionLabel, SDCC_DEFAULT_VERSION } from "../../src/common/toolmeta"; // Node-friendly worker entry point (re-exports TOOLS without Worker wiring) import { TOOLS } from "../../src/worker/workerlib"; describe('Tool metadata registry', function () { it('every registry key matches its tool id', function () { for (let id in TOOL_META) { assert.strictEqual(TOOL_META[id].id, id, `TOOL_META['${id}'].id mismatch`); } }); it('every worker build tool is registered (and vice versa)', function () { // worker TOOLS -> must exist in registry for (let id of Object.keys(TOOLS)) { assert.ok(TOOL_META[id], `worker TOOLS['${id}'] missing from TOOL_META`); } // registry -> must have a worker build fn, unless explicitly deferred // or built on a remote server (llvm-mos) for (let id in TOOL_META) { let meta = TOOL_META[id]; if (meta.noWorkerBuild || meta.remote) continue; assert.ok(TOOLS[id], `TOOL_META['${id}'] missing from worker TOOLS`); } }); it('getToolMeta strips the remote: transport prefix', function () { assert.strictEqual(getToolMeta('remote:llvm-mos'), TOOL_META['llvm-mos']); assert.strictEqual(getToolMeta('llvm-mos'), TOOL_META['llvm-mos']); assert.strictEqual(getToolMeta('bogus'), undefined); }); it('getToolMetaForFilename finds tools by extension', function () { assert.ok(getToolMetaForFilename('foo.c').some(m => m.id === 'cc65')); assert.ok(getToolMetaForFilename('foo.s').some(m => m.id === 'ca65')); assert.ok(getToolMetaForFilename('foo.wiz').some(m => m.id === 'wiz')); assert.ok(getToolMetaForFilename('foo.ecs').some(m => m.id === 'ecs')); assert.ok(getToolMetaForFilename('foo.dg').some(m => m.id === 'dialog')); // unknown extension -> no matches assert.deepStrictEqual(getToolMetaForFilename('foo.xyz'), []); }); it('getPreloadFSName resolves per-platform filesystems', function () { assert.strictEqual(getPreloadFSName('cc65', 'nes'), undefined); // WASI zip, not preloadFS assert.strictEqual(getSharedFileSystemName('cc65', 'nes'), 'wasi:cc65-fs-nes.zip'); assert.strictEqual(getSharedFileSystemName('cc65', 'c64'), 'wasi:cc65-fs-c64.zip'); assert.strictEqual(getSharedFileSystemName('cc65', 'nonexistent'), undefined); // the 'default' config leads with whichever SDCC the project defaults to // (getSharedFileSystemName prefers preloadFS); platforms whose libraries // were built by 3.x always keep the 3.6.5 preloadFS. const defaultPreload = SDCC_DEFAULT_VERSION === 3 ? 'sdcc' : undefined; const defaultShared = SDCC_DEFAULT_VERSION === 3 ? 'sdcc' : 'wasi:sdcc-fs.zip'; assert.strictEqual(getPreloadFSName('sdcc'), defaultPreload); assert.strictEqual(getSharedFileSystemName('sdcc', 'mw8080bw'), defaultShared); assert.strictEqual(getPreloadFSName('sdcc', 'gb'), 'sdcc'); assert.strictEqual(getPreloadFSName('sdcc', 'msx-libcv'), 'sdcc'); assert.strictEqual(getSharedFileSystemName('sdcc', 'msx'), defaultShared); assert.strictEqual(getPreloadFSName('dasm'), undefined); }); it('getSharedFileSystemName resolves suffixed platform ids to their root base', function () { // atari8 runs as 'atari8-800' / 'atari8-5200' / *.xlmame variants assert.strictEqual(getSharedFileSystemName('cc65', 'atari8'), 'wasi:cc65-fs-atari8.zip'); assert.strictEqual(getSharedFileSystemName('cc65', 'atari8-800'), 'wasi:cc65-fs-atari8.zip'); assert.strictEqual(getSharedFileSystemName('cc65', 'atari8-800.xlmame'), 'wasi:cc65-fs-atari8.zip'); assert.strictEqual(getSharedFileSystemName('cc65', 'atari8-5200'), 'wasi:cc65-fs-atari8.zip'); assert.strictEqual(getSharedFileSystemName('fastbasic', 'atari8-800'), 'wasi:cc65-fs-atari8.zip'); assert.strictEqual(getSharedFileSystemName('ecs', 'nes'), 'wasi:cc65-fs-nes.zip'); }); it('getSkeletonName defaults to the tool id', function () { assert.strictEqual(getSkeletonName('dasm'), 'dasm'); assert.strictEqual(getSkeletonName('remote:llvm-mos'), 'llvm-mos'); }); it('getToolVersionLabel shows an alternate version when there is one', function () { // both SDCC releases are selectable, so show both (default first) let sdcc = SDCC_DEFAULT_VERSION === 3 ? '3.6.5 (default) / 4.6.0' : '4.6.0 (default) / 3.6.5'; assert.strictEqual(getToolVersionLabel(TOOL_META['sdcc']), sdcc); // single-version tools show just their version, unknown ones a dash assert.strictEqual(getToolVersionLabel(TOOL_META['dasm']), '2.20.17'); assert.strictEqual(getToolVersionLabel(TOOL_META['basic']), '—'); assert.strictEqual(getToolVersionLabel(undefined), '—'); }); it('covers the previously-scattered editor styles and help URLs', function () { // spot-check ids that used to live in TOOL_TO_SOURCE_STYLE / TOOL_TO_HELPURL for (let id of ['dasm', 'acme', 'cc65', 'ca65', 'sdcc', 'cmoc', 'zmac', 'nesasm', 'bataribasic', 'fastbasic', 'wiz', 'silice', 'ecs', 'dialog']) { assert.ok(TOOL_META[id], `expected tool '${id}' in registry`); } assert.strictEqual(TOOL_META['cc65'].editorStyle, 'text/x-csrc'); assert.strictEqual(TOOL_META['ca65'].editorStyle, '6502'); assert.ok(TOOL_META['dasm'].helpURL); }); it('include patterns exist for tools that do dependency parsing', function () { assert.ok(TOOL_META['cc65'].includePatterns.length > 0); assert.ok(TOOL_META['verilator'].includePatterns.length > 0); assert.ok(TOOL_META['acme'].includePatterns.length > 0); assert.ok(TOOL_META['dialog'].includePatterns.length > 0); assert.ok(TOOL_META['wiz'].includePatterns.length > 0); }); it('matches include directives for the tool that builds the file', function () { let deps = (text: string, tool?: string, platform?: string) => matchDependencyPatterns(text, getIncludePatterns(tool, platform)); assert.deepStrictEqual(deps('#include "foo.h"\n', 'cc65'), ['foo.h']); assert.deepStrictEqual(deps('\t.include "foo.inc"\n', 'ca65'), ['foo.inc']); assert.deepStrictEqual(deps('//#resource "foo.bin"\n', 'dasm'), ['foo.bin']); // dasm also accepts unquoted include/incbin names assert.deepStrictEqual(deps('\tINCLUDE VCS.h\n\tINCLUDE macro.h\n', 'dasm'), ['VCS.h', 'macro.h']); assert.deepStrictEqual(deps(' .include foo.inc\n incbin table.bin\n', 'dasm'), ['foo.inc', 'table.bin']); // quoted names stay matched by the shared pattern only (no duplicates) assert.deepStrictEqual(deps('\tinclude "vcs.h"\n', 'dasm'), ['vcs.h']); assert.deepStrictEqual(deps('!src "foo.acme"\n', 'acme'), ['foo.acme']); assert.deepStrictEqual(deps('%% #include "foo.dg"\n', 'dialog'), ['foo.dg']); assert.deepStrictEqual(deps(' USE foo.xasm\n', 'xasm6809'), ['foo.xasm']); // ecs uses both its own import and the shared .include directive assert.deepStrictEqual(deps('.include "vcs.h"\nimport "lib.ecs"\n', 'ecs'), ['vcs.h', 'lib.ecs']); // wiz: import gets an implicit .wiz extension, embed does not assert.deepStrictEqual(deps('import "nes";\nembed "map.bin";\n', 'wiz'), ['nes.wiz', 'map.bin']); // verilog: .arch names a .json file assert.deepStrictEqual(deps('`include "hvsync.v"\n.arch mynes\n', 'verilator'), ['hvsync.v', 'mynes.json']); // unknown tool falls back to the platform assert.deepStrictEqual(deps('`include "hvsync.v"\n', null, 'verilog'), ['hvsync.v']); assert.deepStrictEqual(deps('#include "foo.h"\n', null, 'c64'), ['foo.h']); // system includes () are NOT build dependencies assert.deepStrictEqual(deps('#include \n', 'cc65'), []); assert.deepStrictEqual(deps('#include \n', 'sdcc'), []); }); it('matches system include patterns for UI links only', function () { // tools with a bundled filesystem get system-include link patterns assert.deepStrictEqual(getSystemIncludePatterns('cc65'), SYSTEM_INCLUDE_PATTERNS); assert.deepStrictEqual(getSystemIncludePatterns('sdcc'), SYSTEM_INCLUDE_PATTERNS); // tools without one do not (dasm has no includeDirs) assert.deepStrictEqual(getSystemIncludePatterns('dasm'), []); assert.deepStrictEqual(getSystemIncludePatterns('bogus'), []); // ...and they extract the filename from <...> let deps = (text: string, tool?: string) => matchDependencyPatterns(text, getSystemIncludePatterns(tool)); assert.deepStrictEqual(deps('#include \n', 'cc65'), ['stdio.h']); assert.deepStrictEqual(deps(' # include \n', 'cc65'), ['atari7800.h']); // quoted includes are not matched by the system patterns assert.deepStrictEqual(deps('#include "foo.h"\n', 'cc65'), []); }); it('resolves shared filesystem names for all tools with includeDirs', function () { assert.equal(getSharedFileSystemName('cc65', 'nes'), 'wasi:cc65-fs-nes.zip'); // preloadFS-backed tools assert.equal(getSharedFileSystemName('sdcc', 'coleco'), 'sdcc'); assert.equal(getSharedFileSystemName('bataribasic', 'vcs'), 'wasi:bb-fs.zip'); assert.equal(getSharedFileSystemName('wiz'), 'wiz'); // WASI zip-backed tools (cc2600/cc7800/armtcc/oscar64/dialog) assert.equal(getSharedFileSystemName('cc2600'), 'wasi:cc2600-fs.zip'); assert.equal(getSharedFileSystemName('cc7800'), 'wasi:cc7800-fs.zip'); assert.equal(getSharedFileSystemName('armtcc'), 'wasi:arm32-fs.zip'); assert.equal(getSharedFileSystemName('oscar64'), 'wasi:oscar64-fs.zip'); assert.equal(getSharedFileSystemName('dialog'), 'wasi:dialog-fs.zip'); // tools without a bundled filesystem assert.equal(getSharedFileSystemName('cmoc'), undefined); assert.equal(getSharedFileSystemName('smlrc'), undefined); assert.equal(getSharedFileSystemName('dasm'), undefined); }); it('matches link directives only for tools that link', function () { let deps = (text: string, tool?: string, platform?: string) => matchDependencyPatterns(text, getLinkPatterns(tool, platform)); assert.deepStrictEqual(deps('//#link "foo.c"\n', 'cc65'), ['foo.c']); assert.deepStrictEqual(deps(';#link "foo.s"\n', 'ca65'), ['foo.s']); assert.deepStrictEqual(deps('//#link "foo.v"\n', 'verilator'), []); // hdl tools don't link assert.deepStrictEqual(deps('//#link "foo.v"\n', null, 'verilog'), []); }); it('parses verilog directives in .asm files built by jsasm', function () { // presets/verilog/test2.asm: .include "*.v" plus ".arch femto16" let text = '.include "cpu16.v"\n.arch femto16\n'; let files = matchDependencyPatterns(text, getIncludePatterns('jsasm', 'verilog')); assert.deepStrictEqual(files, ['cpu16.v', 'femto16.json']); // and no shared-pattern duplicate of the .include assert.strictEqual(files.filter(f => f == 'cpu16.v').length, 1); }); it('falls back to the platform when a tool declares no patterns', function () { // 'basic' has no includePatterns of its own -> platform default assert.deepStrictEqual( matchDependencyPatterns('#include "foo.h"\n', getIncludePatterns('basic', 'c64')), ['foo.h']); // an explicit empty list means "this tool has no such directives" assert.deepStrictEqual(getLinkPatterns('jsasm', 'verilog'), []); }); it('resolves platform-specific tool help pages', function () { // cc65 publishes per-platform manuals; those win over the generic page assert.strictEqual(getToolHelpURL('cc65', 'atari8'), 'https://cc65.github.io/doc/atari.html'); assert.strictEqual(getToolHelpURL('cc65', 'c64'), 'https://cc65.github.io/doc/c64.html'); // suffixed platform ids resolve through their root base assert.strictEqual(getToolHelpURL('cc65', 'atari8-800.xlmame'), 'https://cc65.github.io/doc/atari.html'); // no platform-specific page -> the tool's generic helpURL assert.strictEqual(getToolHelpURL('cc65', 'vector'), TOOL_META['cc65'].helpURL); assert.strictEqual(getToolHelpURL('dasm', 'atari8'), TOOL_META['dasm'].helpURL); // tools with no docs at all, or unknown tools assert.strictEqual(getToolHelpURL('ld65', 'atari8'), undefined); assert.strictEqual(getToolHelpURL('bogus', 'atari8'), undefined); }); it('getPlatformToolHelpURL only returns platform-specific pages', function () { assert.strictEqual(getPlatformToolHelpURL('cc65', 'atari8'), 'https://cc65.github.io/doc/atari.html'); assert.strictEqual(getPlatformToolHelpURL('cc65', 'atari8-800.xlmame'), 'https://cc65.github.io/doc/atari.html'); // generic-only tools have no platform-specific page assert.strictEqual(getPlatformToolHelpURL('dasm', 'atari8'), undefined); assert.strictEqual(getPlatformToolHelpURL('cc65', 'vector'), undefined); assert.strictEqual(getPlatformToolHelpURL('bogus', 'atari8'), undefined); }); it('rewinds shared global patterns between files', function () { // patterns are shared between tools and have the /g flag, so a stale // lastIndex would make the second scan miss the directive let pats = getIncludePatterns('cc65'); assert.deepStrictEqual(matchDependencyPatterns('#include "a.h"\n', pats), ['a.h']); assert.deepStrictEqual(matchDependencyPatterns('#include "b.h"\n', pats), ['b.h']); }); });