Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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
119 changes: 64 additions & 55 deletions scripts/emulator-testing/emulators/emulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,64 +53,73 @@ 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;
let buf = new Uint8Array(2 ** 26);
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 +193,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