// Handles the job of waiting until text is seen that matches a // regular expression. import { makeFulfillablePromise } from '../utils/fiber-helpers.js'; import TestFailure from './test-failure.js'; import { Console } from '../console/console.js'; export default class Matcher { constructor(run) { this.buf = ""; this.fullBuffer = ""; this.ended = false; this.resetMatch(); this.run = run; // used only to set a field on exceptions this.endPromise = new Promise((resolve) => { this.resolveEndPromise = resolve; }); } async write(data) { this.buf += data; this.fullBuffer += data; await this._tryMatch(); } getFullBuffer() { return this.fullBuffer; } resetMatch() { const mp = this.matchPromise; this.matchPattern = null; this.matchPromise = null; this.matchStrict = null; this.matchFullBuffer = false; return mp; } setMatchStrict(strict) { this.matchStrict = strict; } rejectMatch(error) { const mp = this.resetMatch(); if (mp) { mp.reject(error); } else { // If this.matchPromise was not defined, we should not swallow this // error, so we must throw it instead. throw error; } } resolveMatch(value) { const mp = this.resetMatch(); if (mp) { mp.resolve(value); } } match(pattern, timeout, strict) { return this.matchAsync(pattern, { timeout, strict }); } // Like match, but returns a Promise without calling .await(). async matchAsync(pattern, { timeout = null, strict = false, matchFullBuffer = false, }) { if (this.matchPromise) { return Promise.reject(new Error("already have a match pending?")); } this.matchPattern = pattern; this.matchStrict = strict; this.matchFullBuffer = matchFullBuffer; const mp = this.matchPromise = makeFulfillablePromise(); await this._tryMatch(); // could clear this.matchPromise let timer = null; if (timeout) { const failure = new TestFailure('match-timeout', { run: this.run, pattern: this.matchPattern, }); timer = setTimeout(() => { this.rejectMatch(failure); }, timeout * 1000); } else { return mp; } return mp.then( (result) => { clearTimeout(timer); return result; }, (error) => { clearTimeout(timer); throw error; } ); } matchBeforeEnd(pattern, timeout) { return this._beforeEnd(() => this.matchAsync(pattern, { timeout: timeout || 15, matchFullBuffer: true, })); } _beforeEnd(promiseCallback) { this.endPromise = this.endPromise.then(promiseCallback); return this.endPromise; } end() { return this.endAsync(); } endAsync() { this.resolveEndPromise(); return this._beforeEnd(async () => { this.ended = true; this._tryMatch(); return this.matchPromise; }); } matchEmpty() { if (this.buf.length > 0) { Console.info("Extra junk is :", this.buf); throw new TestFailure('junk-at-end', { run: this.run }); } } async awaitMatchPromise() { if (!this.matchPromise) { return; } await this.matchPromise; this.matchPattern = null; } async _tryMatch() { const mp = this.matchPromise; if (! mp) { return; } let ret = null; if (this.matchFullBuffer) { // Note: this.matchStrict is ignored if this.matchFullBuffer truthy. if (this.matchPattern instanceof RegExp) { ret = this.fullBuffer.match(this.matchPattern); } else if (this.fullBuffer.indexOf(this.matchPattern) >= 0) { ret = this.matchPattern; } } else if (this.matchPattern instanceof RegExp) { const m = this.buf.match(this.matchPattern); if (m) { if (this.matchStrict && m.index !== 0) { Console.info("Extra junk is: ", this.buf.substr(0, m.index)); this.rejectMatch(new TestFailure('junk-before', { run: this.run, pattern: this.matchPattern, })); return; } ret = m; this.buf = this.buf.slice(m.index + m[0].length); } } else { const i = this.buf.indexOf(this.matchPattern); if (i !== -1) { if (this.matchStrict && i !== 0) { Console.info("Extra junk is: ", this.buf.substr(0, i)); this.rejectMatch(new TestFailure('junk-before', { run: this.run, pattern: this.matchPattern, })); return; } ret = this.matchPattern; this.buf = this.buf.slice(i + this.matchPattern.length); } } if (ret !== null) { this.resolveMatch(ret); return; } if (this.ended) { this.rejectMatch(new TestFailure('no-match', { run: this.run, pattern: this.matchPattern, })); return; } } } import { markThrowingMethods } from "./test-utils.js"; markThrowingMethods(Matcher.prototype);