Files
meteor/tools/tool-testing/matcher.js
2023-04-04 14:02:30 -04:00

201 lines
4.8 KiB
JavaScript

// 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 _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);