Skip to content

Commit b7ebcb4

Browse files
authored
Merge pull request #18306 from github/scheduled-workflow-tests
Tests for scheduled workflows
2 parents 8e9f001 + 04b3dd3 commit b7ebcb4

4 files changed

Lines changed: 53 additions & 3 deletions

File tree

.github/workflows/repo-freeze-reminders.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ name: Repo Freeze Reminders
66

77
on:
88
schedule:
9-
- cron: '00 11 * * *' # once per day around 11:00am UTC
9+
- cron: '9 11 * * *' # once per day around 11:09am UTC
1010

1111
env:
1212
FREEZE: ${{ secrets.FREEZE }}

.github/workflows/repo-sync-stalls.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ name: Repo Sync Stalls
77
on:
88
workflow_dispatch:
99
schedule:
10-
- cron: '0 */2 * * *'
10+
- cron: '32 */2 * * *' # At minute 32 past every 2nd hour.
1111

1212
jobs:
1313
repo-sync-stalls:

tests/content/lint-files.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ describe('lint markdown content', () => {
248248
describe.each(mdToLint)(
249249
'%s',
250250
(markdownRelPath, markdownAbsPath) => {
251-
let content, ast, links, isHidden, isEarlyAccess, isSitePolicy, frontmatterErrors, frontmatterData
251+
let content, ast, links, yamlScheduledWorkflows, isHidden, isEarlyAccess, isSitePolicy, frontmatterErrors, frontmatterData
252252

253253
beforeAll(async () => {
254254
const fileContents = await readFileAsync(markdownAbsPath, 'utf8')
@@ -266,6 +266,23 @@ describe('lint markdown content', () => {
266266
visit(ast, ['link', 'definition'], node => {
267267
links.push(node.url)
268268
})
269+
270+
yamlScheduledWorkflows = []
271+
visit(ast, 'code', node => {
272+
if (/ya?ml/.test(node.lang) && node.value.includes('schedule') && node.value.includes('cron')) {
273+
yamlScheduledWorkflows.push(node.value)
274+
}
275+
})
276+
277+
// visit is not async-friendly so we need to do an async map to parse the YML snippets
278+
yamlScheduledWorkflows = (await Promise.all(yamlScheduledWorkflows.map(async (snippet) => {
279+
// If we don't parse the Liquid first, yaml loading chokes on {% raw %} tags
280+
const rendered = await renderContent.liquid.parseAndRender(snippet)
281+
const parsed = yaml.safeLoad(rendered)
282+
return parsed.on.schedule
283+
})))
284+
.flat()
285+
.map(schedule => schedule.cron)
269286
})
270287

271288
// We need to support some non-Early Access hidden docs in Site Policy
@@ -293,6 +310,20 @@ describe('lint markdown content', () => {
293310
expect(matches.length, errorMessage).toBe(0)
294311
})
295312

313+
test('yaml snippets that include scheduled workflows must not run on the hour', async () => {
314+
const hourlySchedules = yamlScheduledWorkflows.filter(schedule => {
315+
const hour = schedule.split(' ')[0]
316+
// return any minute cron segments that equal 0, 00, 000, etc.
317+
return !/[^0]/.test(hour)
318+
})
319+
expect(hourlySchedules).toEqual([])
320+
})
321+
322+
// Note this only ensures that scheduled workflow snippets are unique _per Markdown file_
323+
test('yaml snippets that include scheduled workflows run at unique times', () => {
324+
expect(yamlScheduledWorkflows.length).toEqual(new Set(yamlScheduledWorkflows).size)
325+
})
326+
296327
test('must not leak Early Access doc URLs', async () => {
297328
// Only execute for docs that are NOT Early Access
298329
if (!isEarlyAccess) {

tests/unit/actions-workflows.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ function actionsUsedInWorkflow (workflow) {
1919
.map(key => get(workflow, key))
2020
}
2121

22+
const scheduledWorkflows = workflows
23+
.map(workflow => workflow.data.on.schedule)
24+
.filter(Boolean)
25+
.flat()
26+
.map(schedule => schedule.cron)
27+
2228
const allUsedActions = chain(workflows)
2329
.map(actionsUsedInWorkflow)
2430
.flatten()
@@ -38,4 +44,17 @@ describe('GitHub Actions workflows', () => {
3844
const disallowedActions = difference(allUsedActions, allowedActions)
3945
expect(disallowedActions).toEqual([])
4046
})
47+
48+
test('no scheduled workflows run on the hour', () => {
49+
const hourlySchedules = scheduledWorkflows.filter(schedule => {
50+
const hour = schedule.split(' ')[0]
51+
// return any minute cron segments that equal 0, 00, 000, etc.
52+
return !/[^0]/.test(hour)
53+
})
54+
expect(hourlySchedules).toEqual([])
55+
})
56+
57+
test('all scheduled workflows run at unique times', () => {
58+
expect(scheduledWorkflows.length).toEqual(new Set(scheduledWorkflows).size)
59+
})
4160
})

0 commit comments

Comments
 (0)