From 1efbc0eabff92062de2259f36146222c15ade6a9 Mon Sep 17 00:00:00 2001 From: Mary Marchini Date: Sat, 8 Aug 2020 19:15:00 -0700 Subject: [PATCH] feat: check Actions and handle doc-only changes Doc-only changes don't need a full Jenkins CI, instead we can check if the last Actions run was successful. Therefore this commit also adds check for Action runs. Jenkins CI messages were improved as well. Fix: https://github.com/nodejs/node-core-utils/issues/324 Fix: https://github.com/nodejs/node/issues/32335 Fix: https://github.com/nodejs/node/issues/29770 --- lib/ci/ci_type_parser.js | 4 +- lib/pr_checker.js | 83 ++- lib/queries/PR.gql | 5 + lib/session.js | 2 +- test/fixtures/comments_with_failed_ci.json | 6 + test/fixtures/comments_with_pending_ci.json | 6 + test/fixtures/comments_with_success_ci.json | 6 + test/fixtures/data.js | 31 +- test/fixtures/generate-graphql-fixture.js | 0 .../pending/node-test-pull-request-32777.json | 528 ++++++++++++++++++ test/fixtures/pull_requests/doc-only.json | 50 ++ test/unit/pr_checker.test.js | 459 ++++++++++++++- 12 files changed, 1163 insertions(+), 17 deletions(-) create mode 100644 test/fixtures/comments_with_failed_ci.json create mode 100644 test/fixtures/comments_with_pending_ci.json create mode 100644 test/fixtures/comments_with_success_ci.json create mode 100644 test/fixtures/generate-graphql-fixture.js create mode 100644 test/fixtures/jenkins/pending/node-test-pull-request-32777.json create mode 100644 test/fixtures/pull_requests/doc-only.json diff --git a/lib/ci/ci_type_parser.js b/lib/ci/ci_type_parser.js index dd87a09d..2d7abed8 100644 --- a/lib/ci/ci_type_parser.js +++ b/lib/ci/ci_type_parser.js @@ -25,8 +25,8 @@ const CI_TYPE_ENUM = { }; const CI_PROVIDERS = { - JENKINS: 'jenkins', - GITHUB: 'github-check' + GITHUB: 'github-check', + NODEJS: 'nodejs' }; const { JOB_CI, FULL_CI } = CI_TYPE_ENUM; diff --git a/lib/pr_checker.js b/lib/pr_checker.js index ca46a0c9..c76265f5 100644 --- a/lib/pr_checker.js +++ b/lib/pr_checker.js @@ -204,7 +204,7 @@ class PRChecker { } async checkCI() { - const ciType = this.argv.ciType || CI_PROVIDERS.JENKINS; + const ciType = this.argv.ciType || CI_PROVIDERS.NODEJS; const providers = Object.values(CI_PROVIDERS); if (!providers.includes(ciType)) { @@ -214,8 +214,8 @@ class PRChecker { } let status = false; - if (ciType === CI_PROVIDERS.JENKINS) { - status = await this.checkJenkinsCI(); + if (ciType === CI_PROVIDERS.NODEJS) { + status = await this.checkNodejsCI(); } else if (ciType === CI_PROVIDERS.GITHUB) { status = this.checkGitHubCI(); } @@ -233,7 +233,7 @@ class PRChecker { let status = true; if (!ciMap.size) { - cli.error('No CI runs detected'); + cli.error('No Jenkins CI runs detected'); this.CIStatus = false; return false; } else if (!this.hasFullCI(ciMap)) { @@ -292,6 +292,13 @@ class PRChecker { cli.error( `${failures.length} failure(s) on the last Jenkins CI run`); status = false; + // NOTE(mmarchini): not sure why PEDING returns null + } else if (result === null) { + cli.error( + 'Last Jenkins CI still running'); + status = false; + } else { + cli.ok('Last Jenkins CI successful'); } } @@ -299,10 +306,47 @@ class PRChecker { return status; } + checkActionsCI() { + const { cli, commits } = this; + + if (!commits || commits.length === 0) { + cli.error('No commits detected'); + return false; + } + + // NOTE(mmarchini): we only care about the last commit. Maybe in the future + // we'll want to check all commits for a successful CI. + const { commit } = commits[commits.length - 1]; + + this.CIStatus = false; + const checkSuites = commit.checkSuites || { nodes: [] }; + if (!commit.status && checkSuites.nodes.length === 0) { + cli.error('No GitHub CI runs detected'); + return false; + } + + // GitHub new Check API + for (const { status, conclusion } of checkSuites.nodes) { + if (status !== 'COMPLETED') { + cli.error('GitHub CI is still running'); + return false; + } + + if (!['SUCCESS', 'NEUTRAL'].includes(conclusion)) { + cli.error('Last GitHub CI failed'); + return false; + } + } + + cli.ok('Last GitHub Actions successful'); + this.CIStatus = true; + return true; + } + checkGitHubCI() { const { cli, commits } = this; - if (!commits) { + if (!commits || commits.length === 0) { cli.error('No commits detected'); return false; } @@ -314,7 +358,7 @@ class PRChecker { this.CIStatus = false; const checkSuites = commit.checkSuites || { nodes: [] }; if (!commit.status && checkSuites.nodes.length === 0) { - cli.error('No CI runs detected'); + cli.error('No GitHub CI runs detected'); return false; } @@ -350,6 +394,33 @@ class PRChecker { return true; } + isOnlyDocChanges() { + const { cli, pr } = this; + + // NOTE(mmarchini): if files not present, fallback + // to old behavior. This should only be the case on old tests + // TODO(mmarchini): add files to all fixtures on old tests + if (!pr.files) { + return false; + } + + for (const { path } of pr.files.nodes) { + if (!path.endsWith('.md')) { + return false; + } + } + cli.info('Doc-only changes'); + return true; + } + + async checkNodejsCI() { + let status = this.checkActionsCI(); + if (!this.isOnlyDocChanges()) { + status &= await this.checkJenkinsCI(); + } + return status; + } + checkAuthor() { const { cli, commits, pr } = this; diff --git a/lib/queries/PR.gql b/lib/queries/PR.gql index 81b81911..27cc1396 100644 --- a/lib/queries/PR.gql +++ b/lib/queries/PR.gql @@ -18,6 +18,11 @@ query PR($prid: Int!, $owner: String!, $repo: String!) { name } }, + files(first: 100) { + nodes { + path + } + }, title, baseRefName, headRefName, diff --git a/lib/session.js b/lib/session.js index 4db0e3d9..9ba61a5b 100644 --- a/lib/session.js +++ b/lib/session.js @@ -99,7 +99,7 @@ class Session { } get ciType() { - return this.config.ciType || 'jenkins'; + return this.config.ciType || 'nodejs'; } get pullName() { diff --git a/test/fixtures/comments_with_failed_ci.json b/test/fixtures/comments_with_failed_ci.json new file mode 100644 index 00000000..ced2258e --- /dev/null +++ b/test/fixtures/comments_with_failed_ci.json @@ -0,0 +1,6 @@ +[ + { + "publishedAt": "2018-10-22T04:16:36.458Z", + "bodyText": "CI: https://ci.nodejs.org/job/node-test-pull-request/15442/" + } +] diff --git a/test/fixtures/comments_with_pending_ci.json b/test/fixtures/comments_with_pending_ci.json new file mode 100644 index 00000000..66c26a82 --- /dev/null +++ b/test/fixtures/comments_with_pending_ci.json @@ -0,0 +1,6 @@ +[ + { + "publishedAt": "2018-10-22T04:16:36.458Z", + "bodyText": "CI: https://ci.nodejs.org/job/node-test-pull-request/32777/" + } +] diff --git a/test/fixtures/comments_with_success_ci.json b/test/fixtures/comments_with_success_ci.json new file mode 100644 index 00000000..3f2a51ea --- /dev/null +++ b/test/fixtures/comments_with_success_ci.json @@ -0,0 +1,6 @@ +[ + { + "publishedAt": "2018-10-22T04:16:36.458Z", + "bodyText": "CI: https://ci.nodejs.org/job/node-test-pull-request/15237/" + } +] diff --git a/test/fixtures/data.js b/test/fixtures/data.js index 8c6f17ed..1d90e544 100644 --- a/test/fixtures/data.js +++ b/test/fixtures/data.js @@ -36,7 +36,10 @@ const approvingReviews = readJSON('reviews_approved.json'); const requestingChangesReviews = readJSON('reviews_requesting_changes.json'); const commentsWithCI = readJSON('comments_with_ci.json'); +const commentsWithFailedCI = readJSON('comments_with_failed_ci.json'); const commentsWithLGTM = readJSON('comments_with_lgtm.json'); +const commentsWithPendingCI = readJSON('comments_with_pending_ci.json'); +const commentsWithSuccessCI = readJSON('comments_with_success_ci.json'); const oddCommits = readJSON('odd_commits.json'); const incorrectGitConfigCommits = readJSON('incorrect_git_config_commits.json'); @@ -99,6 +102,27 @@ for (const item of readdirSync(path('./github-ci'))) { githubCI[basename(item, '.json')] = readJSON(`./github-ci/${item}`); }; +const pullRequests = {}; + +for (const item of readdirSync(path('./pull_requests'))) { + if (!item.endsWith('.json')) { + continue; + } + pullRequests[basename(item, '.json')] = readJSON(`./pull_requests/${item}`); +}; + +const jenkinsCI = {}; + +for (const subdir of readdirSync(path('./jenkins'))) { + for (const item of readdirSync(path(`./jenkins/${subdir}`))) { + if (!item.endsWith('.json')) { + continue; + } + jenkinsCI[`${subdir}/${basename(item, '.json')}`] = + readJSON(`./jenkins/${subdir}/${item}`); + } +}; + module.exports = { approved, requestedChanges, @@ -109,8 +133,12 @@ module.exports = { approvingReviews, requestingChangesReviews, commentsWithCI, + commentsWithFailedCI, commentsWithLGTM, + commentsWithSuccessCI, + commentsWithPendingCI, oddCommits, + jenkinsCI, githubCI, incorrectGitConfigCommits, simpleCommits, @@ -137,5 +165,6 @@ module.exports = { readmeNoCollaboratorE, readmeUnordered, closedPR, - mergedPR + mergedPR, + pullRequests }; diff --git a/test/fixtures/generate-graphql-fixture.js b/test/fixtures/generate-graphql-fixture.js new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/jenkins/pending/node-test-pull-request-32777.json b/test/fixtures/jenkins/pending/node-test-pull-request-32777.json new file mode 100644 index 00000000..1054fd58 --- /dev/null +++ b/test/fixtures/jenkins/pending/node-test-pull-request-32777.json @@ -0,0 +1,528 @@ +{ + "_class": "com.tikal.jenkins.plugins.multijob.MultiJobBuild", + "actions": [ + { + "_class": "hudson.model.ParametersAction", + "parameters": [ + { + "_class": "hudson.model.BooleanParameterValue", + "name": "CERTIFY_SAFE", + "value": true + }, + { + "_class": "hudson.model.StringParameterValue", + "name": "TARGET_GITHUB_ORG", + "value": "nodejs" + }, + { + "_class": "hudson.model.StringParameterValue", + "name": "TARGET_REPO_NAME", + "value": "node" + }, + { + "_class": "hudson.model.StringParameterValue", + "name": "PR_ID", + "value": "34761" + }, + { + "_class": "hudson.model.StringParameterValue", + "name": "REBASE_ONTO", + "value": "" + }, + { + "_class": "com.wangyin.parameter.WHideParameterValue", + "name": "DESCRIPTION_SETTER_DESCRIPTION", + "value": "\"\"" + } + ] + }, + { + "_class": "hudson.model.CauseAction", + "causes": [ + { + "_class": "hudson.model.Cause$UserIdCause", + "shortDescription": "Started by user mary marchini", + "userId": "mmarchini", + "userName": "mary marchini" + } + ] + }, + {}, + {}, + {}, + {}, + { + "_class": "hudson.plugins.git.util.BuildData", + "buildsByBranchName": { + "refs/remotes/origin/_jenkins_local_branch": { + "_class": "hudson.plugins.git.util.Build", + "buildNumber": 32777, + "buildResult": null, + "marked": { + "SHA1": "fd987b66ed783f78aff1785eecfc1b0b1d92075f", + "branch": [ + { + "SHA1": "fd987b66ed783f78aff1785eecfc1b0b1d92075f", + "name": "refs/remotes/origin/_jenkins_local_branch" + } + ] + }, + "revision": { + "SHA1": "fd987b66ed783f78aff1785eecfc1b0b1d92075f", + "branch": [ + { + "SHA1": "fd987b66ed783f78aff1785eecfc1b0b1d92075f", + "name": "refs/remotes/origin/_jenkins_local_branch" + } + ] + } + } + }, + "lastBuiltRevision": { + "SHA1": "fd987b66ed783f78aff1785eecfc1b0b1d92075f", + "branch": [ + { + "SHA1": "fd987b66ed783f78aff1785eecfc1b0b1d92075f", + "name": "refs/remotes/origin/_jenkins_local_branch" + } + ] + }, + "remoteUrls": [ + "git@github.com:$TARGET_GITHUB_ORG/$TARGET_REPO_NAME.git" + ], + "scmName": "" + }, + { + "_class": "hudson.plugins.git.GitTagAction" + }, + {}, + {}, + {}, + {}, + { + "_class": "hudson.model.ParametersAction", + "parameters": [ + { + "_class": "hudson.model.StringParameterValue", + "name": "DESCRIPTION_SETTER_DESCRIPTION", + "value": "Testing 34761" + } + ] + }, + { + "_class": "hudson.plugins.parameterizedtrigger.BuildInfoExporterAction" + }, + { + "_class": "com.tikal.jenkins.plugins.multijob.MultiJobTestResults", + "failCount": 0, + "skipCount": 0, + "totalCount": 0, + "urlName": "testReport" + }, + {}, + {}, + {}, + {} + ], + "artifacts": [], + "building": true, + "description": "Testing 34761", + "displayName": "#32777", + "duration": 0, + "estimatedDuration": 1565270, + "executor": {}, + "fullDisplayName": "node-test-pull-request #32777", + "id": "32777", + "keepLog": false, + "number": 32777, + "queueId": 936970, + "result": null, + "timestamp": 1597377490175, + "url": "https://ci.nodejs.org/job/node-test-pull-request/32777/", + "builtOn": "test-ibm-ubuntu1804-x64-1", + "changeSet": { + "_class": "hudson.plugins.git.GitChangeSetList", + "items": [ + { + "_class": "hudson.plugins.git.GitChangeSet", + "affectedPaths": [ + "test/fuzzers/fuzz_url.cc", + "configure.py", + "node.gyp" + ], + "commitId": "fd987b66ed783f78aff1785eecfc1b0b1d92075f", + "timestamp": 1597352807000, + "author": { + "absoluteUrl": "https://ci.nodejs.org/user/david", + "fullName": "david" + }, + "authorEmail": "david@adalogics.com", + "comment": "Resolved suggestions for improvements in oss-fuzz infrastructure.\n", + "date": "2020-08-13 22:06:47 +0100", + "id": "fd987b66ed783f78aff1785eecfc1b0b1d92075f", + "msg": "Resolved suggestions for improvements in oss-fuzz infrastructure.", + "paths": [ + { + "editType": "edit", + "file": "configure.py" + }, + { + "editType": "edit", + "file": "test/fuzzers/fuzz_url.cc" + }, + { + "editType": "edit", + "file": "node.gyp" + } + ] + } + ], + "kind": "git" + }, + "culprits": [ + { + "absoluteUrl": "https://ci.nodejs.org/user/david", + "fullName": "david" + }, + { + "absoluteUrl": "https://ci.nodejs.org/user/oyyd", + "fullName": "Ouyang Yadong" + }, + { + "absoluteUrl": "https://ci.nodejs.org/user/trott", + "fullName": "Rich Trott" + } + ], + "subBuilds": [ + { + "abort": false, + "build": { + "_class": "com.tikal.jenkins.plugins.multijob.MultiJobBuild", + "subBuilds": [ + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 34989, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-freebsd", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-freebsd/34989/" + }, + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 35763, + "duration": "24 sec and counting", + "icon": "red_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-osx", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-osx/35763/" + }, + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 36592, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-linux", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-linux/36592/" + }, + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 34386, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-smartos", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-smartos/34386/" + }, + { + "abort": false, + "build": { + "_class": "hudson.model.FreeStyleBuild" + }, + "buildNumber": 36264, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-linter", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-linter/36264/" + }, + { + "abort": false, + "build": { + "_class": "hudson.model.FreeStyleBuild" + }, + "buildNumber": 15923, + "duration": "25 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "test-worker", + "jobName": "node-test-commit-custom-suites-freestyle", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-custom-suites-freestyle/15923/" + }, + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 34459, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-plinux", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-plinux/34459/" + }, + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 21826, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-linux-containered", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-linux-containered/21826/" + }, + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 32917, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-arm", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-arm/32917/" + }, + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 22507, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-linuxone", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-linuxone/22507/" + }, + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 32069, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-aix", + "multiJobBuild": false, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-aix/32069/" + }, + { + "abort": false, + "build": { + "_class": "com.tikal.jenkins.plugins.multijob.MultiJobBuild", + "subBuilds": [ + { + "abort": false, + "build": { + "_class": "hudson.model.FreeStyleBuild" + }, + "buildNumber": 68259, + "duration": "10 sec", + "icon": "blue.png", + "jobAlias": "", + "jobName": "git-rebase", + "multiJobBuild": false, + "parentBuildNumber": 37751, + "parentJobName": "node-test-commit-windows-fanned", + "phaseName": "Git rebase", + "result": "SUCCESS", + "retry": false, + "url": "job/git-rebase/68259/" + }, + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 35529, + "duration": "9.9 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-compile-windows", + "multiJobBuild": false, + "parentBuildNumber": 37751, + "parentJobName": "node-test-commit-windows-fanned", + "phaseName": "Compilation", + "result": null, + "retry": false, + "url": "job/node-compile-windows/35529/" + }, + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 4173, + "duration": "10 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-compile-windows-debug", + "multiJobBuild": false, + "parentBuildNumber": 37751, + "parentJobName": "node-test-commit-windows-fanned", + "phaseName": "Compilation", + "result": null, + "retry": false, + "url": "job/node-compile-windows-debug/4173/" + } + ] + }, + "buildNumber": 37751, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-windows-fanned", + "multiJobBuild": true, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-windows-fanned/37751/" + }, + { + "abort": false, + "build": { + "_class": "com.tikal.jenkins.plugins.multijob.MultiJobBuild", + "subBuilds": [ + { + "abort": false, + "build": { + "_class": "hudson.matrix.MatrixBuild" + }, + "buildNumber": 30143, + "duration": "5 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-cross-compile", + "multiJobBuild": false, + "parentBuildNumber": 15899, + "parentJobName": "node-test-commit-arm-fanned", + "phaseName": "Cross Compilation", + "result": null, + "retry": false, + "url": "job/node-cross-compile/30143/" + } + ] + }, + "buildNumber": 15899, + "duration": "24 sec and counting", + "icon": "blue_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit-arm-fanned", + "multiJobBuild": true, + "parentBuildNumber": 40219, + "parentJobName": "node-test-commit", + "phaseName": "Tests", + "result": null, + "retry": false, + "url": "job/node-test-commit-arm-fanned/15899/" + } + ] + }, + "buildNumber": 40219, + "duration": "32 sec and counting", + "icon": "red_anime.gif", + "jobAlias": "", + "jobName": "node-test-commit", + "multiJobBuild": true, + "parentBuildNumber": 32777, + "parentJobName": "node-test-pull-request", + "phaseName": "", + "result": null, + "retry": false, + "url": "job/node-test-commit/40219/" + } + ] +} diff --git a/test/fixtures/pull_requests/doc-only.json b/test/fixtures/pull_requests/doc-only.json new file mode 100644 index 00000000..8f0e42f2 --- /dev/null +++ b/test/fixtures/pull_requests/doc-only.json @@ -0,0 +1,50 @@ +{ + "createdAt": "2020-08-12T10:54:02Z", + "authorAssociation": "CONTRIBUTOR", + "author": { + "login": "aduh95", + "email": "", + "name": "Antoine du Hamel" + }, + "url": "https://github.com/nodejs/node/pull/34748", + "bodyHTML": "

This part of the docs aims to contain documentation regarding package configuration that covers both ESM and CJS realms.

\n

Refs: nodejs/modules#539

\n\n
Checklist
\n\n\n", + "bodyText": "This part of the docs aims to contain documentation regarding package configuration that covers both ESM and CJS realms.\nRefs: nodejs/modules#539\n\nChecklist\n\n\n make -j4 test (UNIX), or vcbuild test (Windows) passes\n documentation is changed or added\n commit message follows commit guidelines", + "labels": { + "nodes": [ + { + "name": "ES Modules" + }, + { + "name": "doc" + }, + { + "name": "tools" + } + ] + }, + "files": { + "nodes": [ + { + "path": "doc/api/errors.md" + }, + { + "path": "doc/api/esm.md" + }, + { + "path": "doc/api/index.md" + }, + { + "path": "doc/api/package_json.md" + } + ] + }, + "title": "doc: move package config docs to separate page", + "baseRefName": "master", + "headRefName": "doc-package-config", + "changedFiles": 4, + "mergeable": "MERGEABLE", + "closed": false, + "closedAt": null, + "merged": false, + "mergedAt": null +} diff --git a/test/unit/pr_checker.test.js b/test/unit/pr_checker.test.js index 8fca0f21..134015a6 100644 --- a/test/unit/pr_checker.test.js +++ b/test/unit/pr_checker.test.js @@ -7,6 +7,8 @@ const TestCLI = require('../fixtures/test_cli'); const PRData = require('../../lib/pr_data'); const PRChecker = require('../../lib/pr_checker'); +const { jobCache } = require('../../lib/ci/build-types/job'); +jobCache.disable(); const GT_7D = '2018-11-23T17:50:44.477Z'; const LT_7D_GT_48H = '2018-11-27T17:50:44.477Z'; @@ -20,10 +22,14 @@ const { requestedChangesReviewers, approvingReviews, githubCI, + jenkinsCI, requestingChangesReviews, noReviewers, commentsWithCI, + commentsWithFailedCI, commentsWithLGTM, + commentsWithSuccessCI, + commentsWithPendingCI, singleCommitAfterReview, multipleCommitsAfterReview, moreThanThreeCommitsAfterReview, @@ -38,7 +44,8 @@ const { semverMajorPR, conflictingPR, closedPR, - mergedPR + mergedPR, + pullRequests } = require('../fixtures/data'); const argv = { maxCommits: 3 }; @@ -614,12 +621,428 @@ describe('PRChecker', () => { }); describe('checkCI', () => { + it('should error if invalid CI', async() => { + const cli = new TestCLI(); + + const data = { + pr: firstTimerPR, + reviewers: allGreenReviewers, + comments: commentsWithLGTM, + reviews: approvingReviews, + commits: simpleCommits, + collaborators, + authorIsNew: () => true, + getThread() { + return PRData.prototype.getThread.call(this); + } + }; + const checker = new PRChecker( + cli, + data, + {}, + { ...argv, ciType: 'invalid' }); + + cli.clearCalls(); + const status = await checker.checkCI(); + assert(!status); + }); + + it('should fail if doc-only changes without Actions', async() => { + const cli = new TestCLI(); + + const expectedLogs = { + error: [ + ['No GitHub CI runs detected'] + ], + info: [ + ['Doc-only changes'] + ] + }; + + const data = { + pr: pullRequests['doc-only'], + reviewers: allGreenReviewers, + comments: [], + reviews: approvingReviews, + commits: simpleCommits, + collaborators, + authorIsNew: () => true, + getThread() { + return PRData.prototype.getThread.call(this); + } + }; + const checker = new PRChecker( + cli, + data, + { + json: sinon.stub().callsFake(await function(url) { + return undefined; + }) + }, + argv); + + cli.clearCalls(); + const status = await checker.checkCI(); + assert(!status); + cli.assertCalledWith(expectedLogs, { + ignore: ['startSpinner', 'updateSpinner', 'stopSpinner'] + }); + }); + + it('should succeed if doc-only changes without Jenkins', async() => { + const cli = new TestCLI(); + + const expectedLogs = { + ok: [ + ['Last GitHub Actions successful'] + ], + info: [ + ['Doc-only changes'] + ] + }; + + const data = { + pr: pullRequests['doc-only'], + reviewers: allGreenReviewers, + comments: [], + reviews: approvingReviews, + commits: githubCI['check-suite-success'], + collaborators, + authorIsNew: () => true, + getThread() { + return PRData.prototype.getThread.call(this); + } + }; + const checker = new PRChecker( + cli, + data, + { + json: sinon.stub().callsFake(await function() { + return undefined; + }) + }, + argv); + + cli.clearCalls(); + const status = await checker.checkCI(); + assert(status); + cli.assertCalledWith(expectedLogs, { + ignore: ['startSpinner', 'updateSpinner', 'stopSpinner'] + }); + }); + + it('should succeed if doc-only changes with failed Jenkins', async() => { + const cli = new TestCLI(); + + const jenkins = jenkinsCI['trigger-failure/node-test-pull-request-15442']; + + const expectedLogs = { + ok: [ + ['Last GitHub Actions successful'] + ], + info: [ + ['Doc-only changes'] + ] + }; + + const data = { + pr: pullRequests['doc-only'], + reviewers: allGreenReviewers, + comments: commentsWithFailedCI, + reviews: approvingReviews, + commits: githubCI['check-suite-success'], + collaborators, + authorIsNew: () => true, + getThread() { + return PRData.prototype.getThread.call(this); + } + }; + const checker = new PRChecker( + cli, + data, + { + json: sinon.stub().callsFake(await function(url) { + if (!url.startsWith(jenkins.url)) { + // Throwing won't fail the checkCI call, but returning undefined + // will. + return undefined; + } + return jenkins; + }) + }, + argv); + + cli.clearCalls(); + const status = await checker.checkCI(); + assert(status); + cli.assertCalledWith(expectedLogs, { + ignore: ['startSpinner', 'updateSpinner', 'stopSpinner'] + }); + }); + + it('should error if pending Jenkins CI', async() => { + const cli = new TestCLI(); + + const jenkins = jenkinsCI['pending/node-test-pull-request-32777']; + + const expectedLogs = { + error: [ + ['No GitHub CI runs detected'], + ['Last Jenkins CI still running'] + ], + info: [ + [`Last Full PR CI on 2018-10-22T04:16:36.458Z: ${jenkins.url}`] + ] + }; + + const data = { + pr: firstTimerPR, + reviewers: allGreenReviewers, + comments: commentsWithPendingCI, + reviews: approvingReviews, + commits: simpleCommits, + collaborators, + authorIsNew: () => true, + getThread() { + return PRData.prototype.getThread.call(this); + } + }; + const checker = new PRChecker( + cli, + data, + { + json: sinon.stub().callsFake(await function(url) { + if (!url.startsWith(jenkins.url)) { + // Throwing won't fail the checkCI call, but returning undefined + // will. + return undefined; + } + return jenkins; + }) + }, + argv); + + cli.clearCalls(); + const status = await checker.checkCI(); + assert(!status); + cli.assertCalledWith(expectedLogs, { + ignore: ['startSpinner', 'updateSpinner', 'stopSpinner'] + }); + }); + + it('should error if pending Actions', async() => { + const cli = new TestCLI(); + + const jenkins = jenkinsCI['success/node-test-pull-request-15237']; + + const expectedLogs = { + ok: [ + ['Last Jenkins CI successful'] + ], + error: [ + ['GitHub CI is still running'] + ], + info: [ + [`Last Full PR CI on 2018-10-22T04:16:36.458Z: ${jenkins.url}`] + ] + }; + + const data = { + pr: firstTimerPR, + reviewers: allGreenReviewers, + comments: commentsWithSuccessCI, + reviews: approvingReviews, + commits: githubCI['check-suite-pending'], + collaborators, + authorIsNew: () => true, + getThread() { + return PRData.prototype.getThread.call(this); + } + }; + const checker = new PRChecker( + cli, + data, + { + json: sinon.stub().callsFake(await function(url) { + if (!url.startsWith(jenkins.url)) { + // Throwing won't fail the checkCI call, but returning undefined + // will. + return undefined; + } + return jenkins; + }) + }, + argv); + + cli.clearCalls(); + const status = await checker.checkCI(); + assert(!status); + cli.assertCalledWith(expectedLogs, { + ignore: ['startSpinner', 'updateSpinner', 'stopSpinner'] + }); + }); + + it('should error if failed Actions', async() => { + const cli = new TestCLI(); + + const jenkins = jenkinsCI['success/node-test-pull-request-15237']; + + const expectedLogs = { + ok: [ + ['Last Jenkins CI successful'] + ], + error: [ + ['Last GitHub CI failed'] + ], + info: [ + [`Last Full PR CI on 2018-10-22T04:16:36.458Z: ${jenkins.url}`] + ] + }; + + const data = { + pr: firstTimerPR, + reviewers: allGreenReviewers, + comments: commentsWithSuccessCI, + reviews: approvingReviews, + commits: githubCI['check-suite-failure'], + collaborators, + authorIsNew: () => true, + getThread() { + return PRData.prototype.getThread.call(this); + } + }; + const checker = new PRChecker( + cli, + data, + { + json: sinon.stub().callsFake(await function(url) { + if (!url.startsWith(jenkins.url)) { + // Throwing won't fail the checkCI call, but returning undefined + // will. + return undefined; + } + return jenkins; + }) + }, + argv); + + cli.clearCalls(); + const status = await checker.checkCI(); + assert(!status); + cli.assertCalledWith(expectedLogs, { + ignore: ['startSpinner', 'updateSpinner', 'stopSpinner'] + }); + }); + + it('should succeed if both CIs succeed', async() => { + const cli = new TestCLI(); + + const jenkins = jenkinsCI['success/node-test-pull-request-15237']; + + const expectedLogs = { + ok: [ + ['Last GitHub Actions successful'], + ['Last Jenkins CI successful'] + ], + error: [ + ], + info: [ + [`Last Full PR CI on 2018-10-22T04:16:36.458Z: ${jenkins.url}`] + ] + }; + + const data = { + pr: firstTimerPR, + reviewers: allGreenReviewers, + comments: commentsWithSuccessCI, + reviews: approvingReviews, + commits: githubCI['check-suite-success'], + collaborators, + authorIsNew: () => true, + getThread() { + return PRData.prototype.getThread.call(this); + } + }; + const checker = new PRChecker( + cli, + data, + { + json: sinon.stub().callsFake(await function(url) { + if (!url.startsWith(jenkins.url)) { + // Throwing won't fail the checkCI call, but returning undefined + // will. + return undefined; + } + return jenkins; + }) + }, + argv); + + cli.clearCalls(); + const status = await checker.checkCI(); + assert(status); + cli.assertCalledWith(expectedLogs, { + ignore: ['startSpinner', 'updateSpinner', 'stopSpinner'] + }); + }); + + it('should error if failed Jenkins CI', async() => { + const cli = new TestCLI(); + + const jenkins = jenkinsCI['trigger-failure/node-test-pull-request-15442']; + + const expectedLogs = { + error: [ + ['No GitHub CI runs detected'], + ['1 failure(s) on the last Jenkins CI run'] + ], + info: [ + [`Last Full PR CI on 2018-10-22T04:16:36.458Z: ${jenkins.url}`] + ] + }; + + const data = { + pr: firstTimerPR, + reviewers: allGreenReviewers, + comments: commentsWithFailedCI, + reviews: approvingReviews, + commits: simpleCommits, + collaborators, + authorIsNew: () => true, + getThread() { + return PRData.prototype.getThread.call(this); + } + }; + const checker = new PRChecker( + cli, + data, + { + json: sinon.stub().callsFake(await function(url) { + if (!url.startsWith(jenkins.url)) { + // Throwing won't fail the checkCI call, but returning undefined + // will. + return undefined; + } + return jenkins; + }) + }, + argv); + + cli.clearCalls(); + const status = await checker.checkCI(); + assert(!status); + cli.assertCalledWith(expectedLogs, { + ignore: ['startSpinner', 'updateSpinner', 'stopSpinner'] + }); + }); + it('should error if no CI runs detected', async() => { const cli = new TestCLI(); const expectedLogs = { error: [ - ['No CI runs detected'] + ['No GitHub CI runs detected'], + ['No Jenkins CI runs detected'] ] }; @@ -646,6 +1069,12 @@ describe('PRChecker', () => { const cli = new TestCLI(); const expectedLogs = { + ok: [ + ['Last Jenkins CI successful'] + ], + error: [ + ['No commits detected'] + ], info: [ [ 'Last Full PR CI on 2017-10-25T04:16:36.458Z: ' + @@ -693,7 +1122,7 @@ describe('PRChecker', () => { const checker = new PRChecker(cli, data, {}, argv); const status = await checker.checkCI(); - assert(status); + assert(!status); cli.assertCalledWith(expectedLogs, { ignore: ['startSpinner', 'updateSpinner', 'stopSpinner'] }); @@ -712,6 +1141,12 @@ describe('PRChecker', () => { ], info: [ ['Last Full PR CI on 2017-10-24T11:19:25Z: https://ci.nodejs.org/job/node-test-pull-request/10984/'] + ], + ok: [ + ['Last Jenkins CI successful'] + ], + error: [ + ['No GitHub CI runs detected'] ] }; @@ -754,7 +1189,12 @@ describe('PRChecker', () => { 'https://ci.nodejs.org/job/node-test-pull-request/12984/' ] ], - error: [] + ok: [ + ['Last Jenkins CI successful'] + ], + error: [ + ['No GitHub CI runs detected'] + ] }; const checker = new PRChecker(cli, { @@ -792,7 +1232,12 @@ describe('PRChecker', () => { 'https://ci.nodejs.org/job/node-test-pull-request/12984/' ] ], - error: [] + ok: [ + ['Last Jenkins CI successful'] + ], + error: [ + ['No GitHub CI runs detected'] + ] }; const checker = new PRChecker(cli, { @@ -835,7 +1280,7 @@ describe('PRChecker', () => { const expectedLogs = { error: [ - ['No CI runs detected'] + ['No GitHub CI runs detected'] ] }; @@ -1044,7 +1489,7 @@ describe('PRChecker', () => { const expectedLogs = { error: [ - ['No CI runs detected'] + ['No GitHub CI runs detected'] ] };