[go: nahoru, domu]

Skip to content

Commit

Permalink
revamp triggers end-to-end functional test (firebase#2178)
Browse files Browse the repository at this point in the history
* rewrite triggers-end-to-end tests in new style

* a little cleanup

* use FBTOOLS_TARGET_PROJECT

* link the CLI before running tests

* fixing up tests

* remove only

* linting cleanup

* save process for more safety

* add better handling for starting with a custom function
  • Loading branch information
bkendall committed Apr 27, 2020
1 parent a6ee068 commit fd199e3
Show file tree
Hide file tree
Showing 9 changed files with 520 additions and 547 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/node-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ jobs:
- 8.x
script:
- npm run test:hosting
- ./scripts/test-triggers-end-to-end.sh
- npm run test:client-integration
- npm run test:extensions-emulator
- npm run test:triggers-end-to-end
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
"prepare": "npm run clean && npm run build -- --build tsconfig.publish.json",
"test": "npm run lint:quiet && npm run mocha",
"test:client-integration": "./scripts/client-integration-tests/run.sh",
"test:extensions-emulator": "./scripts/extensions-emulator-tests/run.sh",
"test:hosting": "./scripts/hosting-tests/run.sh",
"test:extensions-emulator": "./scripts/extensions-emulator-tests/run.sh"
"test:triggers-end-to-end": "./scripts/triggers-end-to-end-tests/run.sh"
},
"files": [
"lib",
Expand Down
19 changes: 0 additions & 19 deletions scripts/test-triggers-end-to-end.sh

This file was deleted.

79 changes: 79 additions & 0 deletions scripts/triggers-end-to-end-tests/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as subprocess from "child_process";

export class CLIProcess {
process?: subprocess.ChildProcess;

constructor(private readonly name: string, private readonly workdir: string) {}

start(
cmd: string,
project: string,
additionalArgs: string[],
logDoneFn?: (d: unknown) => unknown
): Promise<void> {
const args = [cmd, "--project", project];

if (additionalArgs) {
args.push(...additionalArgs);
}

const p = subprocess.spawn("firebase", args, { cwd: this.workdir });
if (!p) {
throw new Error("Failed to start firebase CLI");
}
this.process = p;

this.process.stdout.on("data", (data: unknown) => {
process.stdout.write(`[${this.name} stdout] ` + data);
});

this.process.stderr.on("data", (data: unknown) => {
console.log(`[${this.name} stderr] ` + data);
});

let started: Promise<void>;
if (logDoneFn) {
started = new Promise((resolve, reject) => {
const customCallback = (data: unknown): void => {
if (logDoneFn(data)) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
p.stdout.removeListener("close", customFailure);
resolve();
}
};
const customFailure = (): void => {
p.stdout.removeListener("data", customCallback);
reject(new Error("failed to resolve startup before process.stdout closed"));
};
p.stdout.on("data", customCallback);
p.stdout.on("close", customFailure);
});
} else {
started = new Promise((resolve) => {
p.once("close", () => {
this.process = undefined;
resolve();
});
});
}

return started;
}

stop(): Promise<void> {
const p = this.process;
if (!p) {
return Promise.resolve();
}

const stopped = new Promise((resolve) => {
p.once("exit", (/* exitCode, signal */) => {
this.process = undefined;
resolve();
});
}).then(() => undefined); // Fixes return type.

p.kill("SIGINT");
return stopped;
}
}
155 changes: 155 additions & 0 deletions scripts/triggers-end-to-end-tests/framework.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import * as request from "request";

import { CLIProcess } from "./cli";

const FIREBASE_PROJECT_ZONE = "us-central1";

/*
* Markers this test looks for in the emulator process stdout
* as one test for whether a cloud function was triggered.
*/
const RTDB_FUNCTION_LOG = "========== RTDB FUNCTION ==========";
const FIRESTORE_FUNCTION_LOG = "========== FIRESTORE FUNCTION ==========";
const PUBSUB_FUNCTION_LOG = "========== PUBSUB FUNCTION ==========";
const ALL_EMULATORS_STARTED_LOG = "All emulators started, it is now safe to connect.";

interface ConnectionInfo {
host: string;
port: number;
}

export interface FrameworkOptions {
emulators: {
database: ConnectionInfo;
firestore: ConnectionInfo;
functions: ConnectionInfo;
pubsub: ConnectionInfo;
};
}

export class TriggerEndToEndTest {
rtdbEmulatorHost = "localhost";
rtdbEmulatorPort: number;
firestoreEmulatorHost = "localhost";
firestoreEmulatorPort: number;
functionsEmulatorHost = "localhost";
functionsEmulatorPort: number;
pubsubEmulatorHost = "localhost";
pubsubEmulatorPort: number;
allEmulatorsStarted = false;
rtdbTriggerCount = 0;
firestoreTriggerCount = 0;
pubsubTriggerCount = 0;
rtdbFromFirestore = false;
firestoreFromRtdb = false;
rtdbFromRtdb = false;
firestoreFromFirestore = false;
cliProcess?: CLIProcess;

constructor(public project: string, private readonly workdir: string, config: FrameworkOptions) {
this.rtdbEmulatorPort = config.emulators.database.port;
this.firestoreEmulatorPort = config.emulators.firestore.port;
this.functionsEmulatorPort = config.emulators.functions.port;
this.pubsubEmulatorPort = config.emulators.pubsub.port;
}

/*
* Check that all directions of database <-> functions <-> firestore
* worked.
*/
success(): boolean {
return (
this.rtdbFromFirestore &&
this.rtdbFromRtdb &&
this.firestoreFromFirestore &&
this.firestoreFromRtdb
);
}

startEmulators(additionalArgs: string[] = []): Promise<void> {
const cli = new CLIProcess("default", this.workdir);
const started = cli.start("emulators:start", this.project, additionalArgs, (data: unknown) => {
if (typeof data != "string" && !Buffer.isBuffer(data)) {
throw new Error(`data is not a string or buffer (${typeof data})`);
}
return data.includes(ALL_EMULATORS_STARTED_LOG);
});

cli.process?.stdout.on("data", (data) => {
if (data.includes(RTDB_FUNCTION_LOG)) {
this.rtdbTriggerCount++;
}
if (data.includes(FIRESTORE_FUNCTION_LOG)) {
this.firestoreTriggerCount++;
}
if (data.includes(PUBSUB_FUNCTION_LOG)) {
this.pubsubTriggerCount++;
}
});

this.cliProcess = cli;
return started;
}

startEmulatorsAndWait(additionalArgs: string[], done: (_: unknown) => void): void {
this.startEmulators(additionalArgs).then(done);
}

stopEmulators(): Promise<void> {
return this.cliProcess ? this.cliProcess.stop() : Promise.resolve();
}

invokeHttpFunction(name: string): Promise<request.Response> {
return new Promise((resolve, reject) => {
const url = `http://localhost:${[
this.functionsEmulatorPort,
this.project,
FIREBASE_PROJECT_ZONE,
name,
].join("/")}`;

console.log(`URL: ${url}`);
const req = request.get(url);
req.once("response", resolve);
req.once("error", reject);
});
}

writeToRtdb(): Promise<request.Response> {
return this.invokeHttpFunction("writeToRtdb");
}

writeToFirestore(): Promise<request.Response> {
return this.invokeHttpFunction("writeToFirestore");
}

writeToPubsub(): Promise<request.Response> {
return this.invokeHttpFunction("writeToPubsub");
}

writeToScheduledPubsub(): Promise<request.Response> {
return this.invokeHttpFunction("writeToScheduledPubsub");
}

waitForCondition(
conditionFn: () => boolean,
timeout: number,
callback: (err?: Error) => void
): void {
let elapsed = 0;
const interval = 10;
const id = setInterval(() => {
elapsed += interval;
if (elapsed > timeout) {
clearInterval(id);
callback(new Error(`Timed out waiting for condition: ${conditionFn.toString()}}`));
return;
}

if (conditionFn()) {
clearInterval(id);
callback();
}
}, interval);
}
}
16 changes: 0 additions & 16 deletions scripts/triggers-end-to-end-tests/package.json

This file was deleted.

12 changes: 12 additions & 0 deletions scripts/triggers-end-to-end-tests/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash

source scripts/set-default-credentials.sh

npm link

mocha \
--require ts-node/register \
--require source-map-support/register \
--require src/test/helpers/mocha-bootstrap.js \
--exit \
scripts/triggers-end-to-end-tests/tests.ts
Loading

0 comments on commit fd199e3

Please sign in to comment.