Skip to content

Commit ed3fd6e

Browse files
authored
Import/export support for emulators (#1968)
1 parent b18ffad commit ed3fd6e

21 files changed

+603
-87
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* Adds import/export support to `emulators` commands for Firestore (#1167).

package-lock.json

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
"plist": "^3.0.1",
9898
"portfinder": "^1.0.23",
9999
"progress": "^2.0.3",
100+
"rimraf": "^3.0.0",
100101
"request": "^2.87.0",
101102
"semver": "^5.7.1",
102103
"superstatic": "^6.0.1",
@@ -129,6 +130,7 @@
129130
"@types/plist": "^3.0.1",
130131
"@types/progress": "^2.0.3",
131132
"@types/request": "^2.48.1",
133+
"@types/rimraf": "^2.0.3",
132134
"@types/semver": "^6.0.0",
133135
"@types/sinon": "^5.0.5",
134136
"@types/sinon-chai": "^3.2.2",
@@ -156,7 +158,6 @@
156158
"nock": "^9.3.3",
157159
"nyc": "^14.0.0",
158160
"prettier": "1.14.3",
159-
"rimraf": "^3.0.0",
160161
"sinon": "^6.3.4",
161162
"sinon-chai": "^3.2.0",
162163
"source-map-support": "^0.5.9",

scripts/triggers-end-to-end-tests/.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ database-debug.log*
99
firestore-debug.log*
1010
pubsub-debug.log*
1111

12+
# NPM
13+
package-lock.json
14+
1215
# Firebase cache
1316
.firebase/
1417

@@ -65,4 +68,4 @@ node_modules/
6568
.yarn-integrity
6669

6770
# dotenv environment variables file
68-
.env
71+
.env

scripts/triggers-end-to-end-tests/firebase.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
},
66
"functions": {},
77
"emulators": {
8+
"hub": {
9+
"port": 4000
10+
},
811
"database": {
912
"port": 9000
1013
},

scripts/triggers-end-to-end-tests/run.spec.js

Lines changed: 112 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const chai = require("chai");
66
const expect = chai.expect;
77
const assert = chai.assert;
88
const fs = require("fs");
9+
const os = require("os");
910

1011
const Firestore = require("@google-cloud/firestore");
1112

@@ -32,7 +33,6 @@ const ALL_EMULATORS_STARTED_LOG = "All emulators started, it is now safe to conn
3233
* parallel emulator subprocesses.
3334
*/
3435
const TEST_SETUP_TIMEOUT = 60000;
35-
const EMULATORS_STARTUP_DELAY_TIMEOUT = 60000;
3636
const EMULATORS_WRITE_DELAY_MS = 5000;
3737
const EMULATORS_SHUTDOWN_DELAY_MS = 5000;
3838
const EMULATOR_TEST_TIMEOUT = EMULATORS_WRITE_DELAY_MS * 2;
@@ -44,6 +44,66 @@ const EMULATOR_TEST_TIMEOUT = EMULATORS_WRITE_DELAY_MS * 2;
4444
const FIRESTORE_COMPLETION_MARKER = "test/done_from_firestore";
4545
const DATABASE_COMPLETION_MARKER = "test/done_from_database";
4646

47+
function CLIProcess(name) {
48+
this.name = name;
49+
this.process = undefined;
50+
}
51+
CLIProcess.prototype.constructor = CLIProcess;
52+
53+
CLIProcess.prototype.start = function(cmd, additionalArgs, logDoneFn) {
54+
const args = [PROJECT_ROOT + "/lib/bin/firebase.js", cmd, "--project", FIREBASE_PROJECT];
55+
56+
if (additionalArgs) {
57+
args.push(...additionalArgs);
58+
}
59+
60+
this.process = subprocess.spawn("node", args);
61+
62+
this.process.stdout.on("data", (data) => {
63+
process.stdout.write(`[${this.name} stdout] ` + data);
64+
});
65+
66+
this.process.stderr.on("data", (data) => {
67+
console.log(`[${this.name} stderr] ` + data);
68+
});
69+
70+
let started;
71+
if (logDoneFn) {
72+
started = new Promise((resolve) => {
73+
this.process.stdout.on("data", (data) => {
74+
if (logDoneFn(data)) {
75+
resolve();
76+
}
77+
});
78+
});
79+
} else {
80+
started = new Promise((resolve) => {
81+
this.process.once("close", () => {
82+
this.process = undefined;
83+
resolve();
84+
});
85+
});
86+
}
87+
88+
return started;
89+
};
90+
91+
CLIProcess.prototype.stop = function() {
92+
if (!this.process) {
93+
return Promise.resolve();
94+
}
95+
96+
const stopped = new Promise((resolve) => {
97+
this.process.once("close", (/* exitCode, signal */) => {
98+
this.process = undefined;
99+
resolve();
100+
});
101+
});
102+
103+
this.process.kill("SIGINT");
104+
return stopped;
105+
};
106+
47107
function TriggerEndToEndTest(config) {
48108
this.rtdb_emulator_host = "localhost";
49109
this.rtdb_emulator_port = config.emulators.database.port;
@@ -69,7 +129,7 @@ function TriggerEndToEndTest(config) {
69129
this.rtdb_from_rtdb = false;
70130
this.firestore_from_firestore = false;
71131

72-
this.emulators_process = null;
132+
this.cli_process = null;
73133
}
74134

75135
/*
@@ -86,50 +146,36 @@ TriggerEndToEndTest.prototype.success = function success() {
86146
};
87147

88148
TriggerEndToEndTest.prototype.startEmulators = function startEmulators(additionalArgs) {
89-
var self = this;
90-
const args = [
91-
PROJECT_ROOT + "/lib/bin/firebase.js",
92-
"emulators:start",
93-
"--project",
94-
FIREBASE_PROJECT,
95-
];
96-
97-
if (additionalArgs) {
98-
args.push(...additionalArgs);
99-
}
100-
101-
self.emulators_process = subprocess.spawn("node", args);
149+
const cli = new CLIProcess("default");
150+
const started = cli.start("emulators:start", additionalArgs, (data) => {
151+
return data.indexOf(ALL_EMULATORS_STARTED_LOG) > -1;
152+
});
102153

103-
self.emulators_process.stdout.on("data", function(data) {
104-
process.stdout.write("[emulators stdout] " + data);
154+
cli.process.stdout.on("data", (data) => {
105155
if (data.indexOf(RTDB_FUNCTION_LOG) > -1) {
106-
self.rtdb_trigger_count++;
156+
this.rtdb_trigger_count++;
107157
}
108158
if (data.indexOf(FIRESTORE_FUNCTION_LOG) > -1) {
109-
self.firestore_trigger_count++;
159+
this.firestore_trigger_count++;
110160
}
111161
if (data.indexOf(PUBSUB_FUNCTION_LOG) > -1) {
112-
self.pubsub_trigger_count++;
113-
}
114-
if (data.indexOf(ALL_EMULATORS_STARTED_LOG) > -1) {
115-
self.all_emulators_started = true;
162+
this.pubsub_trigger_count++;
116163
}
117164
});
118165

119-
self.emulators_process.stderr.on("data", function(data) {
120-
console.log("[emulators stderr] " + data);
121-
});
166+
this.cli_process = cli;
167+
return started;
122168
};
123169

124-
TriggerEndToEndTest.prototype.stopEmulators = function stopEmulators(done) {
125-
this.emulators_process.once("close", function(/* exitCode, signal */) {
126-
done();
127-
});
170+
TriggerEndToEndTest.prototype.startEmulatorsAndWait = function startEmulatorsAndWait(
171+
additionalArgs,
172+
done
173+
) {
174+
this.startEmulators(additionalArgs).then(done);
175+
};
128176

129-
/*
130-
* CLI process only shuts down emulators cleanly on SIGINT.
131-
*/
132-
this.emulators_process.kill("SIGINT");
177+
TriggerEndToEndTest.prototype.stopEmulators = function stopEmulators(done) {
178+
this.cli_process.stop().then(done);
133179
};
134180

135181
TriggerEndToEndTest.prototype.invokeHttpFunction = function invokeHttpFunction(name, done) {
@@ -217,15 +263,7 @@ describe("database and firestore emulator function triggers", function() {
217263
});
218264
},
219265
function(done) {
220-
test.startEmulators(["--only", "functions,database,firestore"]);
221-
test.waitForCondition(
222-
() => test.all_emulators_started,
223-
EMULATORS_STARTUP_DELAY_TIMEOUT,
224-
(err) => {
225-
expect(err).to.be.undefined;
226-
done();
227-
}
228-
);
266+
test.startEmulatorsAndWait(["--only", "functions,database,firestore"], done);
229267
},
230268
function(done) {
231269
test.firestore_client = new Firestore({
@@ -390,15 +428,7 @@ describe("pubsub emulator function triggers", function() {
390428
});
391429
},
392430
function(done) {
393-
test.startEmulators(["--only", "functions,pubsub"]);
394-
test.waitForCondition(
395-
() => test.all_emulators_started,
396-
EMULATORS_STARTUP_DELAY_TIMEOUT,
397-
(err) => {
398-
expect(err).to.be.undefined;
399-
done();
400-
}
401-
);
431+
test.startEmulatorsAndWait(["--only", "functions,pubsub"], done);
402432
},
403433
],
404434
done
@@ -429,3 +459,33 @@ describe("pubsub emulator function triggers", function() {
429459
done();
430460
});
431461
});
462+
463+
describe("import/export end to end", () => {
464+
it("should be able to import/export firestore data", async () => {
465+
// Start up emulator suite
466+
const emulatorsCLI = new CLIProcess("1");
467+
await emulatorsCLI.start("emulators:start", ["--only", "firestore"], (data) => {
468+
return data.indexOf(ALL_EMULATORS_STARTED_LOG) > -1;
469+
});
470+
471+
// Ask for export
472+
const exportCLI = new CLIProcess("2");
473+
const exportPath = fs.mkdtempSync(path.join(os.tmpdir(), "emulator-data"));
474+
await exportCLI.start("emulators:export", [exportPath]);
475+
476+
// Stop the suite
477+
await emulatorsCLI.stop();
478+
479+
// Attempt to import
480+
const importCLI = new CLIProcess("3");
481+
await importCLI.start(
482+
"emulators:start",
483+
["--only", "firestore", "--import", exportPath],
484+
(data) => {
485+
return data.indexOf(ALL_EMULATORS_STARTED_LOG) > -1;
486+
}
487+
);
488+
489+
await importCLI.stop();
490+
}).timeout(2 * TEST_SETUP_TIMEOUT);
491+
});

src/commands/emulators-exec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ module.exports = new Command("emulators:exec <script>")
8484
)
8585
.option(commandUtils.FLAG_ONLY, commandUtils.DESC_ONLY)
8686
.option(commandUtils.FLAG_INSPECT_FUNCTIONS, commandUtils.DESC_INSPECT_FUNCTIONS)
87+
.option(commandUtils.FLAG_IMPORT, commandUtils.DESC_IMPORT)
8788
.action(async (script: string, options: any) => {
8889
const projectId = getProjectId(options, true);
8990
const extraEnv: Record<string, string> = {};

0 commit comments

Comments
 (0)