Skip to content

Commit af63e3b

Browse files
authored
fix(ssh): Fix race condition in ssh and add better debug info on failures
2 parents fbed992 + fde085b commit af63e3b

4 files changed

Lines changed: 36 additions & 7 deletions

File tree

tests/ssh/app-ssh.test.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ const setupApp = async (env: TestEnv) => {
9494
async function sshShellExec(
9595
conn: Client,
9696
command: string,
97+
allowNonZeroExitCode = false,
9798
): Promise<{ code: number; stdout: string; stderr: string }> {
9899
const START = `__START_${Math.random().toString(36).slice(2)}__`;
99100
const END = `__END_${Math.random().toString(36).slice(2)}__`;
@@ -103,9 +104,13 @@ async function sshShellExec(
103104
if (err) return reject(err);
104105
let stdout = "";
105106
let stderr = "";
106-
let code = -1;
107+
let code = undefined;
107108
let done = false;
109+
108110
const parseCode = (buf: string): boolean => {
111+
if (code !== undefined) {
112+
return true; // exit code was set already
113+
}
109114
const idx = buf.indexOf(`${END}:`);
110115
if (idx === -1) return false;
111116
const tail = buf.substring(idx + END.length + 1).trim();
@@ -143,26 +148,41 @@ async function sshShellExec(
143148
} else if (startIdx !== -1) {
144149
cmdOut = stdout.substring(startIdx + START.length);
145150
}
151+
if (code === undefined) {
152+
return reject(
153+
new Error(
154+
`ssh error: command=${command}, stream closed before exit code marker was observed, stdout=${cmdOut}, stderr=${stderr}`,
155+
),
156+
);
157+
}
158+
if (!allowNonZeroExitCode && code !== 0) {
159+
return reject(
160+
new Error(
161+
`ssh error: command=${command}, code=${code}, stdout=${cmdOut}, stderr=${stderr}`,
162+
),
163+
);
164+
}
146165
stream.end();
147166
resolve({ code, stdout: cmdOut, stderr });
148167
};
149168
const onStdout = (d: Buffer) => {
150169
stdout += d.toString();
151170
if (parseCode(stdout)) {
152-
finish();
171+
stream.end();
153172
}
154173
};
155174
const onStderr = (d: Buffer) => {
156175
const t = d.toString();
157176
stderr += t;
158177
if (parseCode(stderr)) {
159-
finish();
178+
stream.end();
160179
}
161180
};
162181
stream.on("data", onStdout);
163182
stream.stderr.on("data", onStderr);
164183
stream.on("close", () => {
165184
console.log("stream close");
185+
finish();
166186
});
167187
stream.write(`echo ${START}\n`);
168188
stream.write(`${command}\n`);
@@ -256,7 +276,7 @@ test("app-ssh", async () => {
256276
expect(checkData.code).toBe(0);
257277
expect(checkData.stdout).toContain("ok");
258278

259-
const errCase = await sshShellExec(conn, "echo oops 1>&2; false");
279+
const errCase = await sshShellExec(conn, "echo oops 1>&2; false", true);
260280
expect(errCase.code).not.toBe(0);
261281
expect(errCase.stderr).toContain("oops");
262282
} finally {

tests/ssh/ssh.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ test("ssh", async () => {
77
const output = await env.runWasmerCommand({
88
args: ["ssh", ...args],
99
stdin,
10-
noAssertSuccess: true,
1110
});
1211
const stdout = output.stdout.replace("\r\n", "\n").trim();
1312
return stdout;

tests/superpanics/python.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ test.concurrent("deploy python app", async () => {
1111
i++;
1212
try {
1313
const env = TestEnv.fromEnv();
14-
const filePath = path.join(projectRoot, "fixtures", "python", "echo-server.py");
14+
const filePath = path.join(
15+
projectRoot,
16+
"fixtures",
17+
"python",
18+
"echo-server.py",
19+
);
1520
let testCode = await fs.promises.readFile(filePath, "utf-8");
1621
testCode = testCode.replaceAll("__TEMPLATE__", `${Math.random()}`);
1722
console.log(testCode);

tests/validation/log.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import { projectRoot } from "../utils/path";
1212

1313
describe("Log tests", () => {
1414
it("Check fetch is logged on simple logging app", async () => {
15-
const filePath = path.join(projectRoot, "fixtures", "php", "path-logger.php");
15+
const filePath = path.join(
16+
projectRoot,
17+
"fixtures",
18+
"php",
19+
"path-logger.php",
20+
);
1621
const phpPathLogger = await fs.readFile(filePath, "utf-8");
1722
const env = TestEnv.fromEnv();
1823
const appName = randomAppName();

0 commit comments

Comments
 (0)