UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

145 lines (111 loc) 3.71 kB
import _ from 'lodash' import debugModule from 'debug' import stream from 'stream' const debug = debugModule('cypress:server:stream_buffer') function streamBuffer (initialSize = 2048) { let buffer: Buffer | null = Buffer.allocUnsafe(initialSize) let bytesWritten = 0 let finished = false const onWrite = (chunk, enc, cb) => { if (finished || !chunk || !buffer) { debug('received write after deleting buffer, ignoring %o', { chunkLength: chunk && chunk.length, enc }) return cb() } if (chunk.length + bytesWritten > buffer.length) { let newBufferLength = buffer.length while (newBufferLength < chunk.length + bytesWritten) { newBufferLength *= 2 } debug('extending buffer to accomodate new chunk', { bufferLength: buffer.length, newBufferLength, }) const newBuffer = Buffer.allocUnsafe(newBufferLength) buffer.copy(newBuffer) buffer = newBuffer } debug('appending chunk to buffer %o', { bytesWritten, chunkLength: chunk.length }) bytesWritten += chunk.copy(buffer, bytesWritten) // emit in case there are readers waiting writeable.emit('chunk', chunk) cb() } const onFinal = (cb) => { debug('stream buffer writeable final called') finished = true cb() } class StreamBuffer extends stream.Writable { private readers: stream.Readable[] = [] public createReadStream () { let bytesRead = 0 const readerId = _.uniqueId('reader') const onRead = (size) => { if (!buffer) { debug('read requested after unpipeAll, ignoring %o', { size }) return } // if there are unread bytes in the buffer, // send up to bytesWritten back if (bytesRead < bytesWritten) { const chunkLength = bytesWritten - bytesRead const bytes = buffer.slice(bytesRead, bytesRead + chunkLength) const bytesLength = bytes.length debug('reading unread bytes from buffer %o', { readerId, bytesRead, bytesWritten, chunkLength, readChunkLength: bytesLength, }) bytesRead += bytesLength // if we can still push more bytes into // the buffer then do it if (readable.push(bytes)) { return onRead(size) } } // if it's finished and there are no unread bytes, EOF if (finished) { // cleanup listeners that were added writeable.removeListener('chunk', onRead) writeable.removeListener('finish', onRead) debug('buffered stream EOF %o', { readerId }) return readable.push(null) } // if we're not finished we may end up writing // more data - or we may end writeable.removeListener('chunk', onRead) writeable.once('chunk', onRead) // if the writeable stream buffer isn't finished // yet - then read() will not be called again, // so we restart reading when its finished writeable.removeListener('finish', onRead) writeable.once('finish', onRead) } const readable = new stream.Readable({ read: onRead, // @ts-ignore autoDestroy: true, }) this.readers.push(readable) return readable } unpipeAll () { buffer = null // aggressive GC _.invokeMap(this.readers, 'unpipe') } _buffer () { return buffer } _finished () { return finished } } const writeable = new StreamBuffer({ write: onWrite, final: onFinal, // @ts-ignore autoDestroy: true, }) return writeable } module.exports = { streamBuffer, }