diff --git a/developer/src/kmldmlc/test/test-compiler-e2e.ts b/developer/src/kmldmlc/test/test-compiler-e2e.ts index 6f9f12b5f8..145bf2a90e 100644 --- a/developer/src/kmldmlc/test/test-compiler-e2e.ts +++ b/developer/src/kmldmlc/test/test-compiler-e2e.ts @@ -7,25 +7,32 @@ import Compiler from '../src/keyman/compiler/compiler'; import KMXBuilder from '../src/keyman/kmx/kmx-builder'; import {CompilerCallbacks, makePathToFixture} from './helpers/index'; +function checkMessages(callbacks: CompilerCallbacks) { + if(callbacks.messages.length > 0) { + console.log(callbacks.messages); + } + assert.isEmpty(callbacks.messages); +} + function compileKeyboard(inputFilename: string): Uint8Array { const callbacks = new CompilerCallbacks(); const k = new Compiler(callbacks); - let source = k.load(inputFilename); - if(!source) { - return null; - } - if(!k.validate(source)) { - return null; - } - let kmx = k.compile(source); - if(!kmx) { - return null; - } + const source = k.load(inputFilename); + checkMessages(callbacks); + assert.isNotNull(source, 'k.load should not have returned null'); + + const valid = k.validate(source); + checkMessages(callbacks); + assert.isTrue(valid, 'k.validate should not have failed'); + + const kmx = k.compile(source); + checkMessages(callbacks); + assert.isNotNull(kmx, 'k.compile should not have returned null'); // Use the builder to generate the binary output file - let builder = new KMXBuilder(kmx, true); - let result = builder.compile(); - assert(callbacks.messages.length == 0); + const builder = new KMXBuilder(kmx, true); + const result = builder.compile(); + checkMessages(callbacks); return result; } diff --git a/developer/src/kmldmlc/test/test-messages.ts b/developer/src/kmldmlc/test/test-messages.ts new file mode 100644 index 0000000000..831bb1e758 --- /dev/null +++ b/developer/src/kmldmlc/test/test-messages.ts @@ -0,0 +1,74 @@ +import 'mocha'; +import {assert, expect} from 'chai'; +import { CompilerErrorSeverity, CompilerMessages } from '../src/keyman/compiler/messages'; + +const toTitleCase = (s: string) => s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); + +// +// The purpose of these tests is to ensure that our slightly WET constant +// definitions for compiler errors are consistent in their usage: +// +// * that names are consistent between the message functions and constants +// * that constants have unique codes +// * that constants have the right error mask for their type +// +// This means we don't have to worry about DRYing out the constants, which would +// probably require us to have a preprocessing step, which has its own negatives. +// + +describe('compiler messages', function () { + it('each compiler message function has a corresponding constant', function() { + let keys = Object.keys(CompilerMessages); + + const m = CompilerMessages as Record; + + for(let key of keys) { + if(key == 'severityName') { + // any helper functions we skip here + continue; + } + + if(typeof m[key] == 'function') { + const o = /^(Info|Hint|Warning|Error|Fatal)_([A-Za-z0-9_]+)$/.exec(key); + expect(o).to.be.instanceOf(Array); + + const c = o[1].toUpperCase() + '_' + o[2]; + expect(m[c]).to.be.a('number', `Expected constant name ${c} to exist`); + + let v = m[key]('','','','','','','','','','','','' /* ignore arguments*/); + expect(v.code).to.equal(m[c], `Function ${key} returns the wrong code`); + } + else if(typeof m[key] == 'number') { + const o = /^(INFO|HINT|WARNING|ERROR|FATAL)_([A-Za-z0-9_]+)$/.exec(key); + expect(o).to.be.instanceOf(Array); + + const f = toTitleCase(o[1]) + '_' + o[2]; + expect(m[f]).to.be.a('function', `Expected function name ${f} to exist`); + } else { + assert.fail(`Unexepected compiler message member ${key}`); + } + } + }); + + it('each compiler message constant to be have the right mask and be unique', function() { + let keys = Object.keys(CompilerMessages); + + const m = CompilerMessages as Record; + + let codes: number[] = []; + + for(let key of keys) { + if(typeof m[key] == 'number') { + const o = /^(INFO|HINT|WARNING|ERROR|FATAL)_([A-Za-z0-9_]+)$/.exec(key); + expect(o).to.be.instanceOf(Array); + + const mask = CompilerMessages.severityName(m[key]); + expect(o[1]).to.equal(mask, `Mask value for ${key} does not match`); + + const code = m[key] & CompilerErrorSeverity.Error_Mask; + expect(codes).to.not.contain(code, `Constant value ${key} is not unique`); + codes.push(code); + } + } + }); +}); \ No newline at end of file