tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

close-propagation-forward.any.js (16654B)


      1 // META: global=window,worker,shadowrealm
      2 // META: script=../resources/test-utils.js
      3 // META: script=../resources/recording-streams.js
      4 'use strict';
      5 
      6 const error1 = new Error('error1!');
      7 error1.name = 'error1';
      8 
      9 promise_test(() => {
     10 
     11  const rs = recordingReadableStream({
     12    start(controller) {
     13      controller.close();
     14    }
     15  });
     16 
     17  const ws = recordingWritableStream();
     18 
     19  return rs.pipeTo(ws).then(value => {
     20    assert_equals(value, undefined, 'the promise must fulfill with undefined');
     21  })
     22  .then(() => {
     23    assert_array_equals(rs.events, []);
     24    assert_array_equals(ws.events, ['close']);
     25 
     26    return Promise.all([
     27      rs.getReader().closed,
     28      ws.getWriter().closed
     29    ]);
     30  });
     31 
     32 }, 'Closing must be propagated forward: starts closed; preventClose omitted; fulfilled close promise');
     33 
     34 promise_test(t => {
     35 
     36  const rs = recordingReadableStream({
     37    start(controller) {
     38      controller.close();
     39    }
     40  });
     41 
     42  const ws = recordingWritableStream({
     43    close() {
     44      throw error1;
     45    }
     46  });
     47 
     48  return promise_rejects_exactly(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error').then(() => {
     49    assert_array_equals(rs.events, []);
     50    assert_array_equals(ws.events, ['close']);
     51 
     52    return Promise.all([
     53      rs.getReader().closed,
     54      promise_rejects_exactly(t, error1, ws.getWriter().closed)
     55    ]);
     56  });
     57 
     58 }, 'Closing must be propagated forward: starts closed; preventClose omitted; rejected close promise');
     59 
     60 for (const falsy of [undefined, null, false, +0, -0, NaN, '']) {
     61  const stringVersion = Object.is(falsy, -0) ? '-0' : String(falsy);
     62 
     63  promise_test(() => {
     64 
     65    const rs = recordingReadableStream({
     66      start(controller) {
     67        controller.close();
     68      }
     69    });
     70 
     71    const ws = recordingWritableStream();
     72 
     73    return rs.pipeTo(ws, { preventClose: falsy }).then(value => {
     74      assert_equals(value, undefined, 'the promise must fulfill with undefined');
     75    })
     76    .then(() => {
     77      assert_array_equals(rs.events, []);
     78      assert_array_equals(ws.events, ['close']);
     79 
     80      return Promise.all([
     81        rs.getReader().closed,
     82        ws.getWriter().closed
     83      ]);
     84    });
     85 
     86  }, `Closing must be propagated forward: starts closed; preventClose = ${stringVersion} (falsy); fulfilled close ` +
     87     `promise`);
     88 }
     89 
     90 for (const truthy of [true, 'a', 1, Symbol(), { }]) {
     91  promise_test(() => {
     92 
     93    const rs = recordingReadableStream({
     94      start(controller) {
     95        controller.close();
     96      }
     97    });
     98 
     99    const ws = recordingWritableStream();
    100 
    101    return rs.pipeTo(ws, { preventClose: truthy }).then(value => {
    102      assert_equals(value, undefined, 'the promise must fulfill with undefined');
    103    })
    104    .then(() => {
    105      assert_array_equals(rs.events, []);
    106      assert_array_equals(ws.events, []);
    107 
    108      return rs.getReader().closed;
    109    });
    110 
    111  }, `Closing must be propagated forward: starts closed; preventClose = ${String(truthy)} (truthy)`);
    112 }
    113 
    114 promise_test(() => {
    115 
    116  const rs = recordingReadableStream({
    117    start(controller) {
    118      controller.close();
    119    }
    120  });
    121 
    122  const ws = recordingWritableStream();
    123 
    124  return rs.pipeTo(ws, { preventClose: true, preventAbort: true }).then(value => {
    125    assert_equals(value, undefined, 'the promise must fulfill with undefined');
    126  })
    127  .then(() => {
    128    assert_array_equals(rs.events, []);
    129    assert_array_equals(ws.events, []);
    130 
    131    return rs.getReader().closed;
    132  });
    133 
    134 }, 'Closing must be propagated forward: starts closed; preventClose = true, preventAbort = true');
    135 
    136 promise_test(() => {
    137 
    138  const rs = recordingReadableStream({
    139    start(controller) {
    140      controller.close();
    141    }
    142  });
    143 
    144  const ws = recordingWritableStream();
    145 
    146  return rs.pipeTo(ws, { preventClose: true, preventAbort: true, preventCancel: true }).then(value => {
    147    assert_equals(value, undefined, 'the promise must fulfill with undefined');
    148  })
    149  .then(() => {
    150    assert_array_equals(rs.events, []);
    151    assert_array_equals(ws.events, []);
    152 
    153    return rs.getReader().closed;
    154  });
    155 
    156 }, 'Closing must be propagated forward: starts closed; preventClose = true, preventAbort = true, preventCancel = true');
    157 
    158 promise_test(t => {
    159 
    160  const rs = recordingReadableStream();
    161 
    162  const ws = recordingWritableStream();
    163 
    164  const pipePromise = rs.pipeTo(ws);
    165 
    166  t.step_timeout(() => rs.controller.close());
    167 
    168  return pipePromise.then(value => {
    169    assert_equals(value, undefined, 'the promise must fulfill with undefined');
    170  })
    171  .then(() => {
    172    assert_array_equals(rs.eventsWithoutPulls, []);
    173    assert_array_equals(ws.events, ['close']);
    174 
    175    return Promise.all([
    176      rs.getReader().closed,
    177      ws.getWriter().closed
    178    ]);
    179  });
    180 
    181 }, 'Closing must be propagated forward: becomes closed asynchronously; preventClose omitted; fulfilled close promise');
    182 
    183 promise_test(t => {
    184 
    185  const rs = recordingReadableStream();
    186 
    187  const ws = recordingWritableStream({
    188    close() {
    189      throw error1;
    190    }
    191  });
    192 
    193  const pipePromise = promise_rejects_exactly(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
    194 
    195  t.step_timeout(() => rs.controller.close());
    196 
    197  return pipePromise.then(() => {
    198    assert_array_equals(rs.eventsWithoutPulls, []);
    199    assert_array_equals(ws.events, ['close']);
    200 
    201    return Promise.all([
    202      rs.getReader().closed,
    203      promise_rejects_exactly(t, error1, ws.getWriter().closed)
    204    ]);
    205  });
    206 
    207 }, 'Closing must be propagated forward: becomes closed asynchronously; preventClose omitted; rejected close promise');
    208 
    209 promise_test(t => {
    210 
    211  const rs = recordingReadableStream();
    212 
    213  const ws = recordingWritableStream();
    214 
    215  const pipePromise = rs.pipeTo(ws, { preventClose: true });
    216 
    217  t.step_timeout(() => rs.controller.close());
    218 
    219  return pipePromise.then(value => {
    220    assert_equals(value, undefined, 'the promise must fulfill with undefined');
    221  })
    222  .then(() => {
    223    assert_array_equals(rs.eventsWithoutPulls, []);
    224    assert_array_equals(ws.events, []);
    225 
    226    return rs.getReader().closed;
    227  });
    228 
    229 }, 'Closing must be propagated forward: becomes closed asynchronously; preventClose = true');
    230 
    231 promise_test(t => {
    232 
    233  const rs = recordingReadableStream();
    234 
    235  const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
    236 
    237  const pipePromise = rs.pipeTo(ws);
    238 
    239  t.step_timeout(() => rs.controller.close());
    240 
    241  return pipePromise.then(value => {
    242    assert_equals(value, undefined, 'the promise must fulfill with undefined');
    243  })
    244  .then(() => {
    245    assert_array_equals(rs.eventsWithoutPulls, []);
    246    assert_array_equals(ws.events, ['close']);
    247 
    248    return Promise.all([
    249      rs.getReader().closed,
    250      ws.getWriter().closed
    251    ]);
    252  });
    253 
    254 }, 'Closing must be propagated forward: becomes closed asynchronously; dest never desires chunks; ' +
    255   'preventClose omitted; fulfilled close promise');
    256 
    257 promise_test(t => {
    258 
    259  const rs = recordingReadableStream();
    260 
    261  const ws = recordingWritableStream({
    262    close() {
    263      throw error1;
    264    }
    265  }, new CountQueuingStrategy({ highWaterMark: 0 }));
    266 
    267  const pipePromise = promise_rejects_exactly(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
    268 
    269  t.step_timeout(() => rs.controller.close());
    270 
    271  return pipePromise.then(() => {
    272    assert_array_equals(rs.eventsWithoutPulls, []);
    273    assert_array_equals(ws.events, ['close']);
    274 
    275    return Promise.all([
    276      rs.getReader().closed,
    277      promise_rejects_exactly(t, error1, ws.getWriter().closed)
    278    ]);
    279  });
    280 
    281 }, 'Closing must be propagated forward: becomes closed asynchronously; dest never desires chunks; ' +
    282   'preventClose omitted; rejected close promise');
    283 
    284 promise_test(t => {
    285 
    286  const rs = recordingReadableStream();
    287 
    288  const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
    289 
    290  const pipePromise = rs.pipeTo(ws, { preventClose: true });
    291 
    292  t.step_timeout(() => rs.controller.close());
    293 
    294  return pipePromise.then(value => {
    295    assert_equals(value, undefined, 'the promise must fulfill with undefined');
    296  })
    297  .then(() => {
    298    assert_array_equals(rs.eventsWithoutPulls, []);
    299    assert_array_equals(ws.events, []);
    300 
    301    return rs.getReader().closed;
    302  });
    303 
    304 }, 'Closing must be propagated forward: becomes closed asynchronously; dest never desires chunks; ' +
    305   'preventClose = true');
    306 
    307 promise_test(t => {
    308 
    309  const rs = recordingReadableStream();
    310 
    311  const ws = recordingWritableStream();
    312 
    313  const pipePromise = rs.pipeTo(ws);
    314 
    315  t.step_timeout(() => {
    316    rs.controller.enqueue('Hello');
    317    t.step_timeout(() => rs.controller.close());
    318  }, 10);
    319 
    320  return pipePromise.then(value => {
    321    assert_equals(value, undefined, 'the promise must fulfill with undefined');
    322  })
    323  .then(() => {
    324    assert_array_equals(rs.eventsWithoutPulls, []);
    325    assert_array_equals(ws.events, ['write', 'Hello', 'close']);
    326 
    327    return Promise.all([
    328      rs.getReader().closed,
    329      ws.getWriter().closed
    330    ]);
    331  });
    332 
    333 }, 'Closing must be propagated forward: becomes closed after one chunk; preventClose omitted; fulfilled close promise');
    334 
    335 promise_test(t => {
    336 
    337  const rs = recordingReadableStream();
    338 
    339  const ws = recordingWritableStream({
    340    close() {
    341      throw error1;
    342    }
    343  });
    344 
    345  const pipePromise = promise_rejects_exactly(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
    346 
    347  t.step_timeout(() => {
    348    rs.controller.enqueue('Hello');
    349    t.step_timeout(() => rs.controller.close());
    350  }, 10);
    351 
    352  return pipePromise.then(() => {
    353    assert_array_equals(rs.eventsWithoutPulls, []);
    354    assert_array_equals(ws.events, ['write', 'Hello', 'close']);
    355 
    356    return Promise.all([
    357      rs.getReader().closed,
    358      promise_rejects_exactly(t, error1, ws.getWriter().closed)
    359    ]);
    360  });
    361 
    362 }, 'Closing must be propagated forward: becomes closed after one chunk; preventClose omitted; rejected close promise');
    363 
    364 promise_test(t => {
    365 
    366  const rs = recordingReadableStream();
    367 
    368  const ws = recordingWritableStream();
    369 
    370  const pipePromise = rs.pipeTo(ws, { preventClose: true });
    371 
    372  t.step_timeout(() => {
    373    rs.controller.enqueue('Hello');
    374    t.step_timeout(() => rs.controller.close());
    375  }, 10);
    376 
    377  return pipePromise.then(value => {
    378    assert_equals(value, undefined, 'the promise must fulfill with undefined');
    379  })
    380  .then(() => {
    381    assert_array_equals(rs.eventsWithoutPulls, []);
    382    assert_array_equals(ws.events, ['write', 'Hello']);
    383 
    384    return rs.getReader().closed;
    385  });
    386 
    387 }, 'Closing must be propagated forward: becomes closed after one chunk; preventClose = true');
    388 
    389 promise_test(() => {
    390 
    391  const rs = recordingReadableStream();
    392 
    393  let resolveWritePromise;
    394  const ws = recordingWritableStream({
    395    write() {
    396      return new Promise(resolve => {
    397        resolveWritePromise = resolve;
    398      });
    399    }
    400  });
    401 
    402  let pipeComplete = false;
    403  const pipePromise = rs.pipeTo(ws).then(() => {
    404    pipeComplete = true;
    405  });
    406 
    407  rs.controller.enqueue('a');
    408  rs.controller.close();
    409 
    410  // Flush async events and verify that no shutdown occurs.
    411  return flushAsyncEvents().then(() => {
    412    assert_array_equals(ws.events, ['write', 'a']); // no 'close'
    413    assert_equals(pipeComplete, false, 'the pipe must not be complete');
    414 
    415    resolveWritePromise();
    416 
    417    return pipePromise.then(() => {
    418      assert_array_equals(ws.events, ['write', 'a', 'close']);
    419    });
    420  });
    421 
    422 }, 'Closing must be propagated forward: shutdown must not occur until the final write completes');
    423 
    424 promise_test(() => {
    425 
    426  const rs = recordingReadableStream();
    427 
    428  let resolveWritePromise;
    429  const ws = recordingWritableStream({
    430    write() {
    431      return new Promise(resolve => {
    432        resolveWritePromise = resolve;
    433      });
    434    }
    435  });
    436 
    437  let pipeComplete = false;
    438  const pipePromise = rs.pipeTo(ws, { preventClose: true }).then(() => {
    439    pipeComplete = true;
    440  });
    441 
    442  rs.controller.enqueue('a');
    443  rs.controller.close();
    444 
    445  // Flush async events and verify that no shutdown occurs.
    446  return flushAsyncEvents().then(() => {
    447    assert_array_equals(ws.events, ['write', 'a'],
    448      'the chunk must have been written, but close must not have happened');
    449    assert_equals(pipeComplete, false, 'the pipe must not be complete');
    450 
    451    resolveWritePromise();
    452 
    453    return pipePromise;
    454  }).then(() => flushAsyncEvents()).then(() => {
    455    assert_array_equals(ws.events, ['write', 'a'],
    456      'the chunk must have been written, but close must not have happened');
    457  });
    458 
    459 }, 'Closing must be propagated forward: shutdown must not occur until the final write completes; preventClose = true');
    460 
    461 promise_test(() => {
    462 
    463  const rs = recordingReadableStream();
    464 
    465  let resolveWriteCalled;
    466  const writeCalledPromise = new Promise(resolve => {
    467    resolveWriteCalled = resolve;
    468  });
    469 
    470  let resolveWritePromise;
    471  const ws = recordingWritableStream({
    472    write() {
    473      resolveWriteCalled();
    474 
    475      return new Promise(resolve => {
    476        resolveWritePromise = resolve;
    477      });
    478    }
    479  }, new CountQueuingStrategy({ highWaterMark: 2 }));
    480 
    481  let pipeComplete = false;
    482  const pipePromise = rs.pipeTo(ws).then(() => {
    483    pipeComplete = true;
    484  });
    485 
    486  rs.controller.enqueue('a');
    487  rs.controller.enqueue('b');
    488 
    489  return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
    490    assert_array_equals(ws.events, ['write', 'a'],
    491      'the first chunk must have been written, but close must not have happened yet');
    492    assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
    493 
    494    rs.controller.close();
    495    resolveWritePromise();
    496  }).then(() => flushAsyncEvents()).then(() => {
    497    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
    498      'the second chunk must have been written, but close must not have happened yet');
    499    assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
    500 
    501    resolveWritePromise();
    502    return pipePromise;
    503  }).then(() => {
    504    assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'close'],
    505      'all chunks must have been written and close must have happened');
    506  });
    507 
    508 }, 'Closing must be propagated forward: shutdown must not occur until the final write completes; becomes closed after first write');
    509 
    510 promise_test(() => {
    511 
    512  const rs = recordingReadableStream();
    513 
    514  let resolveWriteCalled;
    515  const writeCalledPromise = new Promise(resolve => {
    516    resolveWriteCalled = resolve;
    517  });
    518 
    519  let resolveWritePromise;
    520  const ws = recordingWritableStream({
    521    write() {
    522      resolveWriteCalled();
    523 
    524      return new Promise(resolve => {
    525        resolveWritePromise = resolve;
    526      });
    527    }
    528  }, new CountQueuingStrategy({ highWaterMark: 2 }));
    529 
    530  let pipeComplete = false;
    531  const pipePromise = rs.pipeTo(ws, { preventClose: true }).then(() => {
    532    pipeComplete = true;
    533  });
    534 
    535  rs.controller.enqueue('a');
    536  rs.controller.enqueue('b');
    537 
    538  return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
    539    assert_array_equals(ws.events, ['write', 'a'],
    540      'the first chunk must have been written, but close must not have happened');
    541    assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
    542 
    543    rs.controller.close();
    544    resolveWritePromise();
    545  }).then(() => flushAsyncEvents()).then(() => {
    546    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
    547      'the second chunk must have been written, but close must not have happened');
    548    assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
    549 
    550    resolveWritePromise();
    551    return pipePromise;
    552  }).then(() => flushAsyncEvents()).then(() => {
    553    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
    554      'all chunks must have been written, but close must not have happened');
    555  });
    556 
    557 }, 'Closing must be propagated forward: shutdown must not occur until the final write completes; becomes closed after first write; preventClose = true');
    558 
    559 
    560 promise_test(t => {
    561  const rs = recordingReadableStream({
    562    start(c) {
    563      c.enqueue('a');
    564      c.enqueue('b');
    565      c.close();
    566    }
    567  });
    568  let rejectWritePromise;
    569  const ws = recordingWritableStream({
    570    write() {
    571      return new Promise((resolve, reject) => {
    572        rejectWritePromise = reject;
    573      });
    574    }
    575  }, { highWaterMark: 3 });
    576  const pipeToPromise = rs.pipeTo(ws);
    577  return delay(0).then(() => {
    578    rejectWritePromise(error1);
    579    return promise_rejects_exactly(t, error1, pipeToPromise, 'pipeTo should reject');
    580  }).then(() => {
    581    assert_array_equals(rs.events, []);
    582    assert_array_equals(ws.events, ['write', 'a']);
    583 
    584    return Promise.all([
    585      rs.getReader().closed,
    586      promise_rejects_exactly(t, error1, ws.getWriter().closed, 'ws should be errored')
    587    ]);
    588  });
    589 }, 'Closing must be propagated forward: erroring the writable while flushing pending writes should error pipeTo');