Skip to content

Commit 99057be

Browse files
committed
Improve build failure detection from XCLogParser
1 parent 4c53bc9 commit 99057be

File tree

10 files changed

+747
-60
lines changed

10 files changed

+747
-60
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { describe, it, expect, beforeEach, vi } from 'vitest';
2+
import { EventEmitter } from 'events';
3+
4+
const spawnMock = vi.hoisted(() => vi.fn()) as ReturnType<typeof vi.fn>;
5+
6+
vi.mock('child_process', () => ({
7+
spawn: spawnMock
8+
}));
9+
10+
import { BuildLogParser } from '../../src/utils/BuildLogParser.js';
11+
12+
class MockChildProcess extends EventEmitter {
13+
public stdout: EventEmitter;
14+
public stderr: EventEmitter;
15+
public killed = false;
16+
17+
constructor(private readonly stdoutData: string, private readonly stderrData = '', private readonly exitCode = 0) {
18+
super();
19+
this.stdout = new EventEmitter();
20+
this.stderr = new EventEmitter();
21+
this.scheduleEmit();
22+
}
23+
24+
kill(): boolean {
25+
this.killed = true;
26+
return true;
27+
}
28+
29+
private scheduleEmit(): void {
30+
setImmediate(() => {
31+
if (this.stdoutData) {
32+
this.stdout.emit('data', Buffer.from(this.stdoutData));
33+
}
34+
if (this.stderrData) {
35+
this.stderr.emit('data', Buffer.from(this.stderrData));
36+
}
37+
this.emit('close', this.exitCode);
38+
});
39+
}
40+
}
41+
42+
describe('BuildLogParser.parseBuildLog', () => {
43+
beforeEach(() => {
44+
spawnMock.mockReset();
45+
});
46+
47+
it('recovers errors from JSON reporter when issues reporter misses them', async () => {
48+
spawnMock.mockImplementation((_command: string, args: string[]) => {
49+
const reporterIndex = args.indexOf('--reporter');
50+
const reporter = reporterIndex >= 0 ? args[reporterIndex + 1] : 'issues';
51+
52+
if (reporter === 'summaryJson') {
53+
return new MockChildProcess(JSON.stringify({
54+
buildStatus: 'failed',
55+
errorCount: 0,
56+
warnings: [],
57+
errors: [],
58+
notes: []
59+
}));
60+
}
61+
62+
if (reporter === 'json') {
63+
return new MockChildProcess(JSON.stringify({
64+
errorCount: 1,
65+
warningCount: 0,
66+
errors: [],
67+
warnings: [],
68+
notes: [],
69+
subSteps: [
70+
{
71+
errorCount: 1,
72+
warningCount: 0,
73+
errors: [],
74+
warnings: [],
75+
notes: [
76+
{
77+
title: "Type 'Void' cannot conform to 'View'",
78+
documentURL: 'file:///path/to/TextsList.swift',
79+
startingLineNumber: 235,
80+
startingColumnNumber: 18,
81+
detail: "/path/to/TextsList.swift:235:18: error: type 'Void' cannot conform to 'View'",
82+
severity: 2,
83+
type: 'swiftError'
84+
}
85+
],
86+
subSteps: []
87+
}
88+
]
89+
}));
90+
}
91+
92+
return new MockChildProcess(JSON.stringify({
93+
errors: [],
94+
warnings: []
95+
}));
96+
});
97+
98+
const result = await BuildLogParser.parseBuildLog('/mock/path/to/log.xcactivitylog');
99+
100+
expect(spawnMock).toHaveBeenCalledTimes(3);
101+
expect(result.buildStatus).toBe('failed');
102+
expect(result.errorCount).toBe(1);
103+
expect(result.errors).not.toHaveLength(0);
104+
expect(result.errors[0]).toContain("TextsList.swift:235");
105+
expect(result.errors[0]).toContain("Type 'Void' cannot conform to 'View'");
106+
});
107+
});

dist/types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface ParsedBuildResults {
1414
errors: string[];
1515
warnings: string[];
1616
buildStatus?: string;
17+
errorCount?: number;
18+
warningCount?: number;
1719
}
1820
export interface EnvironmentValidationResult {
1921
valid: boolean;

dist/types/index.d.ts.map

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

dist/utils/BuildLogParser.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export declare class BuildLogParser {
66
static getRecentBuildLogs(projectPath: string, sinceTime: number): Promise<BuildLogInfo[]>;
77
static getLatestTestLog(projectPath: string): Promise<BuildLogInfo | null>;
88
static parseBuildLog(logPath: string, retryCount?: number, maxRetries?: number): Promise<ParsedBuildResults>;
9+
private static parseBuildSummary;
10+
private static parseDetailedIssues;
911
static canParseLog(logPath: string): Promise<boolean>;
1012
static parseTestResults(_xcresultPath: string): Promise<ParsedBuildResults>;
1113
}

dist/utils/BuildLogParser.d.ts.map

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

0 commit comments

Comments
 (0)