factorio-test-cli 3.2.0 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/factorio-process.js +10 -4
- package/package.json +1 -1
- package/test-output.js +12 -5
- package/test-output.test.js +57 -3
- package/test-results.js +19 -0
- package/test-results.test.js +58 -1
package/factorio-process.js
CHANGED
|
@@ -102,6 +102,9 @@ export function parseResultMessage(message) {
|
|
|
102
102
|
hasFocusedTests: hasFocused,
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
+
function factorioLogHint(dataDir) {
|
|
106
|
+
return `\nCheck Factorio log for details: ${path.join(dataDir, "factorio-current.log")}`;
|
|
107
|
+
}
|
|
105
108
|
function createOutputComponents(options) {
|
|
106
109
|
const handler = new FactorioOutputHandler();
|
|
107
110
|
const collector = new TestRunCollector();
|
|
@@ -129,6 +132,9 @@ function createOutputComponents(options) {
|
|
|
129
132
|
progress.handleTestFinished(test);
|
|
130
133
|
progress.withPermanentOutput(() => printer.printTestResult(test));
|
|
131
134
|
});
|
|
135
|
+
collector.on("describeBlockFailed", (block) => {
|
|
136
|
+
progress.withPermanentOutput(() => printer.printTestResult(block));
|
|
137
|
+
});
|
|
132
138
|
handler.on("result", () => {
|
|
133
139
|
progress.finish();
|
|
134
140
|
printer.resetMessage();
|
|
@@ -206,16 +212,16 @@ export async function runFactorioTestsHeadless(factorioPath, dataDir, savePath,
|
|
|
206
212
|
resolve();
|
|
207
213
|
}
|
|
208
214
|
else if (outputTimedOut) {
|
|
209
|
-
reject(new CliError(`Factorio process stuck: no output received for ${outputTimeout} seconds`));
|
|
215
|
+
reject(new CliError(`Factorio process stuck: no output received for ${outputTimeout} seconds${factorioLogHint(dataDir)}`));
|
|
210
216
|
}
|
|
211
217
|
else if (startupTimedOut) {
|
|
212
|
-
reject(new CliError(
|
|
218
|
+
reject(new CliError(`Factorio unresponsive: no test run started within 10 seconds${factorioLogHint(dataDir)}`));
|
|
213
219
|
}
|
|
214
220
|
else if (handler.getResultMessage() !== undefined) {
|
|
215
221
|
resolve();
|
|
216
222
|
}
|
|
217
223
|
else {
|
|
218
|
-
reject(new CliError(`Factorio exited with code ${code}, signal ${signal}, no result received`));
|
|
224
|
+
reject(new CliError(`Factorio exited with code ${code}, signal ${signal}, no result received${factorioLogHint(dataDir)}`));
|
|
219
225
|
}
|
|
220
226
|
});
|
|
221
227
|
});
|
|
@@ -253,7 +259,7 @@ export async function runFactorioTestsGraphics(factorioPath, dataDir, savePath,
|
|
|
253
259
|
resolve();
|
|
254
260
|
}
|
|
255
261
|
else {
|
|
256
|
-
reject(new CliError(`Factorio exited with code ${code}, signal ${signal}`));
|
|
262
|
+
reject(new CliError(`Factorio exited with code ${code}, signal ${signal}${factorioLogHint(dataDir)}`));
|
|
257
263
|
}
|
|
258
264
|
});
|
|
259
265
|
});
|
package/package.json
CHANGED
package/test-output.js
CHANGED
|
@@ -87,16 +87,18 @@ export class OutputFormatter {
|
|
|
87
87
|
formatTestResult(test) {
|
|
88
88
|
if (this.options.quiet)
|
|
89
89
|
return;
|
|
90
|
-
const
|
|
90
|
+
const prefix = this.getPrefix(test.result);
|
|
91
|
+
const duration = test.durationMs !== undefined ? ` (${formatDuration(test.durationMs)})` : "";
|
|
92
|
+
console.log(`${prefix} ${test.path}${duration}`);
|
|
93
|
+
const showLogs = test.result === "failed" || test.result === "error" || this.options.showPassedLogs;
|
|
91
94
|
if (showLogs && test.logs.length > 0) {
|
|
95
|
+
console.log("Log messages:");
|
|
92
96
|
for (const log of test.logs) {
|
|
93
97
|
console.log(" " + log);
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
console.log(`${prefix} ${test.path}${duration}`);
|
|
99
|
-
if (test.result === "failed") {
|
|
100
|
+
if ((test.result === "failed" || test.result === "error") && test.errors.length > 0) {
|
|
101
|
+
console.log("Errors:");
|
|
100
102
|
for (const error of test.errors) {
|
|
101
103
|
console.log(" " + error);
|
|
102
104
|
}
|
|
@@ -107,6 +109,7 @@ export class OutputFormatter {
|
|
|
107
109
|
return;
|
|
108
110
|
if (!this.options.quiet) {
|
|
109
111
|
this.printRecapSection(data.tests, "failed", "Failures:");
|
|
112
|
+
this.printRecapSection(data.tests, "error", "Describe block errors:");
|
|
110
113
|
this.printRecapSection(data.tests, "todo", "Todo:");
|
|
111
114
|
}
|
|
112
115
|
this.printCountsLine(data.summary);
|
|
@@ -126,6 +129,8 @@ export class OutputFormatter {
|
|
|
126
129
|
const segments = [];
|
|
127
130
|
if (summary.failed > 0)
|
|
128
131
|
segments.push(chalk.red(`${summary.failed} failed`));
|
|
132
|
+
if (summary.describeBlockErrors > 0)
|
|
133
|
+
segments.push(chalk.red(`${summary.describeBlockErrors} errors`));
|
|
129
134
|
if (summary.todo > 0)
|
|
130
135
|
segments.push(chalk.magenta(`${summary.todo} todo`));
|
|
131
136
|
if (summary.skipped > 0)
|
|
@@ -140,6 +145,8 @@ export class OutputFormatter {
|
|
|
140
145
|
return chalk.green("PASS");
|
|
141
146
|
case "failed":
|
|
142
147
|
return chalk.red("FAIL");
|
|
148
|
+
case "error":
|
|
149
|
+
return chalk.red("ERROR");
|
|
143
150
|
case "skipped":
|
|
144
151
|
return chalk.yellow("SKIP");
|
|
145
152
|
case "todo":
|
package/test-output.test.js
CHANGED
|
@@ -150,12 +150,19 @@ describe("OutputFormatter", () => {
|
|
|
150
150
|
expect(output.some((line) => line.includes("assertion failed"))).toBe(true);
|
|
151
151
|
expect(output.some((line) => line.includes("at test.ts:10"))).toBe(true);
|
|
152
152
|
});
|
|
153
|
-
it("shows logs
|
|
153
|
+
it("shows status line first, then logs and errors with headers", () => {
|
|
154
154
|
const formatter = new OutputFormatter({});
|
|
155
155
|
formatter.formatTestResult(failedTest);
|
|
156
|
-
const logIndex = output.findIndex((line) => line.includes("debug output"));
|
|
157
156
|
const failIndex = output.findIndex((line) => line.includes("FAIL"));
|
|
158
|
-
|
|
157
|
+
const logHeaderIndex = output.findIndex((line) => line.includes("Log messages:"));
|
|
158
|
+
const logIndex = output.findIndex((line) => line.includes("debug output"));
|
|
159
|
+
const errorHeaderIndex = output.findIndex((line) => line.includes("Errors:"));
|
|
160
|
+
const errorIndex = output.findIndex((line) => line.includes("assertion failed"));
|
|
161
|
+
expect(failIndex).toBe(0);
|
|
162
|
+
expect(logHeaderIndex).toBeGreaterThan(failIndex);
|
|
163
|
+
expect(logIndex).toBeGreaterThan(logHeaderIndex);
|
|
164
|
+
expect(errorHeaderIndex).toBeGreaterThan(logIndex);
|
|
165
|
+
expect(errorIndex).toBeGreaterThan(errorHeaderIndex);
|
|
159
166
|
});
|
|
160
167
|
it("hides logs for passed tests by default", () => {
|
|
161
168
|
const formatter = new OutputFormatter({});
|
|
@@ -199,6 +206,22 @@ describe("OutputFormatter", () => {
|
|
|
199
206
|
expect(output[0]).toContain("TODO");
|
|
200
207
|
expect(output[0]).toContain("todo test");
|
|
201
208
|
});
|
|
209
|
+
it("formats describe block error with ERROR prefix", () => {
|
|
210
|
+
const formatter = new OutputFormatter({});
|
|
211
|
+
const errorTest = {
|
|
212
|
+
path: "root > block",
|
|
213
|
+
result: "error",
|
|
214
|
+
errors: ["Error running afterAll: Oh no"],
|
|
215
|
+
logs: ["hook log"],
|
|
216
|
+
};
|
|
217
|
+
formatter.formatTestResult(errorTest);
|
|
218
|
+
expect(output[0]).toContain("ERROR");
|
|
219
|
+
expect(output[0]).toContain("root > block");
|
|
220
|
+
expect(output.some((line) => line.includes("Log messages:"))).toBe(true);
|
|
221
|
+
expect(output.some((line) => line.includes("hook log"))).toBe(true);
|
|
222
|
+
expect(output.some((line) => line.includes("Errors:"))).toBe(true);
|
|
223
|
+
expect(output.some((line) => line.includes("Oh no"))).toBe(true);
|
|
224
|
+
});
|
|
202
225
|
});
|
|
203
226
|
describe("OutputFormatter.formatSummary", () => {
|
|
204
227
|
let consoleSpy;
|
|
@@ -298,6 +321,37 @@ describe("OutputFormatter.formatSummary", () => {
|
|
|
298
321
|
expect(countsLine).toContain("1 passed");
|
|
299
322
|
expect(countsLine).toContain("(4 total)");
|
|
300
323
|
});
|
|
324
|
+
it("shows describe block errors in separate section from failures", () => {
|
|
325
|
+
const formatter = new OutputFormatter({});
|
|
326
|
+
const data = {
|
|
327
|
+
tests: [
|
|
328
|
+
{ path: "test", result: "failed", errors: ["test err"], logs: [] },
|
|
329
|
+
{ path: "block", result: "error", errors: ["block err"], logs: [] },
|
|
330
|
+
],
|
|
331
|
+
summary: makeSummary({ ran: 1, failed: 1, describeBlockErrors: 1, status: "failed" }),
|
|
332
|
+
};
|
|
333
|
+
formatter.formatSummary(data);
|
|
334
|
+
expect(output.some((l) => l.includes("Failures:"))).toBe(true);
|
|
335
|
+
expect(output.some((l) => l.includes("Describe block errors:"))).toBe(true);
|
|
336
|
+
const failuresIdx = output.findIndex((l) => l.includes("Failures:"));
|
|
337
|
+
const errorsIdx = output.findIndex((l) => l.includes("Describe block errors:"));
|
|
338
|
+
const failLine = output.findIndex((l) => l.includes("FAIL"));
|
|
339
|
+
const errorLine = output.findIndex((l) => l.includes("ERROR"));
|
|
340
|
+
expect(failLine).toBeGreaterThan(failuresIdx);
|
|
341
|
+
expect(failLine).toBeLessThan(errorsIdx);
|
|
342
|
+
expect(errorLine).toBeGreaterThan(errorsIdx);
|
|
343
|
+
});
|
|
344
|
+
it("includes describe block errors count in summary line", () => {
|
|
345
|
+
const formatter = new OutputFormatter({});
|
|
346
|
+
const data = {
|
|
347
|
+
tests: [{ path: "block", result: "error", errors: ["err"], logs: [] }],
|
|
348
|
+
summary: makeSummary({ passed: 1, describeBlockErrors: 2, status: "failed" }),
|
|
349
|
+
};
|
|
350
|
+
formatter.formatSummary(data);
|
|
351
|
+
const countsLine = output.find((l) => l.includes("Tests:"));
|
|
352
|
+
expect(countsLine).toContain("2 errors");
|
|
353
|
+
expect(countsLine).toContain("1 passed");
|
|
354
|
+
});
|
|
301
355
|
it("does nothing when summary is undefined", () => {
|
|
302
356
|
const formatter = new OutputFormatter({});
|
|
303
357
|
formatter.formatSummary({ tests: [] });
|
package/test-results.js
CHANGED
|
@@ -5,11 +5,13 @@ export class TestRunCollector extends EventEmitter {
|
|
|
5
5
|
data = { tests: [] };
|
|
6
6
|
currentTest;
|
|
7
7
|
currentLogs = [];
|
|
8
|
+
pendingLogs = [];
|
|
8
9
|
testStartTime;
|
|
9
10
|
handleEvent(event) {
|
|
10
11
|
switch (event.type) {
|
|
11
12
|
case "testStarted":
|
|
12
13
|
this.flushCurrentTest();
|
|
14
|
+
this.pendingLogs = [];
|
|
13
15
|
this.testStartTime = performance.now();
|
|
14
16
|
this.currentTest = {
|
|
15
17
|
path: event.test.path,
|
|
@@ -61,6 +63,20 @@ export class TestRunCollector extends EventEmitter {
|
|
|
61
63
|
logs: [],
|
|
62
64
|
});
|
|
63
65
|
break;
|
|
66
|
+
case "describeBlockFailed": {
|
|
67
|
+
this.flushCurrentTest();
|
|
68
|
+
const captured = {
|
|
69
|
+
path: event.block.path,
|
|
70
|
+
source: event.block.source,
|
|
71
|
+
result: "error",
|
|
72
|
+
errors: event.errors,
|
|
73
|
+
logs: [...this.pendingLogs],
|
|
74
|
+
};
|
|
75
|
+
this.data.tests.push(captured);
|
|
76
|
+
this.emit("describeBlockFailed", captured);
|
|
77
|
+
this.pendingLogs = [];
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
64
80
|
case "testRunFinished":
|
|
65
81
|
this.flushCurrentTest();
|
|
66
82
|
this.data.summary = event.results;
|
|
@@ -75,6 +91,9 @@ export class TestRunCollector extends EventEmitter {
|
|
|
75
91
|
if (this.currentTest) {
|
|
76
92
|
this.currentLogs.push(line);
|
|
77
93
|
}
|
|
94
|
+
else {
|
|
95
|
+
this.pendingLogs.push(line);
|
|
96
|
+
}
|
|
78
97
|
}
|
|
79
98
|
getData() {
|
|
80
99
|
return this.data;
|
package/test-results.test.js
CHANGED
|
@@ -38,12 +38,69 @@ describe("TestRunCollector", () => {
|
|
|
38
38
|
const data = collector.getData();
|
|
39
39
|
expect(data.tests[0].logs).toEqual(["log line 1", "log line 2"]);
|
|
40
40
|
});
|
|
41
|
-
it("does not
|
|
41
|
+
it("does not attach pending logs to skipped tests", () => {
|
|
42
42
|
collector.captureLog("orphan log");
|
|
43
43
|
collector.handleEvent({ type: "testSkipped", test: { path: "test" } });
|
|
44
44
|
const data = collector.getData();
|
|
45
45
|
expect(data.tests[0].logs).toEqual([]);
|
|
46
46
|
});
|
|
47
|
+
it("handles describeBlockFailed with errors and pending logs", () => {
|
|
48
|
+
collector.captureLog("hook output");
|
|
49
|
+
collector.handleEvent({
|
|
50
|
+
type: "describeBlockFailed",
|
|
51
|
+
block: { path: "root > block", source: { file: "test.ts", line: 5 } },
|
|
52
|
+
errors: ["Error running afterAll: Oh no"],
|
|
53
|
+
});
|
|
54
|
+
const data = collector.getData();
|
|
55
|
+
expect(data.tests).toHaveLength(1);
|
|
56
|
+
expect(data.tests[0]).toMatchObject({
|
|
57
|
+
path: "root > block",
|
|
58
|
+
result: "error",
|
|
59
|
+
errors: ["Error running afterAll: Oh no"],
|
|
60
|
+
logs: ["hook output"],
|
|
61
|
+
source: { file: "test.ts", line: 5 },
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it("emits describeBlockFailed event", () => {
|
|
65
|
+
const handler = vi.fn();
|
|
66
|
+
collector.on("describeBlockFailed", handler);
|
|
67
|
+
collector.handleEvent({
|
|
68
|
+
type: "describeBlockFailed",
|
|
69
|
+
block: { path: "block" },
|
|
70
|
+
errors: ["err"],
|
|
71
|
+
});
|
|
72
|
+
expect(handler).toHaveBeenCalledOnce();
|
|
73
|
+
expect(handler.mock.calls[0][0].path).toBe("block");
|
|
74
|
+
});
|
|
75
|
+
it("clears pending logs when a new test starts", () => {
|
|
76
|
+
collector.captureLog("before test");
|
|
77
|
+
collector.handleEvent({ type: "testStarted", test: { path: "test" } });
|
|
78
|
+
collector.handleEvent({ type: "testPassed", test: { path: "test" } });
|
|
79
|
+
collector.handleEvent({
|
|
80
|
+
type: "describeBlockFailed",
|
|
81
|
+
block: { path: "block" },
|
|
82
|
+
errors: ["err"],
|
|
83
|
+
});
|
|
84
|
+
const data = collector.getData();
|
|
85
|
+
const block = data.tests.find((t) => t.path === "block");
|
|
86
|
+
expect(block.logs).toEqual([]);
|
|
87
|
+
});
|
|
88
|
+
it("captures logs after test finishes for describe block failure", () => {
|
|
89
|
+
collector.handleEvent({ type: "testStarted", test: { path: "test" } });
|
|
90
|
+
collector.captureLog("test log");
|
|
91
|
+
collector.handleEvent({ type: "testPassed", test: { path: "test" } });
|
|
92
|
+
collector.captureLog("after_all log");
|
|
93
|
+
collector.handleEvent({
|
|
94
|
+
type: "describeBlockFailed",
|
|
95
|
+
block: { path: "block" },
|
|
96
|
+
errors: ["hook error"],
|
|
97
|
+
});
|
|
98
|
+
const data = collector.getData();
|
|
99
|
+
expect(data.tests[0].logs).toEqual(["test log"]);
|
|
100
|
+
const block = data.tests[1];
|
|
101
|
+
expect(block.logs).toEqual(["after_all log"]);
|
|
102
|
+
expect(block.errors).toEqual(["hook error"]);
|
|
103
|
+
});
|
|
47
104
|
it("handles testSkipped without prior testStarted", () => {
|
|
48
105
|
collector.handleEvent({ type: "testSkipped", test: { path: "skipped test" } });
|
|
49
106
|
const data = collector.getData();
|