diff --git a/scripts/emulator-testing/dataconnect-test-runner.ts b/scripts/emulator-testing/dataconnect-test-runner.ts index e362ef59cbe..d68dbb85f04 100644 --- a/scripts/emulator-testing/dataconnect-test-runner.ts +++ b/scripts/emulator-testing/dataconnect-test-runner.ts @@ -18,6 +18,7 @@ import { DataConnectEmulator } from './emulators/dataconnect-emulator'; import { spawn } from 'child-process-promise'; import * as path from 'path'; + function runTest(port: number) { console.log( 'path: ' + path.resolve(__dirname, '../../packages/data-connect') @@ -38,7 +39,7 @@ async function run(): Promise { await emulator.setUp(); await runTest(emulator.port); } finally { - await emulator.tearDown(); + emulator.tearDown(); } } run().catch(err => { diff --git a/scripts/emulator-testing/emulators/dataconnect-emulator.ts b/scripts/emulator-testing/emulators/dataconnect-emulator.ts index efe5bdbe52c..d69677f1d1b 100644 --- a/scripts/emulator-testing/emulators/dataconnect-emulator.ts +++ b/scripts/emulator-testing/emulators/dataconnect-emulator.ts @@ -21,8 +21,6 @@ import { Emulator } from './emulator'; const DATABASE_EMULATOR_VERSION = '1.3.7'; export class DataConnectEmulator extends Emulator { - // namespace: string; - constructor(port = 3628) { const os = platform(); let urlString = ''; diff --git a/scripts/emulator-testing/emulators/emulator.ts b/scripts/emulator-testing/emulators/emulator.ts index 85f190bcba9..a58324c50d0 100644 --- a/scripts/emulator-testing/emulators/emulator.ts +++ b/scripts/emulator-testing/emulators/emulator.ts @@ -53,64 +53,78 @@ export abstract class Emulator { return Promise.resolve(); } + console.log(`Downloading emulator from [${this.binaryUrl}] ...`); + const { name: tempDir } = tmp.dirSync({ unsafeCleanup: true }); + const filepath = path.resolve(tempDir, this.binaryName); + let cur = 0; + // The emulators vary in size and are approximately 32MiB. If we define this array to be + // only 32MiB, there's a risk in the future that the download will suddenly fail because there + // was an increase in emulator size, and the buffer is no longer large enough. + // To prevent this, we give some room for an increase in size of the emulators + // and make the buffer 64MiB. + let buf = new Uint8Array(2 ** 26); // 64 MiB return new Promise((resolve, reject) => { - tmp.dir((err: Error | null, dir: string) => { - if (err) reject(err); - console.log(`Created temporary directory at [${dir}].`); - const filepath: string = path.resolve(dir, this.binaryName); - const writer = fs.createWriteStream(filepath); - console.log(`Downloading emulator from [${this.binaryUrl}] ...`); - // Map the DOM's fetch Reader to node's streaming file system - // operations. We will need to access class members `binaryPath` and `copyToCache` after the - // download completes. It's a compilation error to pass `this` into the named function - // `readChunk`, so the download operation is wrapped in a promise that we wait upon. - const downloadPromise = new Promise( - (downloadComplete, downloadFailed) => { - fetch(this.binaryUrl) - .then(resp => { - if (resp.status !== 200 || resp.body === null) { - console.log('Download of emulator failed: ', resp.statusText); - downloadFailed(); - } else { - const reader = resp.body.getReader(); - reader.read().then(function readChunk({ done, value }): any { - if (done) { - downloadComplete(); - } else { - writer.write(value); - return reader.read().then(readChunk); - } - }); + /** + * Once the download is `done` in `readChunk`, we want to set `this.binaryPath` to the path of the + * downloaded emulator. Unfortunately, we can't access `this` when inside `readChunk`'s scope, since + * it's a named function expression, and does not inherit the `this` object from it's parent. + * To work around this, we wrap the fetch in a promise, + * then once it's resolved we can access `this` in a callback arrow function that *does* inherit + * `this` from the parent object, allowing us to set `this.binaryPath`. + * Note that we can't make readChunk an arrow function, since it needs to be named so that we can + * perform recursion to read the next chunk. + */ + const downloadPromise = new Promise( + (downloadComplete, downloadFailed) => { + fetch(this.binaryUrl) + .then(resp => { + if (!resp.ok || resp.body === null) { + return downloadFailed( + `Failed to download emulator: [${resp.status}] ${resp.statusText}` + ); + } + + const reader = resp.body.getReader(); + reader.read().then(function readChunk({ done, value }): any { + if (done) { + return downloadComplete(); + } + + if (!value) { + return downloadFailed( + 'Did not receive chunk in response body' + ); } - }) - .catch(e => { - console.log(`Download of emulator failed: ${e}`); - downloadFailed(); + + buf.set(value, cur); + cur += value.length; + return reader.read().then(readChunk); }); - } - ); + }) + .catch(err => downloadFailed(err)); + } + ); + + downloadPromise + .then(() => { + console.log('Download complete.'); + fs.writeFileSync(filepath, buf.slice(0, cur)); + fs.chmod(filepath, 0o755, err => { + if (err) { + return reject(err); + } - downloadPromise.then( - () => { - console.log('Download complete'); - // Change emulator binary file permission to 'rwxr-xr-x'. - // The execute permission is required for it to be able to start - // with 'java -jar'. - fs.chmod(filepath, 0o755, err => { - if (err) reject(err); - console.log(`Changed emulator file permissions to 'rwxr-xr-x'.`); - this.binaryPath = filepath; - if (this.copyToCache()) { - console.log(`Cached emulator at ${this.cacheBinaryPath}`); - } - resolve(); - }); - }, - () => { - reject(); - } - ); - }); + console.log(`Changed emulator file permissions to 'rwxr-xr-x'.`); + // Since we are now in an arrow function, `this` is inherited from the `download()` method, so it is the Emulator object + this.binaryPath = filepath; + if (this.copyToCache()) { + console.log(`Cached emulator at ${this.cacheBinaryPath}`); + } + + resolve(); + }); + }) + .catch(err => reject(err)); }); } @@ -184,7 +198,7 @@ export abstract class Emulator { } if (this.binaryPath) { - console.log(`Deleting the emulator jar at ${this.binaryPath}`); + console.log(`Deleting the emulator at ${this.binaryPath}`); fs.unlinkSync(this.binaryPath); } }