Skip to content

Commit 134b0d9

Browse files
Merge pull request #602 from codecov/feat/gcov
Add gcov support
2 parents 0310c27 + 4a9212c commit 134b0d9

File tree

10 files changed

+200
-45
lines changed

10 files changed

+200
-45
lines changed

HELPFILE

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,44 @@
11
Options:
2-
-b, --build Specify the build number manually
3-
-B, --branch Specify the branch manually
4-
-e, --env Specify environment variables to be included with this build.
5-
Also accepting environment variables: CODECOV_ENV=VAR,VAR2
6-
-C, --sha Specify the commit SHA mannually
7-
-f, --file Target file(s) to upload
8-
-F, --flags Flag the upload to group coverage metrics [default: ""]
9-
-n, --name Custom defined name of the upload. Visible in Codecov UI
10-
[default: ""]
11-
-N, --parent The commit SHA of the parent for which you are uploading
12-
coverage. If not present, the parent will be determined using
13-
the API of your repository provider. When using the repository
14-
provider's API, the parent is determined via finding the
15-
closest ancestor to the commit.
16-
-P, --pr Specify the pull request number mannually
17-
-s, --dir Directory to search for coverage reports.
18-
Already searches project root and current working directory
19-
-t, --token Codecov upload token [default: ""]
20-
-T, --tag Specify the git tag [default: ""]
21-
-v, --verbose Run with verbose logging [boolean]
22-
-R, --rootDir Specify the project root directory when not in a git repo
23-
-Z, --nonZero Should errors exit with a non-zero (default: false)
24-
[boolean] [default: false]
25-
-d, --dryRun Don't upload files to Codecov [boolean]
26-
-r, --slug Specify the slug manually
27-
-u, --url Change the upload host (Enterprise use)
28-
[string] [default: "https://codecov.io"]
29-
-c, --clean Move discovered coverage reports to the trash
30-
[boolean] [default: false]
31-
-X, --feature Toggle functionalities.
32-
Separate multiple ones by comma: -X network,search
33-
-X network Disable uploading the file network
34-
-X search Disable searching for coverage files
35-
[string]
36-
-Q, --source Used internally by Codecov, this argument helps track wrappers
37-
of the uploader (e.g. GitHub Action, CircleCI Orb)
38-
[string] [default: ""]
39-
--version Show version number [boolean]
40-
-h, --help Show help [boolean]
2+
-b, --build Specify the build number manually
3+
-B, --branch Specify the branch manually
4+
-e, --env Specify environment variables to be included with this build.
5+
Also accepting environment variables: CODECOV_ENV=VAR,VAR2
6+
-C, --sha Specify the commit SHA mannually
7+
-f, --file Target file(s) to upload
8+
-F, --flags Flag the upload to group coverage metrics [default: ""]
9+
-n, --name Custom defined name of the upload. Visible in Codecov UI
10+
[default: ""]
11+
-N, --parent The commit SHA of the parent for which you are uploading
12+
coverage. If not present, the parent will be determined using
13+
the API of your repository provider. When using the repository
14+
provider's API, the parent is determined via finding the
15+
closest ancestor to the commit.
16+
-P, --pr Specify the pull request number mannually
17+
-s, --dir Directory to search for coverage reports.
18+
Already searches project root and current working directory
19+
-t, --token Codecov upload token [default: ""]
20+
-T, --tag Specify the git tag [default: ""]
21+
-v, --verbose Run with verbose logging [boolean]
22+
-R, --rootDir Specify the project root directory when not in a git repo
23+
-Z, --nonZero Should errors exit with a non-zero (default: false)
24+
[boolean] [default: false]
25+
-d, --dryRun Don't upload files to Codecov [boolean]
26+
-r, --slug Specify the slug manually
27+
-u, --url Change the upload host (Enterprise use)
28+
[string] [default: "https://codecov.io"]
29+
-c, --clean Move discovered coverage reports to the trash
30+
[boolean] [default: false]
31+
-X, --feature Toggle functionalities.
32+
Separate multiple ones by comma: -X network,search
33+
-X network Disable uploading the file network
34+
-X search Disable searching for coverage files
35+
[string]
36+
-Q, --source Used internally by Codecov, this argument helps track wrappers
37+
of the uploader (e.g. GitHub Action, CircleCI Orb)
38+
[string] [default: ""]
39+
--gcov Run with gcov support [boolean] [default: false]
40+
--gcovIgnore Paths to ignore during gcov gathering [string]
41+
--gcovInclude Paths to include during gcov gathering [string]
42+
--gcovArgs Extra arguments to pass to gcov [string]
43+
--version Show version number [boolean]
44+
-h, --help Show help [boolean]

src/helpers/cli.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,31 @@ const args: ICLIArgument[] = [
160160
description: `Used internally by Codecov, this argument helps track wrappers
161161
of the uploader (e.g. GitHub Action, CircleCI Orb)`,
162162
},
163+
{
164+
alias: 'g',
165+
name: 'gcov',
166+
type: 'boolean',
167+
default: false,
168+
description: 'Run with gcov support',
169+
},
170+
{
171+
alias: 'gi',
172+
name: 'gcovIgnore',
173+
type: 'string',
174+
description: 'Paths to ignore during gcov gathering',
175+
},
176+
{
177+
alias: 'gI',
178+
name: 'gcovInclude',
179+
type: 'string',
180+
description: 'Paths to include during gcov gathering',
181+
},
182+
{
183+
alias: 'ga',
184+
name: 'gcovArgs',
185+
type: 'string',
186+
description: 'Extra arguments to pass to gcov',
187+
},
163188
]
164189

165190
export interface IYargsObject {

src/helpers/files.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export async function getFileListing(
2727
return getAllFiles(projectRoot, projectRoot, args).join('\n')
2828
}
2929

30-
function manualBlocklist(): string[] {
30+
export function manualBlocklist(): string[] {
3131
// TODO: honor the .gitignore file instead of a hard-coded list
3232
return [
3333
'.DS_Store',

src/helpers/gcov.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import glob from 'fast-glob'
2+
3+
import { manualBlocklist } from '../../src/helpers/files'
4+
import { isProgramInstalled, runExternalProgram } from "./util"
5+
6+
export async function generateGcovCoverageFiles(projectRoot: string, include: string[] = [], ignore: string[] = [], gcovArgs = '',): Promise<string> {
7+
if (!isProgramInstalled('gcov')) {
8+
throw new Error('gcov is not installed, cannot process files')
9+
}
10+
11+
const globstar = (pattern: string) => `**/${pattern}`
12+
const gcovInclude = ['*.gcno', ...include].map(globstar)
13+
const gcovIgnore = [...manualBlocklist(), ...ignore].map(globstar)
14+
const files = await glob(gcovInclude, {cwd: projectRoot, ignore: gcovIgnore, onlyFiles: true})
15+
if (!files.length) {
16+
throw new Error('No gcov files found')
17+
}
18+
return runExternalProgram('gcov', ['-pb', gcovArgs, ...files]);
19+
}

src/index.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
readCoverageFile,
2323
removeFile,
2424
} from './helpers/files'
25+
import { generateGcovCoverageFiles } from './helpers/gcov'
2526

2627
/**
2728
*
@@ -91,11 +92,12 @@ export async function main(
9192
/*
9293
Step 1: validate and sanitize inputs
9394
Step 2: detect if we are in a git repo
94-
Step 3: get network (file listing)
95-
Step 4: select coverage files (search or specify)
96-
Step 5: generate upload file
97-
Step 6: determine CI provider
98-
Step 7: either upload or dry-run
95+
Step 3: sanitize and set token
96+
Step 4: get network (file listing)
97+
Step 5: select coverage files (search or specify)
98+
Step 6: generate upload file
99+
Step 7: determine CI provider
100+
Step 8: either upload or dry-run
99101
*/
100102

101103
// #region == Step 1: validate and sanitize inputs
@@ -153,6 +155,13 @@ export async function main(
153155
let requestedPaths: string[] = []
154156

155157
// Look for files
158+
159+
if (args.gcov) {
160+
UploadLogger.verbose('Running gcov...')
161+
const gcovLogs = await generateGcovCoverageFiles(projectRoot)
162+
UploadLogger.verbose(`${gcovLogs}`)
163+
}
164+
156165
let coverageFilePaths: string[] = []
157166
if (args.file) {
158167
if (typeof args.file === 'string') {

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export interface UploaderArgs {
2626
upstream: string // Upstream proxy to connect to
2727
source?: string // Track wrappers of the uploader
2828
changelog?: string // Displays the changelog and exits
29+
gcov?: string // Run with gcov support
30+
gcovIgnore?: string // Paths to ignore during gcov gathering
31+
gcovInclude?: string // Paths to include during gcov gathering
32+
gcovArgs?: string // Extra arguments to pass to gcov
2933
}
3034

3135
export type UploaderEnvs = NodeJS.Dict<string>

test/fixtures/gcov/main.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <stdio.h>
2+
3+
void numbers(int num)
4+
{
5+
if (num == 1) {
6+
printf("Number is 1\n");
7+
} else if (num == 2){
8+
printf("Number is 2\n");
9+
} else {
10+
printf("Number is %d\n", num);
11+
}
12+
}
13+
14+
15+
int main(void)
16+
{
17+
numbers(1);
18+
numbers(2);
19+
return 0;
20+
}

test/fixtures/gcov/main.gcno

884 Bytes
Binary file not shown.

test/fixtures/gcov/makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CC = gcc
2+
CFLAG = -fPIC -fprofile-arcs -ftest-coverage
3+
RM = rm -rf
4+
5+
main : main.o
6+
$(CC) $(CFLAG) -o main main.o
7+
8+
main.o : main.c
9+
$(CC) $(CFLAG) -c -Wall -Werror main.c
10+
11+
clean:
12+
$(RM) main *.o *.so *.gcno *.gcda *.gcov

test/helpers/gcov.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
2+
import td from 'testdouble'
3+
import childProcess from 'child_process'
4+
import { generateGcovCoverageFiles } from '../../src/helpers/gcov'
5+
6+
describe('generateGcovCoverageFiles()', () => {
7+
afterEach(() => {
8+
td.reset()
9+
})
10+
11+
it('should find and run on test/fixtures/gcov/main.gcno', async () => {
12+
const output = `File 'main.c'\nLines executed:0.00% of 11\nBranches executed:0.00% of 4\nTaken at least once:0.00% of 4\nCalls executed:0.00% of 5\nCreating 'main.c.gcov'\n\nLines executed:0.00% of 11`
13+
const spawnSync = td.replace(childProcess, 'spawnSync')
14+
td.when(spawnSync('gcov')).thenReturn({
15+
stdout: 'gcov installed',
16+
error: null
17+
})
18+
td.when(spawnSync('gcov', td.matchers.contains('test/fixtures/gcov/main.gcno'))).thenReturn({
19+
stdout: output,
20+
error: null
21+
})
22+
const projectRoot = process.cwd()
23+
expect(await generateGcovCoverageFiles(projectRoot)).toBe(output)
24+
})
25+
26+
it('should pass gcov arguments directly through', async () => {
27+
const spawnSync = td.replace(childProcess, 'spawnSync')
28+
td.when(spawnSync('gcov')).thenReturn({
29+
stdout: 'gcov installed',
30+
error: null
31+
})
32+
td.when(spawnSync('gcov', td.matchers.contains('NEWGCOVARG'))).thenReturn({
33+
stdout: 'Matched',
34+
})
35+
36+
const projectRoot = process.cwd()
37+
expect(await generateGcovCoverageFiles(projectRoot, [], [], 'NEWGCOVARG')).toEqual("Matched")
38+
})
39+
40+
it('should return an error when no gcno files found', async () => {
41+
const spawnSync = td.replace(childProcess, 'spawnSync')
42+
td.when(spawnSync('gcov')).thenReturn({
43+
stdout: 'gcov installed',
44+
error: null
45+
})
46+
td.when(spawnSync('gcov', td.matchers.contains('test'))).thenReturn({
47+
stdout: 'No executable lines'
48+
})
49+
const projectRoot = process.cwd()
50+
await expect(generateGcovCoverageFiles(projectRoot, [], ['test'])).rejects.toThrowError(/No gcov files found/)
51+
52+
53+
})
54+
55+
it('should return an error when gcov is not installed', async () => {
56+
const spawnSync = td.replace(childProcess, 'spawnSync')
57+
td.when(spawnSync('gcov')).thenReturn({ error: "Command 'gcov' not found" })
58+
59+
const projectRoot = process.cwd()
60+
await expect(generateGcovCoverageFiles(projectRoot, [], [], 'NEWGCOVARG')).rejects.toThrowError(/gcov is not installed/)
61+
})
62+
})

0 commit comments

Comments
 (0)