From 4215ab487831b2d4205f98de547791cfc8888db5 Mon Sep 17 00:00:00 2001 From: I584999 Date: Wed, 20 Nov 2024 09:23:05 -0500 Subject: [PATCH 1/4] Documentation testing is now utilizing async processes --- test/documentation.spec.ts | 87 ++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/test/documentation.spec.ts b/test/documentation.spec.ts index 16f0c431cb2a83..f8559b02e02d09 100644 --- a/test/documentation.spec.ts +++ b/test/documentation.spec.ts @@ -19,12 +19,12 @@ describe('documentation', () => { }); describe('website-documentation', () => { - function getConfigOptionSubHeaders( + async function getConfigOptionSubHeaders( file: string, configOption: string, - ): string[] { + ): Promise { const subHeadings = []; - const content = fs.readFileSync(`docs/usage/${file}`, 'utf8'); + const content = await fs.readFile(`docs/usage/${file}`, 'utf8'); const reg = regEx(`##\\s${configOption}[\\s\\S]+?\n##\\s`); const match = reg.exec(content); const subHeadersMatch = match?.[0]?.matchAll(/\n###\s(?\w+)\n/g); @@ -39,8 +39,8 @@ describe('documentation', () => { } describe('docs/usage/configuration-options.md', () => { - function getConfigHeaders(file: string): string[] { - const content = fs.readFileSync(`docs/usage/${file}`, 'utf8'); + async function getConfigHeaders(file: string): Promise { + const content = await fs.readFile(`docs/usage/${file}`, 'utf8'); const matches = content.match(/\n## (.*?)\n/g) ?? []; return matches.map((match) => match.substring(4, match.length - 1)); } @@ -54,20 +54,20 @@ describe('documentation', () => { .sort(); } - it('has doc headers sorted alphabetically', () => { - expect(getConfigHeaders('configuration-options.md')).toEqual( - getConfigHeaders('configuration-options.md').sort(), + it('has doc headers sorted alphabetically', async () => { + expect(await getConfigHeaders('configuration-options.md')).toEqual( + (await getConfigHeaders('configuration-options.md')).sort(), ); }); - it('has headers for every required option', () => { - expect(getConfigHeaders('configuration-options.md')).toEqual( + it('has headers for every required option', async () => { + expect(await getConfigHeaders('configuration-options.md')).toEqual( getRequiredConfigOptions(), ); }); - function getConfigSubHeaders(file: string): string[] { - const content = fs.readFileSync(`docs/usage/${file}`, 'utf8'); + async function getConfigSubHeaders(file: string): Promise { + const content = await fs.readFile(`docs/usage/${file}`, 'utf8'); const matches = content.match(/\n### (.*?)\n/g) ?? []; return matches .map((match) => match.substring(5, match.length - 1)) @@ -100,30 +100,35 @@ describe('documentation', () => { return parentNames; } - it('has headers for every required sub-option', () => { - expect(getConfigSubHeaders('configuration-options.md')).toEqual( + it('has headers for every required sub-option', async () => { + expect(await getConfigSubHeaders('configuration-options.md')).toEqual( getRequiredConfigSubOptions(), ); }); - it.each([...getParentNames()])( - '%s has sub-headers sorted alphabetically', - (parentName: string) => { + it('has sub-headers sorted alphabetically', async () => { + const parentNames = getParentNames(); + for (const parentName of parentNames) { expect( - getConfigOptionSubHeaders('configuration-options.md', parentName), - ).toEqual( - getConfigOptionSubHeaders( + await getConfigOptionSubHeaders( 'configuration-options.md', parentName, + ), + ).toEqual( + ( + await getConfigOptionSubHeaders( + 'configuration-options.md', + parentName, + ) ).sort(), ); - }, - ); + } + }); }); describe('docs/usage/self-hosted-configuration.md', () => { - function getSelfHostedHeaders(file: string): string[] { - const content = fs.readFileSync(`docs/usage/${file}`, 'utf8'); + async function getSelfHostedHeaders(file: string): Promise { + const content = await fs.readFile(`docs/usage/${file}`, 'utf8'); const matches = content.match(/\n## (.*?)\n/g) ?? []; return matches.map((match) => match.substring(4, match.length - 1)); } @@ -135,32 +140,40 @@ describe('documentation', () => { .sort(); } - it('has headers sorted alphabetically', () => { - expect(getSelfHostedHeaders('self-hosted-configuration.md')).toEqual( - getSelfHostedHeaders('self-hosted-configuration.md').sort(), + it('has headers sorted alphabetically', async () => { + expect( + await getSelfHostedHeaders('self-hosted-configuration.md'), + ).toEqual( + (await getSelfHostedHeaders('self-hosted-configuration.md')).sort(), ); }); - it('has headers for every required option', () => { - expect(getSelfHostedHeaders('self-hosted-configuration.md')).toEqual( - getRequiredSelfHostedOptions(), - ); + it('has headers for every required option', async () => { + expect( + await getSelfHostedHeaders('self-hosted-configuration.md'), + ).toEqual(getRequiredSelfHostedOptions()); }); }); describe('docs/usage/self-hosted-experimental.md', () => { - function getSelfHostedExperimentalConfigHeaders(file: string): string[] { - const content = fs.readFileSync(`docs/usage/${file}`, 'utf8'); + async function getSelfHostedExperimentalConfigHeaders( + file: string, + ): Promise { + const content = await fs.readFile(`docs/usage/${file}`, 'utf8'); const matches = content.match(/\n## (.*?)\n/g) ?? []; return matches.map((match) => match.substring(4, match.length - 1)); } - it('has headers sorted alphabetically', () => { + it('has headers sorted alphabetically', async () => { expect( - getSelfHostedExperimentalConfigHeaders('self-hosted-experimental.md'), - ).toEqual( - getSelfHostedExperimentalConfigHeaders( + await getSelfHostedExperimentalConfigHeaders( 'self-hosted-experimental.md', + ), + ).toEqual( + ( + await getSelfHostedExperimentalConfigHeaders( + 'self-hosted-experimental.md', + ) ).sort(), ); }); From ca8bd10d995c8f98a5221888b1a078533ade9c13 Mon Sep 17 00:00:00 2001 From: I584999 Date: Mon, 25 Nov 2024 09:42:15 -0500 Subject: [PATCH 2/4] Updated test to utilize test.each --- test/documentation.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/documentation.spec.ts b/test/documentation.spec.ts index f8559b02e02d09..c400ed2d183fb2 100644 --- a/test/documentation.spec.ts +++ b/test/documentation.spec.ts @@ -106,9 +106,9 @@ describe('documentation', () => { ); }); - it('has sub-headers sorted alphabetically', async () => { - const parentNames = getParentNames(); - for (const parentName of parentNames) { + test.each([...getParentNames()])( + '%s has sub-headers sorted alphabetically', + async (parentName: string) => { expect( await getConfigOptionSubHeaders( 'configuration-options.md', @@ -122,8 +122,8 @@ describe('documentation', () => { ) ).sort(), ); - } - }); + }, + ); }); describe('docs/usage/self-hosted-configuration.md', () => { From 679a0a8a716fe0225d2653459de0a0dc0a401937 Mon Sep 17 00:00:00 2001 From: I584999 Date: Fri, 27 Dec 2024 11:14:26 -0500 Subject: [PATCH 3/4] Automerge Failure comment implemented --- lib/config/options/index.ts | 7 +++++ lib/config/types.ts | 1 + lib/modules/platform/github/index.spec.ts | 33 +++++++++++++++++++++++ lib/modules/platform/github/index.ts | 10 ++++++- lib/modules/platform/types.ts | 1 + lib/workers/repository/update/pr/index.ts | 1 + lib/workers/types.ts | 1 + 7 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index 0575942785574a..90274b543fad87 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -1997,6 +1997,13 @@ const options: RenovateOptions[] = [ type: 'string', default: 'automergeComment', }, + { + name: 'automergeFailureComment', + description: + 'If an error occurs while automerging, a comment will be created to signal the user. Only used if `automergeFailureComment=on-error`.', + type: 'string', + default: 'none', + }, { name: 'ignoreTests', description: 'Set to `true` to enable automerging without tests.', diff --git a/lib/config/types.ts b/lib/config/types.ts index 4b7169d16869dc..5a8d20207124b8 100644 --- a/lib/config/types.ts +++ b/lib/config/types.ts @@ -34,6 +34,7 @@ export interface RenovateSharedConfig { addLabels?: string[]; autoReplaceGlobalMatch?: boolean; automerge?: boolean; + autoMergeFailureComment?: string; automergeSchedule?: string[]; automergeStrategy?: MergeStrategy; branchName?: string; diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index e16f3db73b860f..28b7a82cc839cb 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -467,6 +467,7 @@ describe('modules/platform/github/index', () => { isArchived: false, nameWithOwner: repository, autoMergeAllowed: true, + automergeFailureComment: 'never', hasIssuesEnabled: true, mergeCommitAllowed: true, rebaseMergeAllowed: true, @@ -2798,6 +2799,18 @@ describe('modules/platform/github/index', () => { platformPrOptions: { usePlatformAutomerge: true }, }; + const prConfigComment: CreatePRConfig = { + sourceBranch: 'some-branch', + targetBranch: 'dev', + prTitle: 'The Title', + prBody: 'Hello world', + labels: ['deps', 'renovate'], + platformPrOptions: { + usePlatformAutomerge: true, + automergeFailureComment: 'on-error', + }, + }; + const mockScope = async (repoOpts: any = {}): Promise => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo', repoOpts); @@ -2975,6 +2988,26 @@ describe('modules/platform/github/index', () => { ]); }); + it('should handle GraphQL errors with automergeFailureComment', async () => { + const scope = await mockScope({ automergeFailureComment: 'on-error' }); + initRepoMock(scope, 'some/repo'); + scope + .get('/repos/some/repo/issues/42/comments?per_page=100') + .reply(200, []) + .post('/repos/some/repo/issues/42/comments') + .reply(200); + await github.initRepo({ repository: 'some/repo' }); + scope.post('/graphql').reply(200, graphqlAutomergeErrorResp); + const pr = await github.createPr(prConfigComment); + expect(pr).toMatchObject({ number: 123 }); + expect(httpMock.getTrace()).toMatchObject([ + graphqlGetRepo, + restCreatePr, + restAddLabels, + graphqlAutomerge, + ]); + }); + it('should handle REST API errors', async () => { const scope = await mockScope(); scope.post('/graphql').reply(500); diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index dfe02ab40a8682..36e2d713dbfd3a 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -1694,13 +1694,21 @@ async function tryPrAutomerge( { prNumber, errors: res.errors }, 'GitHub-native automerge: fail', ); + if (platformPrOptions.automergeFailureComment === 'on-error') { + logger.warn('This automerge request failed'); + await addComment( + prNumber, + 'The Automerge Request for this PR has failed. Please attend.', + ); + return; + } return; } - logger.debug(`GitHub-native automerge: success...PrNo: ${prNumber}`); } catch (err) /* istanbul ignore next: missing test #22198 */ { logger.warn({ prNumber, err }, 'GitHub-native automerge: REST API error'); } + return; } // Creates PR and returns PR number diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index cc770c1d8e43ee..8374ac42fe94c6 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -99,6 +99,7 @@ export interface Issue { export type PlatformPrOptions = { autoApprove?: boolean; automergeStrategy?: MergeStrategy; + automergeFailureComment?: string; azureWorkItemId?: number; bbUseDefaultReviewers?: boolean; bbAutoResolvePrTasks?: boolean; diff --git a/lib/workers/repository/update/pr/index.ts b/lib/workers/repository/update/pr/index.ts index edc85177054180..6be1fbb6533073 100644 --- a/lib/workers/repository/update/pr/index.ts +++ b/lib/workers/repository/update/pr/index.ts @@ -57,6 +57,7 @@ export function getPlatformPrOptions( return { autoApprove: !!config.autoApprove, automergeStrategy: config.automergeStrategy, + automergeFailureComment: config.automergeFailureComment, azureWorkItemId: config.azureWorkItemId ?? 0, bbAutoResolvePrTasks: !!config.bbAutoResolvePrTasks, bbUseDefaultReviewers: !!config.bbUseDefaultReviewers, diff --git a/lib/workers/types.ts b/lib/workers/types.ts index e4c6477b45f662..019af16ec52199 100644 --- a/lib/workers/types.ts +++ b/lib/workers/types.ts @@ -116,6 +116,7 @@ export interface BranchConfig LegacyAdminConfig, PlatformPrOptions { automergeComment?: string; + automergeFailureComment?: string; automergeType?: string; automergedPreviously?: boolean; baseBranch: string; From e03b4117a3a85855ccd513707d7f6e5333df2a78 Mon Sep 17 00:00:00 2001 From: I584999 Date: Mon, 30 Dec 2024 09:12:13 -0500 Subject: [PATCH 4/4] AutomergeFailureComment Testing Implemented --- lib/config/options/index.ts | 2 +- lib/modules/platform/github/index.spec.ts | 20 +++++--------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index 90274b543fad87..0660fad5ce9fc9 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -2002,7 +2002,7 @@ const options: RenovateOptions[] = [ description: 'If an error occurs while automerging, a comment will be created to signal the user. Only used if `automergeFailureComment=on-error`.', type: 'string', - default: 'none', + default: 'never', }, { name: 'ignoreTests', diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 28b7a82cc839cb..cc52577bc09a7d 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -2990,22 +2990,12 @@ describe('modules/platform/github/index', () => { it('should handle GraphQL errors with automergeFailureComment', async () => { const scope = await mockScope({ automergeFailureComment: 'on-error' }); - initRepoMock(scope, 'some/repo'); scope - .get('/repos/some/repo/issues/42/comments?per_page=100') - .reply(200, []) - .post('/repos/some/repo/issues/42/comments') - .reply(200); - await github.initRepo({ repository: 'some/repo' }); - scope.post('/graphql').reply(200, graphqlAutomergeErrorResp); - const pr = await github.createPr(prConfigComment); - expect(pr).toMatchObject({ number: 123 }); - expect(httpMock.getTrace()).toMatchObject([ - graphqlGetRepo, - restCreatePr, - restAddLabels, - graphqlAutomerge, - ]); + .post('/repos/some/repo/issues/123/comments') + .reply(200) + .post('/graphql') + .reply(200, graphqlAutomergeErrorResp); + await expect(github.createPr(prConfigComment)).toResolve(); }); it('should handle REST API errors', async () => {