Merge pull request #363 from github/simon-engledew/fix-incorrect-branch-warning

Fix overzealous warnings when PR scanning is not required
This commit is contained in:
Simon Engledew 2021-01-15 10:59:19 +00:00 committed by GitHub
commit 4bdcd08344
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 412 additions and 264 deletions

61
lib/actions-util.js generated
View file

@ -186,37 +186,27 @@ function validateWorkflow(doc) {
}
let missing = MissingTriggers.None;
if (doc.on === undefined) {
missing = MissingTriggers.Push | MissingTriggers.PullRequest;
// this is not a valid config
}
else if (typeof doc.on === "string") {
switch (doc.on) {
case "push":
missing = MissingTriggers.PullRequest;
break;
case "pull_request":
missing = MissingTriggers.Push;
break;
default:
missing = MissingTriggers.Push | MissingTriggers.PullRequest;
break;
if (doc.on === "pull_request") {
missing = MissingTriggers.Push;
}
}
else if (Array.isArray(doc.on)) {
if (!doc.on.includes("push")) {
const hasPush = doc.on.includes("push");
const hasPullRequest = doc.on.includes("pull_request");
if (hasPullRequest && !hasPush) {
missing = missing | MissingTriggers.Push;
}
if (!doc.on.includes("pull_request")) {
missing = missing | MissingTriggers.PullRequest;
}
}
else if (isObject(doc.on)) {
if (!Object.prototype.hasOwnProperty.call(doc.on, "pull_request")) {
missing = missing | MissingTriggers.PullRequest;
}
if (!Object.prototype.hasOwnProperty.call(doc.on, "push")) {
const hasPush = Object.prototype.hasOwnProperty.call(doc.on, "push");
const hasPullRequest = Object.prototype.hasOwnProperty.call(doc.on, "pull_request");
if (!hasPush && hasPullRequest) {
missing = missing | MissingTriggers.Push;
}
else {
if (hasPush && hasPullRequest) {
const paths = (_e = doc.on.push) === null || _e === void 0 ? void 0 : _e.paths;
// if you specify paths or paths-ignore you can end up with commits that have no baseline
// if they didn't change any files
@ -229,22 +219,27 @@ function validateWorkflow(doc) {
errors.push(exports.WorkflowErrors.PathsIgnoreSpecified);
}
}
const push = branchesToArray((_g = doc.on.push) === null || _g === void 0 ? void 0 : _g.branches);
if (push !== "**") {
const pull_request = branchesToArray((_h = doc.on.pull_request) === null || _h === void 0 ? void 0 : _h.branches);
if (pull_request !== "**") {
const difference = pull_request.filter((value) => !push.some((o) => patternIsSuperset(o, value)));
if (difference.length > 0) {
// there are branches in pull_request that may not have a baseline
// because we are not building them on push
// if doc.on.pull_request is null that means 'all branches'
// if doc.on.pull_request is undefined that means 'off'
// we only want to check for mismatched branches if pull_request is on.
if (doc.on.pull_request !== undefined) {
const push = branchesToArray((_g = doc.on.push) === null || _g === void 0 ? void 0 : _g.branches);
if (push !== "**") {
const pull_request = branchesToArray((_h = doc.on.pull_request) === null || _h === void 0 ? void 0 : _h.branches);
if (pull_request !== "**") {
const difference = pull_request.filter((value) => !push.some((o) => patternIsSuperset(o, value)));
if (difference.length > 0) {
// there are branches in pull_request that may not have a baseline
// because we are not building them on push
errors.push(exports.WorkflowErrors.MismatchedBranches);
}
}
else if (push.length > 0) {
// push is set up to run on a subset of branches
// and you could open a PR against a branch with no baseline
errors.push(exports.WorkflowErrors.MismatchedBranches);
}
}
else if (push.length > 0) {
// push is set up to run on a subset of branches
// and you could open a PR against a branch with no baseline
errors.push(exports.WorkflowErrors.MismatchedBranches);
}
}
}
else {

File diff suppressed because one or more lines are too long

154
lib/actions-util.test.js generated
View file

@ -15,6 +15,9 @@ const yaml = __importStar(require("js-yaml"));
const sinon_1 = __importDefault(require("sinon"));
const actionsutil = __importStar(require("./actions-util"));
const testing_utils_1 = require("./testing-utils");
function errorCodes(actual, expected) {
return [actual.map(({ code }) => code), expected.map(({ code }) => code)];
}
testing_utils_1.setupTests(ava_1.default);
ava_1.default("getRef() throws on the empty string", async (t) => {
process.env["GITHUB_REF"] = "";
@ -69,34 +72,29 @@ ava_1.default("prepareEnvironment() when a local run", (t) => {
t.deepEqual(process.env.GITHUB_JOB, "UNKNOWN-JOB");
t.deepEqual(process.env.CODEQL_ACTION_ANALYSIS_KEY, "LOCAL-RUN:UNKNOWN-JOB");
});
ava_1.default("validateWorkflow() when on is missing", (t) => {
const errors = actionsutil.validateWorkflow({});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MissingHooks]);
});
ava_1.default("validateWorkflow() when on.push is missing", (t) => {
ava_1.default("validateWorkflow() when on is empty", (t) => {
const errors = actionsutil.validateWorkflow({ on: {} });
console.log(errors);
t.deepEqual(errors, [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on.push is an array missing pull_request", (t) => {
const errors = actionsutil.validateWorkflow({ on: ["push"] });
t.deepEqual(errors, [actionsutil.WorkflowErrors.MissingPullRequestHook]);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on.push is an array missing push", (t) => {
const errors = actionsutil.validateWorkflow({ on: ["pull_request"] });
t.deepEqual(errors, [actionsutil.WorkflowErrors.MissingPushHook]);
t.deepEqual(...errorCodes(errors, [actionsutil.WorkflowErrors.MissingPushHook]));
});
ava_1.default("validateWorkflow() when on.push is valid", (t) => {
const errors = actionsutil.validateWorkflow({
on: ["push", "pull_request"],
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on.push is a valid superset", (t) => {
const errors = actionsutil.validateWorkflow({
on: ["push", "pull_request", "schedule"],
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on.push should not have a path", (t) => {
const errors = actionsutil.validateWorkflow({
@ -105,31 +103,33 @@ ava_1.default("validateWorkflow() when on.push should not have a path", (t) => {
pull_request: { branches: ["main"] },
},
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.PathsSpecified]);
t.deepEqual(...errorCodes(errors, [actionsutil.WorkflowErrors.PathsSpecified]));
});
ava_1.default("validateWorkflow() when on.push is a correct object", (t) => {
const errors = actionsutil.validateWorkflow({
on: { push: { branches: ["main"] }, pull_request: { branches: ["main"] } },
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on.pull_requests is a string", (t) => {
const errors = actionsutil.validateWorkflow({
on: { push: { branches: ["main"] }, pull_request: { branches: "*" } },
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MismatchedBranches]);
t.deepEqual(...errorCodes(errors, [actionsutil.WorkflowErrors.MismatchedBranches]));
});
ava_1.default("validateWorkflow() when on.pull_requests is a string and correct", (t) => {
const errors = actionsutil.validateWorkflow({
on: { push: { branches: "*" }, pull_request: { branches: "*" } },
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on.push is correct with empty objects", (t) => {
const errors = actionsutil.validateWorkflow({
on: { push: undefined, pull_request: undefined },
});
t.deepEqual(errors, []);
const errors = actionsutil.validateWorkflow(yaml.safeLoad(`
on:
push:
pull_request:
`));
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on.push is mismatched", (t) => {
const errors = actionsutil.validateWorkflow({
@ -138,7 +138,7 @@ ava_1.default("validateWorkflow() when on.push is mismatched", (t) => {
pull_request: { branches: ["feature"] },
},
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MismatchedBranches]);
t.deepEqual(...errorCodes(errors, [actionsutil.WorkflowErrors.MismatchedBranches]));
});
ava_1.default("validateWorkflow() when on.push is not mismatched", (t) => {
const errors = actionsutil.validateWorkflow({
@ -147,7 +147,7 @@ ava_1.default("validateWorkflow() when on.push is not mismatched", (t) => {
pull_request: { branches: ["main"] },
},
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on.push is mismatched for pull_request", (t) => {
const errors = actionsutil.validateWorkflow({
@ -156,54 +156,52 @@ ava_1.default("validateWorkflow() when on.push is mismatched for pull_request",
pull_request: { branches: ["main", "feature"] },
},
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MismatchedBranches]);
t.deepEqual(...errorCodes(errors, [actionsutil.WorkflowErrors.MismatchedBranches]));
});
ava_1.default("validateWorkflow() for a range of malformed workflows", (t) => {
t.deepEqual(actionsutil.validateWorkflow({
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: {
push: 1,
pull_request: 1,
},
}), []);
t.deepEqual(actionsutil.validateWorkflow({
}), []));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: 1,
}), [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(actionsutil.validateWorkflow({
}), [actionsutil.WorkflowErrors.MissingHooks]));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: 1,
jobs: 1,
}), [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(actionsutil.validateWorkflow({
}), [actionsutil.WorkflowErrors.MissingHooks]));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: 1,
jobs: [1],
}), [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(actionsutil.validateWorkflow({
}), [actionsutil.WorkflowErrors.MissingHooks]));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: 1,
jobs: { 1: 1 },
}), [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(actionsutil.validateWorkflow({
}), [actionsutil.WorkflowErrors.MissingHooks]));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: 1,
jobs: { test: 1 },
}), [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(actionsutil.validateWorkflow({
}), [actionsutil.WorkflowErrors.MissingHooks]));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: 1,
jobs: { test: [1] },
}), [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(actionsutil.validateWorkflow({
}), [actionsutil.WorkflowErrors.MissingHooks]));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: 1,
jobs: { test: { steps: 1 } },
}), [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(actionsutil.validateWorkflow({
}), [actionsutil.WorkflowErrors.MissingHooks]));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: 1,
jobs: { test: { steps: [{ notrun: "git checkout HEAD^2" }] } },
}), [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(actionsutil.validateWorkflow({
}), [actionsutil.WorkflowErrors.MissingHooks]));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: 1,
jobs: { test: [undefined] },
}), [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(actionsutil.validateWorkflow(1), [
actionsutil.WorkflowErrors.MissingHooks,
]);
t.deepEqual(actionsutil.validateWorkflow({
}), [actionsutil.WorkflowErrors.MissingHooks]));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow(1), []));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow({
on: {
push: {
branches: 1,
@ -212,16 +210,17 @@ ava_1.default("validateWorkflow() for a range of malformed workflows", (t) => {
branches: 1,
},
},
}), []);
}), []));
});
ava_1.default("validateWorkflow() when on.pull_request for every branch but push specifies branches", (t) => {
const errors = actionsutil.validateWorkflow({
on: {
push: { branches: ["main"] },
pull_request: null,
},
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MismatchedBranches]);
const errors = actionsutil.validateWorkflow(yaml.safeLoad(`
name: "CodeQL"
on:
push:
branches: ["main"]
pull_request:
`));
t.deepEqual(...errorCodes(errors, [actionsutil.WorkflowErrors.MismatchedBranches]));
});
ava_1.default("validateWorkflow() when on.pull_request for wildcard branches", (t) => {
const errors = actionsutil.validateWorkflow({
@ -230,7 +229,7 @@ ava_1.default("validateWorkflow() when on.pull_request for wildcard branches", (
pull_request: { branches: "feature/moose" },
},
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on.pull_request for mismatched wildcard branches", (t) => {
const errors = actionsutil.validateWorkflow({
@ -239,7 +238,7 @@ ava_1.default("validateWorkflow() when on.pull_request for mismatched wildcard b
pull_request: { branches: "feature/*" },
},
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MismatchedBranches]);
t.deepEqual(...errorCodes(errors, [actionsutil.WorkflowErrors.MismatchedBranches]));
});
ava_1.default("validateWorkflow() when HEAD^2 is checked out", (t) => {
process.env.GITHUB_JOB = "test";
@ -247,7 +246,7 @@ ava_1.default("validateWorkflow() when HEAD^2 is checked out", (t) => {
on: ["push", "pull_request"],
jobs: { test: { steps: [{ run: "git checkout HEAD^2" }] } },
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.CheckoutWrongHead]);
t.deepEqual(...errorCodes(errors, [actionsutil.WorkflowErrors.CheckoutWrongHead]));
});
ava_1.default("formatWorkflowErrors() when there is one error", (t) => {
const message = actionsutil.formatWorkflowErrors([
@ -301,7 +300,7 @@ ava_1.default("validateWorkflow() when branches contain dots", (t) => {
# The branches below must be a subset of the branches above
branches: [4.1, master]
`));
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on.push has a trailing comma", (t) => {
const errors = actionsutil.validateWorkflow(yaml.safeLoad(`
@ -313,7 +312,7 @@ on:
# The branches below must be a subset of the branches above
branches: [master]
`));
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() should only report the current job's CheckoutWrongHead", (t) => {
process.env.GITHUB_JOB = "test";
@ -337,7 +336,7 @@ jobs:
test3:
steps: []
`));
t.deepEqual(errors, [actionsutil.WorkflowErrors.CheckoutWrongHead]);
t.deepEqual(...errorCodes(errors, [actionsutil.WorkflowErrors.CheckoutWrongHead]));
});
ava_1.default("validateWorkflow() should not report a different job's CheckoutWrongHead", (t) => {
process.env.GITHUB_JOB = "test3";
@ -361,6 +360,39 @@ jobs:
test3:
steps: []
`));
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() when on is missing", (t) => {
const errors = actionsutil.validateWorkflow(yaml.safeLoad(`
name: "CodeQL"
`));
t.deepEqual(...errorCodes(errors, []));
});
ava_1.default("validateWorkflow() with a different on setup", (t) => {
t.deepEqual(...errorCodes(actionsutil.validateWorkflow(yaml.safeLoad(`
name: "CodeQL"
on: "workflow_dispatch"
`)), []));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow(yaml.safeLoad(`
name: "CodeQL"
on: [workflow_dispatch]
`)), []));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow(yaml.safeLoad(`
name: "CodeQL"
on:
workflow_dispatch: {}
`)), []));
});
ava_1.default("validateWorkflow() should not report an error if PRs are totally unconfigured", (t) => {
t.deepEqual(...errorCodes(actionsutil.validateWorkflow(yaml.safeLoad(`
name: "CodeQL"
on:
push:
branches: [master]
`)), []));
t.deepEqual(...errorCodes(actionsutil.validateWorkflow(yaml.safeLoad(`
name: "CodeQL"
on: ["push"]
`)), []));
});
//# sourceMappingURL=actions-util.test.js.map

File diff suppressed because one or more lines are too long

View file

@ -5,6 +5,13 @@ import sinon from "sinon";
import * as actionsutil from "./actions-util";
import { setupTests } from "./testing-utils";
function errorCodes(
actual: actionsutil.CodedError[],
expected: actionsutil.CodedError[]
): [string[], string[]] {
return [actual.map(({ code }) => code), expected.map(({ code }) => code)];
}
setupTests(test);
test("getRef() throws on the empty string", async (t) => {
@ -83,30 +90,24 @@ test("prepareEnvironment() when a local run", (t) => {
t.deepEqual(process.env.CODEQL_ACTION_ANALYSIS_KEY, "LOCAL-RUN:UNKNOWN-JOB");
});
test("validateWorkflow() when on is missing", (t) => {
const errors = actionsutil.validateWorkflow({});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MissingHooks]);
});
test("validateWorkflow() when on.push is missing", (t) => {
test("validateWorkflow() when on is empty", (t) => {
const errors = actionsutil.validateWorkflow({ on: {} });
console.log(errors);
t.deepEqual(errors, [actionsutil.WorkflowErrors.MissingHooks]);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on.push is an array missing pull_request", (t) => {
const errors = actionsutil.validateWorkflow({ on: ["push"] });
t.deepEqual(errors, [actionsutil.WorkflowErrors.MissingPullRequestHook]);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on.push is an array missing push", (t) => {
const errors = actionsutil.validateWorkflow({ on: ["pull_request"] });
t.deepEqual(errors, [actionsutil.WorkflowErrors.MissingPushHook]);
t.deepEqual(
...errorCodes(errors, [actionsutil.WorkflowErrors.MissingPushHook])
);
});
test("validateWorkflow() when on.push is valid", (t) => {
@ -114,7 +115,7 @@ test("validateWorkflow() when on.push is valid", (t) => {
on: ["push", "pull_request"],
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on.push is a valid superset", (t) => {
@ -122,7 +123,7 @@ test("validateWorkflow() when on.push is a valid superset", (t) => {
on: ["push", "pull_request", "schedule"],
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on.push should not have a path", (t) => {
@ -133,7 +134,9 @@ test("validateWorkflow() when on.push should not have a path", (t) => {
},
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.PathsSpecified]);
t.deepEqual(
...errorCodes(errors, [actionsutil.WorkflowErrors.PathsSpecified])
);
});
test("validateWorkflow() when on.push is a correct object", (t) => {
@ -141,7 +144,7 @@ test("validateWorkflow() when on.push is a correct object", (t) => {
on: { push: { branches: ["main"] }, pull_request: { branches: ["main"] } },
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on.pull_requests is a string", (t) => {
@ -149,7 +152,9 @@ test("validateWorkflow() when on.pull_requests is a string", (t) => {
on: { push: { branches: ["main"] }, pull_request: { branches: "*" } },
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MismatchedBranches]);
t.deepEqual(
...errorCodes(errors, [actionsutil.WorkflowErrors.MismatchedBranches])
);
});
test("validateWorkflow() when on.pull_requests is a string and correct", (t) => {
@ -157,15 +162,19 @@ test("validateWorkflow() when on.pull_requests is a string and correct", (t) =>
on: { push: { branches: "*" }, pull_request: { branches: "*" } },
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on.push is correct with empty objects", (t) => {
const errors = actionsutil.validateWorkflow({
on: { push: undefined, pull_request: undefined },
});
const errors = actionsutil.validateWorkflow(
yaml.safeLoad(`
on:
push:
pull_request:
`)
);
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on.push is mismatched", (t) => {
@ -176,7 +185,9 @@ test("validateWorkflow() when on.push is mismatched", (t) => {
},
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MismatchedBranches]);
t.deepEqual(
...errorCodes(errors, [actionsutil.WorkflowErrors.MismatchedBranches])
);
});
test("validateWorkflow() when on.push is not mismatched", (t) => {
@ -187,7 +198,7 @@ test("validateWorkflow() when on.push is not mismatched", (t) => {
},
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on.push is mismatched for pull_request", (t) => {
@ -198,119 +209,146 @@ test("validateWorkflow() when on.push is mismatched for pull_request", (t) => {
},
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MismatchedBranches]);
t.deepEqual(
...errorCodes(errors, [actionsutil.WorkflowErrors.MismatchedBranches])
);
});
test("validateWorkflow() for a range of malformed workflows", (t) => {
t.deepEqual(
actionsutil.validateWorkflow({
on: {
push: 1,
pull_request: 1,
},
} as any),
[]
);
t.deepEqual(
actionsutil.validateWorkflow({
on: 1,
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
);
t.deepEqual(
actionsutil.validateWorkflow({
on: 1,
jobs: 1,
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
);
t.deepEqual(
actionsutil.validateWorkflow({
on: 1,
jobs: [1],
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
);
t.deepEqual(
actionsutil.validateWorkflow({
on: 1,
jobs: { 1: 1 },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
);
t.deepEqual(
actionsutil.validateWorkflow({
on: 1,
jobs: { test: 1 },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
);
t.deepEqual(
actionsutil.validateWorkflow({
on: 1,
jobs: { test: [1] },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
);
t.deepEqual(
actionsutil.validateWorkflow({
on: 1,
jobs: { test: { steps: 1 } },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
);
t.deepEqual(
actionsutil.validateWorkflow({
on: 1,
jobs: { test: { steps: [{ notrun: "git checkout HEAD^2" }] } },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
);
t.deepEqual(
actionsutil.validateWorkflow({
on: 1,
jobs: { test: [undefined] },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
);
t.deepEqual(actionsutil.validateWorkflow(1 as any), [
actionsutil.WorkflowErrors.MissingHooks,
]);
t.deepEqual(
actionsutil.validateWorkflow({
on: {
push: {
branches: 1,
...errorCodes(
actionsutil.validateWorkflow({
on: {
push: 1,
pull_request: 1,
},
pull_request: {
branches: 1,
} as any),
[]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow({
on: 1,
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow({
on: 1,
jobs: 1,
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow({
on: 1,
jobs: [1],
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow({
on: 1,
jobs: { 1: 1 },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow({
on: 1,
jobs: { test: 1 },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow({
on: 1,
jobs: { test: [1] },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow({
on: 1,
jobs: { test: { steps: 1 } },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow({
on: 1,
jobs: { test: { steps: [{ notrun: "git checkout HEAD^2" }] } },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow({
on: 1,
jobs: { test: [undefined] },
} as any),
[actionsutil.WorkflowErrors.MissingHooks]
)
);
t.deepEqual(...errorCodes(actionsutil.validateWorkflow(1 as any), []));
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow({
on: {
push: {
branches: 1,
},
pull_request: {
branches: 1,
},
},
},
} as any),
[]
} as any),
[]
)
);
});
test("validateWorkflow() when on.pull_request for every branch but push specifies branches", (t) => {
const errors = actionsutil.validateWorkflow({
on: {
push: { branches: ["main"] },
pull_request: null,
},
});
const errors = actionsutil.validateWorkflow(
yaml.safeLoad(`
name: "CodeQL"
on:
push:
branches: ["main"]
pull_request:
`)
);
t.deepEqual(errors, [actionsutil.WorkflowErrors.MismatchedBranches]);
t.deepEqual(
...errorCodes(errors, [actionsutil.WorkflowErrors.MismatchedBranches])
);
});
test("validateWorkflow() when on.pull_request for wildcard branches", (t) => {
@ -321,7 +359,7 @@ test("validateWorkflow() when on.pull_request for wildcard branches", (t) => {
},
});
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on.pull_request for mismatched wildcard branches", (t) => {
@ -332,7 +370,9 @@ test("validateWorkflow() when on.pull_request for mismatched wildcard branches",
},
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.MismatchedBranches]);
t.deepEqual(
...errorCodes(errors, [actionsutil.WorkflowErrors.MismatchedBranches])
);
});
test("validateWorkflow() when HEAD^2 is checked out", (t) => {
@ -343,7 +383,9 @@ test("validateWorkflow() when HEAD^2 is checked out", (t) => {
jobs: { test: { steps: [{ run: "git checkout HEAD^2" }] } },
});
t.deepEqual(errors, [actionsutil.WorkflowErrors.CheckoutWrongHead]);
t.deepEqual(
...errorCodes(errors, [actionsutil.WorkflowErrors.CheckoutWrongHead])
);
});
test("formatWorkflowErrors() when there is one error", (t) => {
@ -416,7 +458,7 @@ test("validateWorkflow() when branches contain dots", (t) => {
`)
);
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on.push has a trailing comma", (t) => {
@ -432,7 +474,7 @@ on:
`)
);
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() should only report the current job's CheckoutWrongHead", (t) => {
@ -461,7 +503,9 @@ jobs:
`)
);
t.deepEqual(errors, [actionsutil.WorkflowErrors.CheckoutWrongHead]);
t.deepEqual(
...errorCodes(errors, [actionsutil.WorkflowErrors.CheckoutWrongHead])
);
});
test("validateWorkflow() should not report a different job's CheckoutWrongHead", (t) => {
@ -490,5 +534,82 @@ jobs:
`)
);
t.deepEqual(errors, []);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() when on is missing", (t) => {
const errors = actionsutil.validateWorkflow(
yaml.safeLoad(`
name: "CodeQL"
`)
);
t.deepEqual(...errorCodes(errors, []));
});
test("validateWorkflow() with a different on setup", (t) => {
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow(
yaml.safeLoad(`
name: "CodeQL"
on: "workflow_dispatch"
`)
),
[]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow(
yaml.safeLoad(`
name: "CodeQL"
on: [workflow_dispatch]
`)
),
[]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow(
yaml.safeLoad(`
name: "CodeQL"
on:
workflow_dispatch: {}
`)
),
[]
)
);
});
test("validateWorkflow() should not report an error if PRs are totally unconfigured", (t) => {
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow(
yaml.safeLoad(`
name: "CodeQL"
on:
push:
branches: [master]
`)
),
[]
)
);
t.deepEqual(
...errorCodes(
actionsutil.validateWorkflow(
yaml.safeLoad(`
name: "CodeQL"
on: ["push"]
`)
),
[]
)
);
});

View file

@ -184,7 +184,7 @@ enum MissingTriggers {
PullRequest = 2,
}
interface CodedError {
export interface CodedError {
message: string;
code: string;
}
@ -236,33 +236,28 @@ export function validateWorkflow(doc: Workflow): CodedError[] {
let missing = MissingTriggers.None;
if (doc.on === undefined) {
missing = MissingTriggers.Push | MissingTriggers.PullRequest;
// this is not a valid config
} else if (typeof doc.on === "string") {
switch (doc.on) {
case "push":
missing = MissingTriggers.PullRequest;
break;
case "pull_request":
missing = MissingTriggers.Push;
break;
default:
missing = MissingTriggers.Push | MissingTriggers.PullRequest;
break;
if (doc.on === "pull_request") {
missing = MissingTriggers.Push;
}
} else if (Array.isArray(doc.on)) {
if (!doc.on.includes("push")) {
const hasPush = doc.on.includes("push");
const hasPullRequest = doc.on.includes("pull_request");
if (hasPullRequest && !hasPush) {
missing = missing | MissingTriggers.Push;
}
if (!doc.on.includes("pull_request")) {
missing = missing | MissingTriggers.PullRequest;
}
} else if (isObject(doc.on)) {
if (!Object.prototype.hasOwnProperty.call(doc.on, "pull_request")) {
missing = missing | MissingTriggers.PullRequest;
}
if (!Object.prototype.hasOwnProperty.call(doc.on, "push")) {
const hasPush = Object.prototype.hasOwnProperty.call(doc.on, "push");
const hasPullRequest = Object.prototype.hasOwnProperty.call(
doc.on,
"pull_request"
);
if (!hasPush && hasPullRequest) {
missing = missing | MissingTriggers.Push;
} else {
}
if (hasPush && hasPullRequest) {
const paths = doc.on.push?.paths;
// if you specify paths or paths-ignore you can end up with commits that have no baseline
// if they didn't change any files
@ -276,24 +271,29 @@ export function validateWorkflow(doc: Workflow): CodedError[] {
}
}
const push = branchesToArray(doc.on.push?.branches);
// if doc.on.pull_request is null that means 'all branches'
// if doc.on.pull_request is undefined that means 'off'
// we only want to check for mismatched branches if pull_request is on.
if (doc.on.pull_request !== undefined) {
const push = branchesToArray(doc.on.push?.branches);
if (push !== "**") {
const pull_request = branchesToArray(doc.on.pull_request?.branches);
if (push !== "**") {
const pull_request = branchesToArray(doc.on.pull_request?.branches);
if (pull_request !== "**") {
const difference = pull_request.filter(
(value) => !push.some((o) => patternIsSuperset(o, value))
);
if (difference.length > 0) {
// there are branches in pull_request that may not have a baseline
// because we are not building them on push
if (pull_request !== "**") {
const difference = pull_request.filter(
(value) => !push.some((o) => patternIsSuperset(o, value))
);
if (difference.length > 0) {
// there are branches in pull_request that may not have a baseline
// because we are not building them on push
errors.push(WorkflowErrors.MismatchedBranches);
}
} else if (push.length > 0) {
// push is set up to run on a subset of branches
// and you could open a PR against a branch with no baseline
errors.push(WorkflowErrors.MismatchedBranches);
}
} else if (push.length > 0) {
// push is set up to run on a subset of branches
// and you could open a PR against a branch with no baseline
errors.push(WorkflowErrors.MismatchedBranches);
}
}
} else {