Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion scripts/emulator-testing/dataconnect-test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -38,7 +39,7 @@ async function run(): Promise<void> {
await emulator.setUp();
await runTest(emulator.port);
} finally {
await emulator.tearDown();
emulator.tearDown();
}
}
run().catch(err => {
Expand Down
2 changes: 0 additions & 2 deletions scripts/emulator-testing/emulators/dataconnect-emulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand Down
124 changes: 69 additions & 55 deletions scripts/emulator-testing/emulators/emulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>((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<void>(
(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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a couple of other ways around this, I think, not 100% sure. One is to set like const self = this at the top level (outside of the fetch anyway) and then you call self.binaryPath when needed. That looks weird so another better looking option is that you create a function at the top level called setBinaryPath that is just like

const setBinaryPath = (binaryPath) => this.binaryPath = binaryPath;

And then I think you can call setBinaryPath inside readChunk. I'm not 100% sure but I think they should work? If they don't, never mind.

* `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<void>(
(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));
});
}

Expand Down Expand Up @@ -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);
}
}
Expand Down
Loading