diff --git a/cypress/plugins/docker/index.ts b/cypress/plugins/docker/index.ts
new file mode 100644
index 0000000000..2f3c646408
--- /dev/null
+++ b/cypress/plugins/docker/index.ts
@@ -0,0 +1,131 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+///
+
+import * as os from "os";
+import * as childProcess from "child_process";
+import * as fse from "fs-extra";
+
+import PluginEvents = Cypress.PluginEvents;
+import PluginConfigOptions = Cypress.PluginConfigOptions;
+
+// A cypress plugin to run docker commands
+
+export function dockerRun(args: {
+ image: string;
+ containerName: string;
+ params?: string[];
+}): Promise {
+ const userInfo = os.userInfo();
+ const params = args.params ?? [];
+
+ if (userInfo.uid >= 0) {
+ // On *nix we run the docker container as our uid:gid otherwise cleaning it up its media_store can be difficult
+ params.push("-u", `${userInfo.uid}:${userInfo.gid}`);
+ }
+
+ return new Promise((resolve, reject) => {
+ childProcess.execFile('docker', [
+ "run",
+ "--name", args.containerName,
+ "-d",
+ ...params,
+ args.image,
+ "run",
+ ], (err, stdout) => {
+ if (err) reject(err);
+ resolve(stdout.trim());
+ });
+ });
+}
+
+export function dockerExec(args: {
+ containerId: string;
+ params: string[];
+}): Promise {
+ return new Promise((resolve, reject) => {
+ childProcess.execFile("docker", [
+ "exec", args.containerId,
+ ...args.params,
+ ], { encoding: 'utf8' }, err => {
+ if (err) reject(err);
+ else resolve();
+ });
+ });
+}
+
+export async function dockerLogs(args: {
+ containerId: string;
+ stdoutFile?: string;
+ stderrFile?: string;
+}): Promise {
+ const stdoutFile = args.stdoutFile ? await fse.open(args.stdoutFile, "w") : "ignore";
+ const stderrFile = args.stderrFile ? await fse.open(args.stderrFile, "w") : "ignore";
+
+ await new Promise((resolve) => {
+ childProcess.spawn("docker", [
+ "logs",
+ args.containerId,
+ ], {
+ stdio: ["ignore", stdoutFile, stderrFile],
+ }).once('close', resolve);
+ });
+
+ if (args.stdoutFile) await fse.close(stdoutFile);
+ if (args.stderrFile) await fse.close(stderrFile);
+}
+
+export function dockerStop(args: {
+ containerId: string;
+}): Promise {
+ return new Promise((resolve, reject) => {
+ childProcess.execFile('docker', [
+ "stop",
+ args.containerId,
+ ], err => {
+ if (err) reject(err);
+ resolve();
+ });
+ });
+}
+
+export function dockerRm(args: {
+ containerId: string;
+}): Promise {
+ return new Promise((resolve, reject) => {
+ childProcess.execFile('docker', [
+ "rm",
+ args.containerId,
+ ], err => {
+ if (err) reject(err);
+ resolve();
+ });
+ });
+}
+
+/**
+ * @type {Cypress.PluginConfig}
+ */
+export function docker(on: PluginEvents, config: PluginConfigOptions) {
+ on("task", {
+ dockerRun,
+ dockerExec,
+ dockerLogs,
+ dockerStop,
+ dockerRm,
+ });
+}
diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts
index bc62efb03f..8a22b5cb55 100644
--- a/cypress/plugins/index.ts
+++ b/cypress/plugins/index.ts
@@ -21,11 +21,13 @@ import PluginConfigOptions = Cypress.PluginConfigOptions;
import { performance } from "./performance";
import { synapseDocker } from "./synapsedocker";
import { webserver } from "./webserver";
+import { docker } from "./docker";
/**
* @type {Cypress.PluginConfig}
*/
export default function(on: PluginEvents, config: PluginConfigOptions) {
+ docker(on, config);
performance(on, config);
synapseDocker(on, config);
webserver(on, config);
diff --git a/cypress/plugins/synapsedocker/index.ts b/cypress/plugins/synapsedocker/index.ts
index 42241ecc7d..5227b5e4ac 100644
--- a/cypress/plugins/synapsedocker/index.ts
+++ b/cypress/plugins/synapsedocker/index.ts
@@ -19,12 +19,12 @@ limitations under the License.
import * as path from "path";
import * as os from "os";
import * as crypto from "crypto";
-import * as childProcess from "child_process";
import * as fse from "fs-extra";
-import * as net from "net";
import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
+import { getFreePort } from "../utils/port";
+import { dockerExec, dockerLogs, dockerRun, dockerStop } from "../docker";
// A cypress plugins to add command to start & stop synapses in
// docker with preset templates.
@@ -47,16 +47,6 @@ function randB64Bytes(numBytes: number): string {
return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
}
-async function getFreePort(): Promise {
- return new Promise(resolve => {
- const srv = net.createServer();
- srv.listen(0, () => {
- const port = (srv.address()).port;
- srv.close(() => resolve(port));
- });
- });
-}
-
async function cfgDirFromTemplate(template: string): Promise {
const templateDir = path.join(__dirname, "templates", template);
@@ -109,37 +99,22 @@ async function synapseStart(template: string): Promise {
console.log(`Starting synapse with config dir ${synCfg.configDir}...`);
- const containerName = `react-sdk-cypress-synapse-${crypto.randomBytes(4).toString("hex")}`;
- const userInfo = os.userInfo();
-
- let userParams: string[] = [];
- if (userInfo.uid >= 0) {
- // On *nix we run the docker container as our uid:gid otherwise cleaning it up its media_store can be difficult
- userParams = ["-u", `${userInfo.uid}:${userInfo.gid}`];
- }
-
- const synapseId = await new Promise((resolve, reject) => {
- childProcess.execFile('docker', [
- "run",
- "--name", containerName,
- "-d",
+ const synapseId = await dockerRun({
+ image: "matrixdotorg/synapse:develop",
+ containerName: `react-sdk-cypress-synapse-${crypto.randomBytes(4).toString("hex")}`,
+ params: [
+ "--rm",
"-v", `${synCfg.configDir}:/data`,
"-p", `${synCfg.port}:8008/tcp`,
- ...userParams,
- "matrixdotorg/synapse:develop",
- "run",
- ], (err, stdout) => {
- if (err) reject(err);
- resolve(stdout.trim());
- });
+ ],
});
console.log(`Started synapse with id ${synapseId} on port ${synCfg.port}.`);
// Await Synapse healthcheck
- await new Promise((resolve, reject) => {
- childProcess.execFile("docker", [
- "exec", synapseId,
+ await dockerExec({
+ containerId: synapseId,
+ params: [
"curl",
"--connect-timeout", "30",
"--retry", "30",
@@ -147,10 +122,7 @@ async function synapseStart(template: string): Promise {
"--retry-all-errors",
"--silent",
"http://localhost:8008/health",
- ], { encoding: 'utf8' }, (err, stdout) => {
- if (err) reject(err);
- else resolve();
- });
+ ],
});
const synapse: SynapseInstance = { synapseId, ...synCfg };
@@ -163,43 +135,18 @@ async function synapseStop(id: string): Promise {
if (!synCfg) throw new Error("Unknown synapse ID");
- try {
- const synapseLogsPath = path.join("cypress", "synapselogs", id);
- await fse.ensureDir(synapseLogsPath);
+ const synapseLogsPath = path.join("cypress", "synapselogs", id);
+ await fse.ensureDir(synapseLogsPath);
- const stdoutFile = await fse.open(path.join(synapseLogsPath, "stdout.log"), "w");
- const stderrFile = await fse.open(path.join(synapseLogsPath, "stderr.log"), "w");
- await new Promise((resolve, reject) => {
- childProcess.spawn('docker', [
- "logs",
- id,
- ], {
- stdio: ["ignore", stdoutFile, stderrFile],
- }).once('close', resolve);
- });
- await fse.close(stdoutFile);
- await fse.close(stderrFile);
+ await dockerLogs({
+ containerId: id,
+ stdoutFile: path.join(synapseLogsPath, "stdout.log"),
+ stderrFile: path.join(synapseLogsPath, "stderr.log"),
+ });
- await new Promise((resolve, reject) => {
- childProcess.execFile('docker', [
- "stop",
- id,
- ], err => {
- if (err) reject(err);
- resolve();
- });
- });
- } finally {
- await new Promise((resolve, reject) => {
- childProcess.execFile('docker', [
- "rm",
- id,
- ], err => {
- if (err) reject(err);
- resolve();
- });
- });
- }
+ await dockerStop({
+ containerId: id,
+ });
await fse.remove(synCfg.configDir);
@@ -207,7 +154,7 @@ async function synapseStop(id: string): Promise {
console.log(`Stopped synapse id ${id}.`);
// cypress deliberately fails if you return 'undefined', so
- // return null to signal all is well and we've handled the task.
+ // return null to signal all is well, and we've handled the task.
return null;
}
diff --git a/cypress/plugins/utils/port.ts b/cypress/plugins/utils/port.ts
new file mode 100644
index 0000000000..064ccc7cf9
--- /dev/null
+++ b/cypress/plugins/utils/port.ts
@@ -0,0 +1,27 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import * as net from "net";
+
+export async function getFreePort(): Promise {
+ return new Promise(resolve => {
+ const srv = net.createServer();
+ srv.listen(0, () => {
+ const port = (srv.address()).port;
+ srv.close(() => resolve(port));
+ });
+ });
+}