diff --git a/common/predictive-text/index.ts b/common/predictive-text/index.ts index d41d97559b..887f71c2ce 100644 --- a/common/predictive-text/index.ts +++ b/common/predictive-text/index.ts @@ -65,7 +65,7 @@ namespace com.keyman.text.prediction { * @param uri URI of the underlying LMLayer worker code. This will usually be a blob: * or file: URI. If uri is not provided, this will start the default Worker. */ - constructor(capabilities: Capabilities, worker?: Worker) { + constructor(capabilities: Capabilities, worker?: Worker, testMode?: boolean) { // Either use the given worker, or instantiate the default worker. this._worker = worker || DefaultWorker.constructInstance(); this._worker.onmessage = this.onMessage.bind(this) @@ -76,7 +76,7 @@ namespace com.keyman.text.prediction { this._revertPromises = new PromiseStore(); this._nextToken = Number.MIN_SAFE_INTEGER; - this.sendConfig(capabilities); + this.sendConfig(capabilities, !!testMode); } /** @@ -85,10 +85,11 @@ namespace com.keyman.text.prediction { * @param capabilities The host platform's capability spec - a model cannot assume access to more context * than specified by this parameter. */ - private sendConfig(capabilities: Capabilities) { + private sendConfig(capabilities: Capabilities, testMode: boolean) { this._worker.postMessage({ message: 'config', - capabilities: capabilities + capabilities: capabilities, + testMode: testMode }); } diff --git a/common/predictive-text/unit_tests/in_browser/cases/top-level-lmlayer.js b/common/predictive-text/unit_tests/in_browser/cases/top-level-lmlayer.js index 46e182deb9..12c0c2aff1 100644 --- a/common/predictive-text/unit_tests/in_browser/cases/top-level-lmlayer.js +++ b/common/predictive-text/unit_tests/in_browser/cases/top-level-lmlayer.js @@ -7,7 +7,7 @@ describe('LMLayer', function () { describe('[[constructor]]', function () { it('should construct with a single argument', function () { - let lmLayer = new LMLayer(helpers.defaultCapabilities); + let lmLayer = new LMLayer(helpers.defaultCapabilities, null, true); assert.instanceOf(lmLayer, LMLayer); lmLayer.shutdown(); }); diff --git a/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.js b/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.js index 69b48fe866..993975dbfe 100644 --- a/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.js +++ b/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.js @@ -16,7 +16,8 @@ describe('LMLayer using dummy model', function () { it('will predict future suggestions', function () { this.timeout(testconfig.timeouts.standard * 3); // This one makes multiple subsequent calls across // the WebWorker boundary, so we should be generous here. - var lmLayer = new LMLayer(helpers.defaultCapabilities); + + var lmLayer = new LMLayer(helpers.defaultCapabilities, null, true); var stripIDs = function(suggestions) { suggestions.forEach(function(suggestion) { diff --git a/common/predictive-text/unit_tests/in_browser/cases/worker-trie-integration.js b/common/predictive-text/unit_tests/in_browser/cases/worker-trie-integration.js index 635c1f2425..833cda2aea 100644 --- a/common/predictive-text/unit_tests/in_browser/cases/worker-trie-integration.js +++ b/common/predictive-text/unit_tests/in_browser/cases/worker-trie-integration.js @@ -12,7 +12,11 @@ describe('LMLayer using the trie model', function () { it('will predict an empty buffer', function () { this.timeout(testconfig.timeouts.standard * 3); // This one makes multiple subsequent calls across // the WebWorker boundary, so we should be generous here. - var lmLayer = new LMLayer(helpers.defaultCapabilities); + + // Parameter 3 = true: enables 'test mode', disables correction-search timeout. + // This helps prevent the correction-search timeout from flaking out periodically during unit tests in + // CI, since remote servers / devices are involved. + var lmLayer = new LMLayer(helpers.defaultCapabilities, null, true); // We're testing many as asynchronous messages in a row. // this would be cleaner using async/await syntax, but diff --git a/common/web/lm-worker/src/index.ts b/common/web/lm-worker/src/index.ts index e3c16fa2c8..6494c3f5d2 100644 --- a/common/web/lm-worker/src/index.ts +++ b/common/web/lm-worker/src/index.ts @@ -84,6 +84,8 @@ class LMLayerWorker { private _platformCapabilities: Capabilities; + private _testMode: boolean = false; + private _hostURL: string; private _currentModelSource: ModelSourceSpec; @@ -259,6 +261,7 @@ class LMLayerWorker { } this._platformCapabilities = payload.capabilities; + this._testMode = !!payload.testMode; this.transitionToLoadingState(); } @@ -308,7 +311,7 @@ class LMLayerWorker { * @param model The loaded language model. */ private transitionToReadyState(model: LexicalModel): ModelCompositor { - let compositor = new ModelCompositor(model); + let compositor = new ModelCompositor(model, this._testMode); this.state = { name: 'ready', handleMessage: (payload) => { diff --git a/common/web/lm-worker/src/model-compositor.ts b/common/web/lm-worker/src/model-compositor.ts index 95c2e390fd..2bb4fdbafa 100644 --- a/common/web/lm-worker/src/model-compositor.ts +++ b/common/web/lm-worker/src/model-compositor.ts @@ -25,12 +25,15 @@ class ModelCompositor { private SUGGESTION_ID_SEED = 0; - constructor(lexicalModel: LexicalModel) { + private testMode: boolean = false + + constructor(lexicalModel: LexicalModel, testMode?: boolean) { this.lexicalModel = lexicalModel; if(lexicalModel.traverseFromRoot) { this.contextTracker = new correction.ContextTracker(); } this.punctuation = ModelCompositor.determinePunctuationFromModel(lexicalModel); + this.testMode = !!testMode; } private predictFromCorrections(corrections: ProbabilityMass[], context: Context): Distribution { @@ -233,7 +236,8 @@ class ModelCompositor { // Whitespace is probably fine, actually. Less sure about backspace. let bestCorrectionCost: number; - for(let matches of searchSpace.getBestMatches()) { + const SEARCH_TIMEOUT = this.testMode ? 0 : correction.SearchSpace.DEFAULT_ALLOTTED_CORRECTION_TIME_INTERVAL; + for(let matches of searchSpace.getBestMatches(SEARCH_TIMEOUT)) { // Corrections obtained: now to predict from them! let predictionRoots = matches.map(function(match) { let correction = match.matchString; diff --git a/common/web/lm-worker/src/worker-interfaces.ts b/common/web/lm-worker/src/worker-interfaces.ts index dfc3b6e37a..de8f20a1a2 100644 --- a/common/web/lm-worker/src/worker-interfaces.ts +++ b/common/web/lm-worker/src/worker-interfaces.ts @@ -52,6 +52,13 @@ interface ConfigMessage { * The platform's supported capabilities. */ capabilities: Capabilities; + + /** + * An optional flag to enable 'test mode'. + * + * At present, this just disables the correction algorithm's timeout when set to `true`. + */ + testMode?: boolean } interface ModelFile {