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'] ] };