Skip to content

Commit 9f665fa

Browse files
authored
fix: ignore fully skipped startup failures (#1170)
1 parent 98b8e3b commit 9f665fa

4 files changed

Lines changed: 129 additions & 34 deletions

File tree

‎lib/pr_checker.js‎

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -467,43 +467,54 @@ export default class PRChecker {
467467
}
468468

469469
if (!GITHUB_SUCCESS_CONCLUSIONS.includes(conclusion)) {
470-
hasFailures = true;
470+
const runs = checkRuns?.nodes ?? [];
471+
const allRunsSkipped = runs.length > 0 &&
472+
runs.length === checkRuns.totalCount &&
473+
runs.every((checkRun) => checkRun.status === 'COMPLETED' &&
474+
checkRun.conclusion === 'SKIPPED');
475+
476+
// GitHub can report a STARTUP_FAILURE for a workflow even though all
477+
// of its conditional jobs were skipped. There is no failed check in
478+
// that case, so the suite should not make the commit unlandable.
479+
if (conclusion === 'STARTUP_FAILURE' && allRunsSkipped) {
480+
continue;
481+
}
471482

472-
// If we have detailed checkRuns, show specific failing jobs
473-
if (checkRuns && checkRuns.nodes && checkRuns.nodes.length > 0) {
474-
for (const checkRun of checkRuns.nodes) {
475-
if (checkRun.status === 'COMPLETED' &&
476-
!GITHUB_SUCCESS_CONCLUSIONS.includes(checkRun.conclusion)) {
477-
if (checkRun.conclusion === 'CANCELLED') {
478-
cancelledJobs.push({
479-
name: checkRun.name,
480-
conclusion: checkRun.conclusion,
481-
url: checkRun.detailsUrl
482-
});
483-
} else {
484-
failedJobs.push({
485-
name: checkRun.name,
486-
conclusion: checkRun.conclusion,
487-
url: checkRun.detailsUrl
488-
});
489-
}
483+
hasFailures = true;
484+
let reportedFailure = false;
485+
486+
// If we have detailed checkRuns, show specific failing jobs.
487+
for (const checkRun of runs) {
488+
if (checkRun.status === 'COMPLETED' &&
489+
!GITHUB_SUCCESS_CONCLUSIONS.includes(checkRun.conclusion)) {
490+
reportedFailure = true;
491+
if (checkRun.conclusion === 'CANCELLED') {
492+
cancelledJobs.push({
493+
name: checkRun.name,
494+
conclusion: checkRun.conclusion,
495+
url: checkRun.detailsUrl
496+
});
497+
} else {
498+
failedJobs.push({
499+
name: checkRun.name,
500+
conclusion: checkRun.conclusion,
501+
url: checkRun.detailsUrl
502+
});
490503
}
491504
}
492-
} else {
493-
// Fallback to check suite level information if no checkRuns
494-
if (conclusion === 'CANCELLED') {
495-
cancelledJobs.push({
496-
name: GITHUB_ACTIONS_APP,
497-
conclusion,
498-
url: null
499-
});
500-
} else {
501-
failedJobs.push({
502-
name: GITHUB_ACTIONS_APP,
503-
conclusion,
504-
url: null
505-
});
506-
}
505+
}
506+
507+
// Fall back to the suite when its failed conclusion is not reflected
508+
// by an individual check run.
509+
if (!reportedFailure) {
510+
const failures = conclusion === 'CANCELLED'
511+
? cancelledJobs
512+
: failedJobs;
513+
failures.push({
514+
name: GITHUB_ACTIONS_APP,
515+
conclusion,
516+
url: null
517+
});
507518
}
508519
}
509520
}

‎lib/queries/PR.gql‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ query PR($prid: Int!, $owner: String!, $repo: String!) {
3838
conclusion,
3939
status,
4040
checkRuns(first: 40) {
41+
totalCount
4142
nodes {
4243
name
4344
status

‎test/unit/graphql_queries.test.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('GraphQL queries', () => {
2020
headCommitQuery,
2121
/checkSuites\(first: 100, filterBy: \{ appId: 15368 \}\)/);
2222
assert.match(headCommitQuery, /checkRuns\(first: 40\)/);
23+
assert.match(headCommitQuery, /checkRuns\(first: 40\) \{\s+totalCount/);
2324
assert.match(headCommitQuery, /status \{\s+state\s+\}/);
2425
assert.doesNotMatch(headCommitQuery, /\bapp\s*\{/);
2526
assert.doesNotMatch(commitsQuery, /checkSuites/);

‎test/unit/pr_checker.test.js‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,6 +1860,88 @@ describe('PRChecker', () => {
18601860
cli.assertCalledWith(expectedLogs);
18611861
});
18621862

1863+
it('should ignore startup failures with only skipped check runs',
1864+
async() => {
1865+
const cli = new TestCLI();
1866+
1867+
const expectedLogs = {
1868+
ok: [
1869+
['Last GitHub CI successful']
1870+
]
1871+
};
1872+
1873+
const commits = [{
1874+
commit: {
1875+
checkSuites: {
1876+
nodes: [{
1877+
status: 'COMPLETED',
1878+
conclusion: 'STARTUP_FAILURE',
1879+
checkRuns: {
1880+
totalCount: 2,
1881+
nodes: [
1882+
{
1883+
name: 'stale-comment',
1884+
status: 'COMPLETED',
1885+
conclusion: 'SKIPPED'
1886+
},
1887+
{
1888+
name: 'notable-change',
1889+
status: 'COMPLETED',
1890+
conclusion: 'SKIPPED'
1891+
}
1892+
]
1893+
}
1894+
}]
1895+
}
1896+
}
1897+
}];
1898+
const data = Object.assign({}, baseData, { commits });
1899+
1900+
const checker = new PRChecker(cli, data, {}, testArgv);
1901+
1902+
const status = await checker.checkCI();
1903+
assert(status);
1904+
cli.assertCalledWith(expectedLogs);
1905+
});
1906+
1907+
it('should report a suite failure when check runs are incomplete',
1908+
async() => {
1909+
const cli = new TestCLI();
1910+
1911+
const expectedLogs = {
1912+
error: [
1913+
['1 GitHub CI job(s) failed:'],
1914+
[' - github-actions: STARTUP_FAILURE']
1915+
]
1916+
};
1917+
1918+
const commits = [{
1919+
commit: {
1920+
checkSuites: {
1921+
nodes: [{
1922+
status: 'COMPLETED',
1923+
conclusion: 'STARTUP_FAILURE',
1924+
checkRuns: {
1925+
totalCount: 2,
1926+
nodes: [{
1927+
name: 'stale-comment',
1928+
status: 'COMPLETED',
1929+
conclusion: 'SKIPPED'
1930+
}]
1931+
}
1932+
}]
1933+
}
1934+
}
1935+
}];
1936+
const data = Object.assign({}, baseData, { commits });
1937+
1938+
const checker = new PRChecker(cli, data, {}, testArgv);
1939+
1940+
const status = await checker.checkCI();
1941+
assert(!status);
1942+
cli.assertCalledWith(expectedLogs);
1943+
});
1944+
18631945
it('should succeed if commit status succeeded', async() => {
18641946
const cli = new TestCLI();
18651947

0 commit comments

Comments
 (0)