From a708b2f85d0b6286b7f04641d21d2d523bad6842 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 5 Sep 2022 10:43:01 +1000 Subject: [PATCH] chore(developer): add test for compiler messages and cleanup e2e test 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. Also, added more reporting to e2e test in case of failures. --- .../src/kmldmlc/test/test-compiler-e2e.ts | 35 +++++---- developer/src/kmldmlc/test/test-messages.ts | 74 +++++++++++++++++++ 2 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 developer/src/kmldmlc/test/test-messages.ts 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