• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// META: global=window,worker
2'use strict';
3
4class LipFuzzTransformer {
5  constructor(substitutions) {
6    this.substitutions = substitutions;
7    this.partialChunk = '';
8    this.lastIndex = undefined;
9  }
10
11  transform(chunk, controller) {
12    chunk = this.partialChunk + chunk;
13    this.partialChunk = '';
14    // lastIndex is the index of the first character after the last substitution.
15    this.lastIndex = 0;
16    chunk = chunk.replace(/\{\{([a-zA-Z0-9_-]+)\}\}/g, this.replaceTag.bind(this));
17    // Regular expression for an incomplete template at the end of a string.
18    const partialAtEndRegexp = /\{(\{([a-zA-Z0-9_-]+(\})?)?)?$/g;
19    // Avoid looking at any characters that have already been substituted.
20    partialAtEndRegexp.lastIndex = this.lastIndex;
21    this.lastIndex = undefined;
22    const match = partialAtEndRegexp.exec(chunk);
23    if (match) {
24      this.partialChunk = chunk.substring(match.index);
25      chunk = chunk.substring(0, match.index);
26    }
27    controller.enqueue(chunk);
28  }
29
30  flush(controller) {
31    if (this.partialChunk.length > 0) {
32      controller.enqueue(this.partialChunk);
33    }
34  }
35
36  replaceTag(match, p1, offset) {
37    let replacement = this.substitutions[p1];
38    if (replacement === undefined) {
39      replacement = '';
40    }
41    this.lastIndex = offset + replacement.length;
42    return replacement;
43  }
44}
45
46const substitutions = {
47  in1: 'out1',
48  in2: 'out2',
49  quine: '{{quine}}',
50  bogusPartial: '{{incompleteResult}'
51};
52
53const cases = [
54  {
55    input: [''],
56    output: ['']
57  },
58  {
59    input: [],
60    output: []
61  },
62  {
63    input: ['{{in1}}'],
64    output: ['out1']
65  },
66  {
67    input: ['z{{in1}}'],
68    output: ['zout1']
69  },
70  {
71    input: ['{{in1}}q'],
72    output: ['out1q']
73  },
74  {
75    input: ['{{in1}}{{in1}'],
76    output: ['out1', '{{in1}']
77  },
78  {
79    input: ['{{in1}}{{in1}', '}'],
80    output: ['out1', 'out1']
81  },
82  {
83    input: ['{{in1', '}}'],
84    output: ['', 'out1']
85  },
86  {
87    input: ['{{', 'in1}}'],
88    output: ['', 'out1']
89  },
90  {
91    input: ['{', '{in1}}'],
92    output: ['', 'out1']
93  },
94  {
95    input: ['{{', 'in1}'],
96    output: ['', '', '{{in1}']
97  },
98  {
99    input: ['{'],
100    output: ['', '{']
101  },
102  {
103    input: ['{', ''],
104    output: ['', '', '{']
105  },
106  {
107    input: ['{', '{', 'i', 'n', '1', '}', '}'],
108    output: ['', '', '', '', '', '', 'out1']
109  },
110  {
111    input: ['{{in1}}{{in2}}{{in1}}'],
112    output: ['out1out2out1']
113  },
114  {
115    input: ['{{wrong}}'],
116    output: ['']
117  },
118  {
119    input: ['{{wron', 'g}}'],
120    output: ['', '']
121  },
122  {
123    input: ['{{quine}}'],
124    output: ['{{quine}}']
125  },
126  {
127    input: ['{{bogusPartial}}'],
128    output: ['{{incompleteResult}']
129  },
130  {
131    input: ['{{bogusPartial}}}'],
132    output: ['{{incompleteResult}}']
133  }
134];
135
136for (const testCase of cases) {
137  const inputChunks = testCase.input;
138  const outputChunks = testCase.output;
139  promise_test(() => {
140    const lft = new TransformStream(new LipFuzzTransformer(substitutions));
141    const writer = lft.writable.getWriter();
142    const promises = [];
143    for (const inputChunk of inputChunks) {
144      promises.push(writer.write(inputChunk));
145    }
146    promises.push(writer.close());
147    const reader = lft.readable.getReader();
148    let readerChain = Promise.resolve();
149    for (const outputChunk of outputChunks) {
150      readerChain = readerChain.then(() => {
151        return reader.read().then(({ value, done }) => {
152          assert_false(done, `done should be false when reading ${outputChunk}`);
153          assert_equals(value, outputChunk, `value should match outputChunk`);
154        });
155      });
156    }
157    readerChain = readerChain.then(() => {
158      return reader.read().then(({ done }) => assert_true(done, `done should be true`));
159    });
160    promises.push(readerChain);
161    return Promise.all(promises);
162  }, `testing "${inputChunks}" (length ${inputChunks.length})`);
163}
164