163 lines
3.8 KiB
JavaScript
163 lines
3.8 KiB
JavaScript
// META: global=window,worker,shadowrealm
|
|
'use strict';
|
|
|
|
class LipFuzzTransformer {
|
|
constructor(substitutions) {
|
|
this.substitutions = substitutions;
|
|
this.partialChunk = '';
|
|
this.lastIndex = undefined;
|
|
}
|
|
|
|
transform(chunk, controller) {
|
|
chunk = this.partialChunk + chunk;
|
|
this.partialChunk = '';
|
|
// lastIndex is the index of the first character after the last substitution.
|
|
this.lastIndex = 0;
|
|
chunk = chunk.replace(/\{\{([a-zA-Z0-9_-]+)\}\}/g, this.replaceTag.bind(this));
|
|
// Regular expression for an incomplete template at the end of a string.
|
|
const partialAtEndRegexp = /\{(\{([a-zA-Z0-9_-]+(\})?)?)?$/g;
|
|
// Avoid looking at any characters that have already been substituted.
|
|
partialAtEndRegexp.lastIndex = this.lastIndex;
|
|
this.lastIndex = undefined;
|
|
const match = partialAtEndRegexp.exec(chunk);
|
|
if (match) {
|
|
this.partialChunk = chunk.substring(match.index);
|
|
chunk = chunk.substring(0, match.index);
|
|
}
|
|
controller.enqueue(chunk);
|
|
}
|
|
|
|
flush(controller) {
|
|
if (this.partialChunk.length > 0) {
|
|
controller.enqueue(this.partialChunk);
|
|
}
|
|
}
|
|
|
|
replaceTag(match, p1, offset) {
|
|
let replacement = this.substitutions[p1];
|
|
if (replacement === undefined) {
|
|
replacement = '';
|
|
}
|
|
this.lastIndex = offset + replacement.length;
|
|
return replacement;
|
|
}
|
|
}
|
|
|
|
const substitutions = {
|
|
in1: 'out1',
|
|
in2: 'out2',
|
|
quine: '{{quine}}',
|
|
bogusPartial: '{{incompleteResult}'
|
|
};
|
|
|
|
const cases = [
|
|
{
|
|
input: [''],
|
|
output: ['']
|
|
},
|
|
{
|
|
input: [],
|
|
output: []
|
|
},
|
|
{
|
|
input: ['{{in1}}'],
|
|
output: ['out1']
|
|
},
|
|
{
|
|
input: ['z{{in1}}'],
|
|
output: ['zout1']
|
|
},
|
|
{
|
|
input: ['{{in1}}q'],
|
|
output: ['out1q']
|
|
},
|
|
{
|
|
input: ['{{in1}}{{in1}'],
|
|
output: ['out1', '{{in1}']
|
|
},
|
|
{
|
|
input: ['{{in1}}{{in1}', '}'],
|
|
output: ['out1', 'out1']
|
|
},
|
|
{
|
|
input: ['{{in1', '}}'],
|
|
output: ['', 'out1']
|
|
},
|
|
{
|
|
input: ['{{', 'in1}}'],
|
|
output: ['', 'out1']
|
|
},
|
|
{
|
|
input: ['{', '{in1}}'],
|
|
output: ['', 'out1']
|
|
},
|
|
{
|
|
input: ['{{', 'in1}'],
|
|
output: ['', '', '{{in1}']
|
|
},
|
|
{
|
|
input: ['{'],
|
|
output: ['', '{']
|
|
},
|
|
{
|
|
input: ['{', ''],
|
|
output: ['', '', '{']
|
|
},
|
|
{
|
|
input: ['{', '{', 'i', 'n', '1', '}', '}'],
|
|
output: ['', '', '', '', '', '', 'out1']
|
|
},
|
|
{
|
|
input: ['{{in1}}{{in2}}{{in1}}'],
|
|
output: ['out1out2out1']
|
|
},
|
|
{
|
|
input: ['{{wrong}}'],
|
|
output: ['']
|
|
},
|
|
{
|
|
input: ['{{wron', 'g}}'],
|
|
output: ['', '']
|
|
},
|
|
{
|
|
input: ['{{quine}}'],
|
|
output: ['{{quine}}']
|
|
},
|
|
{
|
|
input: ['{{bogusPartial}}'],
|
|
output: ['{{incompleteResult}']
|
|
},
|
|
{
|
|
input: ['{{bogusPartial}}}'],
|
|
output: ['{{incompleteResult}}']
|
|
}
|
|
];
|
|
|
|
for (const testCase of cases) {
|
|
const inputChunks = testCase.input;
|
|
const outputChunks = testCase.output;
|
|
promise_test(() => {
|
|
const lft = new TransformStream(new LipFuzzTransformer(substitutions));
|
|
const writer = lft.writable.getWriter();
|
|
const promises = [];
|
|
for (const inputChunk of inputChunks) {
|
|
promises.push(writer.write(inputChunk));
|
|
}
|
|
promises.push(writer.close());
|
|
const reader = lft.readable.getReader();
|
|
let readerChain = Promise.resolve();
|
|
for (const outputChunk of outputChunks) {
|
|
readerChain = readerChain.then(() => {
|
|
return reader.read().then(({ value, done }) => {
|
|
assert_false(done, `done should be false when reading ${outputChunk}`);
|
|
assert_equals(value, outputChunk, `value should match outputChunk`);
|
|
});
|
|
});
|
|
}
|
|
readerChain = readerChain.then(() => {
|
|
return reader.read().then(({ done }) => assert_true(done, `done should be true`));
|
|
});
|
|
promises.push(readerChain);
|
|
return Promise.all(promises);
|
|
}, `testing "${inputChunks}" (length ${inputChunks.length})`);
|
|
}
|