Skip to content

Commit f0d5d03

Browse files
authored
Merge pull request #1 from homeles/feature-branch1
Removing all the references to Top-Level concurrency which we do not …
2 parents eff6519 + f654f4b commit f0d5d03

File tree

4 files changed

+12
-173
lines changed

4 files changed

+12
-173
lines changed

README.md

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@
22
This GitHub Action validates that the concurrency usage across workflows in a repository does not exceed a specified limit. It helps prevent abuse of GitHub's concurrent job limits by analyzing workflow files and detecting potential parallel execution.
33

44
## What it checks
5-
- Top-level concurrency settings in workflows
6-
- Job-level concurrency settings
75
- Implicit concurrency from jobs that run in parallel (no dependencies)
86
- Matrix job combinations
97
- Jobs within the same workflow run that can execute in parallel
108

119
## Important Notes
12-
- Jobs within the same workflow run can execute in parallel even with `cancel-in-progress: true`
13-
- `cancel-in-progress` only affects concurrent workflow runs, not jobs within the same workflow
14-
- Matrix jobs are counted by their total number of combinations
1510
- Dependencies between jobs (`needs:`) are properly analyzed to identify truly parallel execution paths
11+
- Matrix jobs are counted by their total number of combinations
12+
- All jobs at the same dependency level will be counted towards parallel execution
1613

1714
## Installation
1815

@@ -334,26 +331,7 @@ The action uses the modern GitHub Actions output method with environment files.
334331
### Understanding Concurrency Detection
335332
The action detects concurrency in several ways:
336333

337-
1. **Explicit workflow-level concurrency**: Set via `concurrency:` at the workflow root
338-
- Simple string format: `concurrency: group1`
339-
- Object format with cancel-in-progress:
340-
```yaml
341-
concurrency:
342-
group: build-group
343-
cancel-in-progress: true
344-
```
345-
Note: cancel-in-progress only affects concurrent workflow runs, not jobs within the same workflow
346-
347-
2. **Explicit job-level concurrency**: Set via `concurrency:` in individual jobs
348-
```yaml
349-
jobs:
350-
job1:
351-
concurrency: group1
352-
job2:
353-
concurrency: group2
354-
```
355-
356-
3. **Implicit parallel jobs**: Jobs that can run in parallel based on:
334+
1. **Implicit parallel jobs**: Jobs that can run in parallel based on:
357335
- No `needs:` dependencies between them
358336
- Being in the same dependency level
359337
Example:
@@ -372,7 +350,7 @@ The action detects concurrency in several ways:
372350
```
373351
Here, `build` and `test` can run in parallel (count: 2), while `deploy` runs after them (different level)
374352

375-
4. **Matrix jobs**: Count based on total combinations
353+
2. **Matrix jobs**: Count based on total combinations
376354
```yaml
377355
jobs:
378356
test:
@@ -397,20 +375,7 @@ The action detects concurrency in several ways:
397375
```
398376
Total concurrency: 2 (both jobs can run in parallel)
399377

400-
2. Workflow with cancel-in-progress and parallel jobs:
401-
```yaml
402-
concurrency:
403-
group: build
404-
cancel-in-progress: true
405-
jobs:
406-
job1:
407-
runs-on: ubuntu-latest
408-
job2:
409-
runs-on: ubuntu-latest
410-
```
411-
Total concurrency: 2 (jobs can still run in parallel within the same workflow)
412-
413-
3. Matrix job with dependencies:
378+
2. Matrix job with dependencies:
414379
```yaml
415380
jobs:
416381
test:

dist/index.js

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -182,16 +182,6 @@ try {
182182
return;
183183
}
184184
let details = [];
185-
// Check if workflow has cancel-in-progress concurrency
186-
if (typeof workflow.concurrency === 'object' && workflow.concurrency['cancel-in-progress'] === true) {
187-
Logger.info('Note: Workflow has cancel-in-progress concurrency (only affects concurrent workflow runs)');
188-
details.push({
189-
file: relativeFilePath,
190-
level: 'workflow',
191-
type: 'cancel-in-progress',
192-
counted: false
193-
});
194-
}
195185
// Process each level of parallel jobs for logging
196186
parallelJobs.forEach((level, index) => {
197187
if (level.length > 0) {
@@ -215,8 +205,6 @@ try {
215205
}
216206
details.push({
217207
file: relativeFilePath,
218-
level: 'implicit',
219-
type: 'standard',
220208
jobs: level,
221209
count: levelConcurrency,
222210
counted: true

src/__tests__/validate-concurrency.test.ts

Lines changed: 7 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -50,57 +50,28 @@ describe('Workflow Concurrency Validator', () => {
5050
await new Promise(resolve => setTimeout(resolve, 100));
5151
}
5252

53-
test('validates workflow with top-level concurrency', async () => {
54-
const workflow = `
55-
name: Test Workflow
56-
on: push
57-
concurrency: group1
58-
jobs:
59-
test:
60-
runs-on: ubuntu-latest
61-
steps:
62-
- run: echo "test"
63-
`;
64-
fs.writeFileSync(path.join(workflowDir, 'test1.yml'), workflow);
65-
66-
await runValidator();
67-
68-
const output = getGitHubOutput();
69-
expect(JSON.parse(output.match(/workflow_results<<.*\n(.*)\n/)?.[1] || '')[0]).toEqual(
70-
expect.objectContaining({
71-
file: '.github/workflows/test1.yml',
72-
concurrencyCount: 1,
73-
passed: true
74-
})
75-
);
76-
expect(output).toMatch(/validation_passed<<.*\ntrue\n/);
77-
expect(output).toMatch(/total_concurrency<<.*\n0\n/);
78-
});
79-
80-
test('validates workflow with job-level concurrency', async () => {
53+
test('validates workflow with parallel jobs', async () => {
8154
const workflow = `
8255
name: Test Workflow
8356
on: push
8457
jobs:
8558
test1:
8659
runs-on: ubuntu-latest
87-
concurrency: job1
8860
steps:
8961
- run: echo "test"
9062
test2:
9163
runs-on: ubuntu-latest
92-
concurrency: job2
9364
steps:
9465
- run: echo "test"
9566
`;
96-
fs.writeFileSync(path.join(workflowDir, 'test2.yml'), workflow);
67+
fs.writeFileSync(path.join(workflowDir, 'test1.yml'), workflow);
9768

9869
await runValidator();
9970

10071
const output = getGitHubOutput();
10172
expect(JSON.parse(output.match(/workflow_results<<.*\n(.*)\n/)?.[1] || '')[0]).toEqual(
10273
expect.objectContaining({
103-
file: '.github/workflows/test2.yml',
74+
file: '.github/workflows/test1.yml',
10475
concurrencyCount: 2,
10576
passed: true
10677
})
@@ -109,53 +80,6 @@ jobs:
10980
expect(output).toMatch(/total_concurrency<<.*\n0\n/);
11081
});
11182

112-
test('validates workflow with cancel-in-progress concurrency', async () => {
113-
const workflow = `
114-
name: Test Workflow
115-
on: push
116-
concurrency:
117-
group: build
118-
cancel-in-progress: true
119-
jobs:
120-
test1:
121-
runs-on: ubuntu-latest
122-
steps:
123-
- run: echo "test1"
124-
test2:
125-
runs-on: ubuntu-latest
126-
steps:
127-
- run: echo "test2"
128-
`;
129-
fs.writeFileSync(path.join(workflowDir, 'test3.yml'), workflow);
130-
131-
await runValidator();
132-
133-
const output = getGitHubOutput();
134-
expect(JSON.parse(output.match(/workflow_results<<.*\n(.*)\n/)?.[1] || '')[0]).toEqual(
135-
expect.objectContaining({
136-
file: '.github/workflows/test3.yml',
137-
concurrencyCount: 2,
138-
passed: true,
139-
details: expect.arrayContaining([
140-
expect.objectContaining({
141-
level: 'workflow',
142-
type: 'cancel-in-progress',
143-
counted: false
144-
}),
145-
expect.objectContaining({
146-
level: 'implicit',
147-
type: 'standard',
148-
jobs: ['test1', 'test2'],
149-
count: 2,
150-
counted: true
151-
})
152-
])
153-
})
154-
);
155-
expect(output).toMatch(/validation_passed<<.*\ntrue\n/);
156-
expect(output).toMatch(/total_concurrency<<.*\n0\n/);
157-
});
158-
15983
test('validates implicit concurrency from parallel jobs', async () => {
16084
const workflow = `
16185
name: Test Workflow
@@ -175,14 +99,14 @@ jobs:
17599
steps:
176100
- run: echo "test"
177101
`;
178-
fs.writeFileSync(path.join(workflowDir, 'test4.yml'), workflow);
102+
fs.writeFileSync(path.join(workflowDir, 'test2.yml'), workflow);
179103

180104
await runValidator();
181105

182106
const output = getGitHubOutput();
183107
expect(JSON.parse(output.match(/workflow_results<<.*\n(.*)\n/)?.[1] || '')[0]).toEqual(
184108
expect.objectContaining({
185-
file: '.github/workflows/test4.yml',
109+
file: '.github/workflows/test2.yml',
186110
concurrencyCount: 2,
187111
passed: true
188112
})
@@ -200,16 +124,14 @@ on: push
200124
jobs:
201125
test1:
202126
runs-on: ubuntu-latest
203-
concurrency: job1
204127
steps:
205128
- run: echo "test"
206129
test2:
207130
runs-on: ubuntu-latest
208-
concurrency: job2
209131
steps:
210132
- run: echo "test"
211133
`;
212-
fs.writeFileSync(path.join(workflowDir, 'test5.yml'), workflow);
134+
fs.writeFileSync(path.join(workflowDir, 'test3.yml'), workflow);
213135

214136
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
215137

@@ -218,7 +140,7 @@ jobs:
218140
const output = getGitHubOutput();
219141
expect(JSON.parse(output.match(/workflow_results<<.*\n(.*)\n/)?.[1] || '')[0]).toEqual(
220142
expect.objectContaining({
221-
file: '.github/workflows/test5.yml',
143+
file: '.github/workflows/test3.yml',
222144
concurrencyCount: 2,
223145
passed: false
224146
})
@@ -237,7 +159,6 @@ on: push
237159
jobs:
238160
test1:
239161
runs-on: ubuntu-latest
240-
concurrency: job1
241162
steps:
242163
- run: echo "test"
243164
`;
@@ -248,12 +169,10 @@ on: push
248169
jobs:
249170
test1:
250171
runs-on: ubuntu-latest
251-
concurrency: job1
252172
steps:
253173
- run: echo "test"
254174
test2:
255175
runs-on: ubuntu-latest
256-
concurrency: job2
257176
steps:
258177
- run: echo "test"
259178
`;

src/validate-concurrency.ts

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,12 @@ import path from 'path';
33
import yaml from 'js-yaml';
44
import glob from 'glob';
55

6-
/**
7-
* Represents the concurrency configuration in a workflow file
8-
*/
9-
interface WorkflowConcurrency {
10-
/** The concurrency group name */
11-
group?: string;
12-
/** Whether to cancel in-progress runs */
13-
'cancel-in-progress'?: boolean;
14-
}
15-
166
/**
177
* Represents a job in a GitHub Actions workflow file
188
*/
199
interface WorkflowJob {
2010
/** Dependencies of this job - can be a string or array of strings */
2111
needs?: string | string[];
22-
/** Concurrency configuration for this job */
23-
concurrency?: WorkflowConcurrency | string;
2412
/** Strategy configuration for matrix jobs */
2513
strategy?: {
2614
matrix?: Record<string, any>;
@@ -31,8 +19,6 @@ interface WorkflowJob {
3119
* Represents the structure of a GitHub Actions workflow file
3220
*/
3321
interface WorkflowFile {
34-
/** Top-level concurrency configuration */
35-
concurrency?: WorkflowConcurrency | string;
3622
/** Map of job names to job configurations */
3723
jobs?: Record<string, WorkflowJob>;
3824
}
@@ -43,12 +29,6 @@ interface WorkflowFile {
4329
interface ConcurrencyDetail {
4430
/** Relative path to the workflow file */
4531
file: string;
46-
/** Name of the job (if job-level concurrency) */
47-
job?: string;
48-
/** The level at which concurrency is defined */
49-
level: 'workflow' | 'job' | 'implicit';
50-
/** The type of concurrency configuration */
51-
type?: 'standard' | 'cancel-in-progress';
5232
/** List of job names (for implicit concurrency) */
5333
jobs?: string[];
5434
/** Number of concurrent jobs (for implicit concurrency) */
@@ -268,17 +248,6 @@ try {
268248

269249
let details: ConcurrencyDetail[] = [];
270250

271-
// Check if workflow has cancel-in-progress concurrency
272-
if (typeof workflow.concurrency === 'object' && workflow.concurrency['cancel-in-progress'] === true) {
273-
Logger.info('Note: Workflow has cancel-in-progress concurrency (only affects concurrent workflow runs)');
274-
details.push({
275-
file: relativeFilePath,
276-
level: 'workflow',
277-
type: 'cancel-in-progress',
278-
counted: false
279-
});
280-
}
281-
282251
// Process each level of parallel jobs for logging
283252
parallelJobs.forEach((level, index) => {
284253
if (level.length > 0) {
@@ -305,8 +274,6 @@ try {
305274

306275
details.push({
307276
file: relativeFilePath,
308-
level: 'implicit',
309-
type: 'standard',
310277
jobs: level,
311278
count: levelConcurrency,
312279
counted: true

0 commit comments

Comments
 (0)