Skip to content

Commit c6c9cc6

Browse files
authored
fix: log stdout/stderr on action failures (#176)
1 parent f74f1aa commit c6c9cc6

File tree

3 files changed

+122
-6
lines changed

3 files changed

+122
-6
lines changed

CONTRIBUTING.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ volumes:
3636
driver: local
3737
```
3838

39+
### Creating the database
40+
41+
You'll then need to connect to the database and issue the following commands:
42+
43+
```sql
44+
create role gmtestuser with login password 'gmtestpass';
45+
create database graphile_migrate_test owner gmtestuser;
46+
```
47+
3948
## ASK FIRST!
4049

4150
There's nothing worse than having your PR with 3 days of work in it rejected

__tests__/actions-live.test.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import "./helpers"; // Has side-effects; must come first
2+
3+
import { Logger, LogLevel, LogMeta } from "@graphile/logger";
4+
import * as mockFs from "mock-fs";
5+
6+
import { executeActions } from "../src/actions";
7+
import { _migrate } from "../src/commands/migrate";
8+
import { parseSettings } from "../src/settings";
9+
import { mockPgClient, TEST_DATABASE_URL } from "./helpers";
10+
11+
beforeAll(() => {
12+
// eslint-disable-next-line no-console
13+
console.log("[mock-fs callsites hack]"); // Without this, jest fails due to 'callsites'
14+
mockFs({});
15+
});
16+
17+
afterAll(() => {
18+
mockFs.restore();
19+
});
20+
21+
it("logs output from command actions on success", async () => {
22+
const logs: Array<{
23+
scope: any;
24+
level: LogLevel;
25+
message: string;
26+
meta?: LogMeta;
27+
}> = [];
28+
const logger = new Logger(scope => (level, message, meta) => {
29+
logs.push({ scope, level, message, meta });
30+
});
31+
const parsedSettings = await parseSettings({
32+
connectionString: TEST_DATABASE_URL,
33+
afterAllMigrations: [
34+
{ _: "command", command: "echo 'success' && echo 'err' >&2" },
35+
],
36+
logger,
37+
});
38+
mockPgClient.query.mockClear();
39+
await executeActions(
40+
parsedSettings,
41+
false,
42+
parsedSettings.afterAllMigrations,
43+
);
44+
expect(mockPgClient.query).toHaveBeenCalledTimes(0);
45+
expect(logs).toHaveLength(2);
46+
expect(logs[0]).toMatchObject({
47+
level: "info",
48+
message: "success\n",
49+
});
50+
expect(logs[1]).toMatchObject({
51+
level: "error",
52+
message: "err\n",
53+
});
54+
});
55+
56+
it("logs output from command actions on failure", async () => {
57+
const logs: Array<{
58+
scope: any;
59+
level: LogLevel;
60+
message: string;
61+
meta?: LogMeta;
62+
}> = [];
63+
const logger = new Logger(scope => (level, message, meta) => {
64+
logs.push({ scope, level, message, meta });
65+
});
66+
const parsedSettings = await parseSettings({
67+
connectionString: TEST_DATABASE_URL,
68+
afterAllMigrations: [
69+
{ _: "command", command: "echo 'success' && echo 'err' >&2 && false" },
70+
],
71+
logger,
72+
});
73+
mockPgClient.query.mockClear();
74+
let err;
75+
try {
76+
await executeActions(
77+
parsedSettings,
78+
false,
79+
parsedSettings.afterAllMigrations,
80+
);
81+
} catch (e) {
82+
err = e;
83+
}
84+
expect(err).toBeTruthy();
85+
expect(mockPgClient.query).toHaveBeenCalledTimes(0);
86+
expect(logs).toHaveLength(2);
87+
expect(logs[0]).toMatchObject({
88+
level: "info",
89+
message: "success\n",
90+
});
91+
expect(logs[1]).toMatchObject({
92+
level: "error",
93+
message: "err\n",
94+
});
95+
});

src/actions.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export async function executeActions(
9393
);
9494
} else if (actionSpec._ === "command") {
9595
// Run the command
96-
const { stdout, stderr } = await exec(actionSpec.command, {
96+
const promise = exec(actionSpec.command, {
9797
env: mergeWithoutClobbering(
9898
{
9999
...process.env,
@@ -120,11 +120,23 @@ export async function executeActions(
120120
// 50MB of log data should be enough for any reasonable migration... right?
121121
maxBuffer: 50 * 1024 * 1024,
122122
});
123-
if (stdout) {
124-
parsedSettings.logger.info(stdout);
125-
}
126-
if (stderr) {
127-
parsedSettings.logger.error(stderr);
123+
try {
124+
const { stdout, stderr } = await promise;
125+
if (stdout) {
126+
parsedSettings.logger.info(stdout);
127+
}
128+
if (stderr) {
129+
parsedSettings.logger.error(stderr);
130+
}
131+
} catch (e) {
132+
const { stdout, stderr } = e;
133+
if (stdout) {
134+
parsedSettings.logger.info(stdout);
135+
}
136+
if (stderr) {
137+
parsedSettings.logger.error(stderr);
138+
}
139+
throw e;
128140
}
129141
}
130142
}

0 commit comments

Comments
 (0)