Repository: codecov/uploader
Pull request #794 (2022-06-04 22:25:18)

feat: add gcov executable
fixes https://github.com/codecov/uploader/issues/786

codecov[bot]: # [Codecov](https://app.codecov.io/gh/codecov/uploader/compare/794?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov) Report
> Merging [#794](https://app.codecov.io/gh/codecov/uploader/compare/794?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov) (7ec08b4) into [main](https://codecov.io/gh/codecov/uploader/commit/ebe15b8ce2ab7ce27695d93f478fc0b5a5c9fcbf?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov) (ebe15b8) will **decrease** coverage by `0.06%`.
> The diff coverage is `66.66%`.

```diff
@@            Coverage Diff             @@
##             main     #794      +/-   ##
==========================================
- Coverage   92.33%   92.26%   -0.07%     
==========================================
  Files          33       33              
  Lines        1161     1164       +3     
  Branches      234      236       +2     
==========================================
+ Hits         1072     1074       +2     
- Misses         57       58       +1     
  Partials       32       32              
```

| Flag | Coverage Δ | |
|---|---|---|
| alpine | `92.26% <66.66%> (-0.07%)` | :arrow_down: |
| alpine-proxy | `92.26% <66.66%> (-0.07%)` | :arrow_down: |
| alpine-without-git | `92.26% <66.66%> (-0.07%)` | :arrow_down: |
| linux | `92.26% <66.66%> (-0.07%)` | :arrow_down: |
| linux-without-git | `92.26% <66.66%> (-0.07%)` | :arrow_down: |
| macos | `92.26% <66.66%> (-0.07%)` | :arrow_down: |
| macos-without-git | `92.26% <66.66%> (-0.07%)` | :arrow_down: |
| windows | `?` | |
| windows-without-git | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://app.codecov.io/gh/codecov/uploader/compare/794?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov) | Coverage Δ | |
|---|---|---|
| [src/helpers/cli.ts](https://app.codecov.io/gh/codecov/uploader/compare/794/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov#diff-c3JjL2hlbHBlcnMvY2xpLnRz) | `100.00% <ø> (ø)` | |
| [src/index.ts](https://app.codecov.io/gh/codecov/uploader/compare/794/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov#diff-c3JjL2luZGV4LnRz) | `74.30% <0.00%> (-0.52%)` | :arrow_down: |
| [src/helpers/gcov.ts](https://app.codecov.io/gh/codecov/uploader/compare/794/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov#diff-c3JjL2hlbHBlcnMvZ2Nvdi50cw==) | `100.00% <100.00%> (ø)` | |

lukaszstolarczuk: Hi, is there a chance this PR will be merged? :-)
drazisil-codecov: From https://en.wikipedia.org/wiki/Gcov#Coverage_summaries

> Lcov is a [graphical front-end](https://en.wikipedia.org/wiki/Graphical_user_interface) for gcov. It collects gcov data for multiple source files and creates [HTML](https://en.wikipedia.org/wiki/HTML) pages containing the source code annotated with coverage information.

Why would a customer use `lcov`? In addition to creating HTML files, which Codecov does not support, is it able to be executed without `gcov` being present? What situations would this be the case?
lukaszstolarczuk: > From https://en.wikipedia.org/wiki/Gcov#Coverage_summaries
> 
> > Lcov is a [graphical front-end](https://en.wikipedia.org/wiki/Graphical_user_interface) for gcov. It collects gcov data for multiple source files and creates [HTML](https://en.wikipedia.org/wiki/HTML) pages containing the source code annotated with coverage information.
> 
> Why would a customer use `lcov`? In addition to creating HTML files, which Codecov does not support, is it able to be executed without `gcov` being present? What situations would this be the case?

In our case it wasn't `lcov`, it was `-x "llvm-cov gcov"` with `clang` compilation. As far as I understand, they might produce different coverage reports from `gcov` + `gcc`.
drazisil-codecov: 
> In our case it wasn't `lcov`, it was `-x "llvm-cov gcov"` with `clang` compilation. As far as I understand, they might produce different coverage reports from `gcov` + `gcc`.

@lukaszstolarczuk ,

In that case, since you are calling `gcov` manually, wouldn't it work for you to not call the Codecov `gcov` and run the uploader normally? It should pick up your generated reports. Apologies if I do not quite understand your use case.
lukaszstolarczuk: > > In our case it wasn't `lcov`, it was `-x "llvm-cov gcov"` with `clang` compilation. As far as I understand, they might produce different coverage reports from `gcov` + `gcc`.
> 
> @lukaszstolarczuk ,
> 
> In that case, since you are calling `gcov` manually, wouldn't it work for you to not call the Codecov `gcov` and run the uploader normally? It should pick up your generated reports. Apologies if I do not quite understand your use case.

Oh no, we never call `gcov` manually. With bash uploader we used to call you script like this:
```bash
if [[ ${clang_used} -gt 0 ]]; then
	gcovexe="llvm-cov gcov"
else
	gcovexe="gcov"
fi
/opt/scripts/codecov -c -F ${1} -Z -x "${gcovexe}" "gcovout"
```

right now, with the new uploader, we just ignore that parameter, because it's missing 😉 
```bash
/opt/scripts/codecov --flags ${1} --nonZero --gcov --rootDir . --clean
```

We never confirmed what the reason was, but since the day we started using the new uploader, our coverage dropped by ~6% (the only difference we noticed for sure, was missing clang builds coverage reports).
drazisil-codecov: > right now, with the new uploader, we just ignore that parameter, because it's missing 😉

Ah, makes sens now. Thanks, @lukaszstolarczuk , sorry for the confusion.

Patch:
diff --git a/src/helpers/cli.ts b/src/helpers/cli.ts
index 503f06c..312fb52 100644
--- a/src/helpers/cli.ts
+++ b/src/helpers/cli.ts
@@ -82,6 +82,12 @@ const args: ICLIArgument[] = [
     type: 'string',
     description: 'Paths to include during gcov gathering',
   },
+  {
+    alias: 'gx',
+    name: 'gcovExecutable',
+    type: 'string',
+    description: "gcov executable to run. Defaults to 'gcov'",
+  },
   {
     alias: 'i',
     name: 'networkFilter',
diff --git a/src/helpers/gcov.ts b/src/helpers/gcov.ts
index f4cbb4b..5eed193 100644
--- a/src/helpers/gcov.ts
+++ b/src/helpers/gcov.ts
@@ -3,9 +3,9 @@ import glob from 'fast-glob'
 import { manualBlocklist } from '../../src/helpers/files'
 import { isProgramInstalled, runExternalProgram } from "./util"
 
-export async function generateGcovCoverageFiles(projectRoot: string, include: string[] = [], ignore: string[] = [], gcovArgs: string[] = []): Promise<string> {
-    if (!isProgramInstalled('gcov')) {
-        throw new Error('gcov is not installed, cannot process files')
+export async function generateGcovCoverageFiles(projectRoot: string, include: string[] = [], ignore: string[] = [], gcovArgs: string[] = [], gcovExecutable = 'gcov'): Promise<string> {
+    if (!isProgramInstalled(gcovExecutable)) {
+        throw new Error(`${gcovExecutable} is not installed, cannot process files`)
     }
 
     const globstar = (pattern: string) => `**/${pattern}`
@@ -15,5 +15,8 @@ export async function generateGcovCoverageFiles(projectRoot: string, include: st
     if (!files.length) {
         throw new Error('No gcov files found')
     }
-    return runExternalProgram('gcov', ['-pb', ...gcovArgs, ...files]);
+    if (gcovExecutable === 'gcov') {
+        gcovArgs.unshift('-pb')
+    }
+    return runExternalProgram(gcovExecutable, [...gcovArgs, ...files]);
 }
diff --git a/src/index.ts b/src/index.ts
index b89dcee..5807d6f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -162,11 +162,13 @@ export async function main(
   // Look for files
 
   if (args.gcov) {
-    UploadLogger.verbose('Running gcov...')
     const gcovInclude: string[] = argAsArray(args.gcovInclude)
     const gcovIgnore: string[] = argAsArray(args.gcovIgnore)
     const gcovArgs: string[] = argAsArray(args.gcovArgs)
-    const gcovLogs = await generateGcovCoverageFiles(projectRoot, gcovInclude, gcovIgnore, gcovArgs)
+    const gcovExecutable: string = args.gcovExecutable || 'gcov'
+
+    UploadLogger.verbose(`Running ${gcovExecutable}...`)
+    const gcovLogs = await generateGcovCoverageFiles(projectRoot, gcovInclude, gcovIgnore, gcovArgs, gcovExecutable)
     UploadLogger.verbose(`${gcovLogs}`)
   }
 
diff --git a/src/types.ts b/src/types.ts
index 41184a6..7070645 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -15,6 +15,7 @@ export interface UploaderArgs {
   gcovArgs?:  string | string[] // Extra arguments to pass to gcov
   gcovIgnore?:  string | string[] // Paths to ignore during gcov gathering
   gcovInclude?:  string | string[] // Paths to include during gcov gathering
+  gcovExecutable?: string // gcov executable to run.
   name?: string // Custom defined name of the upload. Visible in Codecov UI
   networkFilter?: string // Specify a prefix on the files listed in the network section of the Codecov report. Useful for upload-specific path fixing
   networkPrefix?: string // Specify a prefix on files listed in the network section of the Codecov report. Useful to help resolve path fixing


Test patch:
diff --git a/test/helpers/gcov.test.ts b/test/helpers/gcov.test.ts
index 6d3d568..35cd3c3 100644
--- a/test/helpers/gcov.test.ts
+++ b/test/helpers/gcov.test.ts
@@ -35,7 +35,7 @@ describe('generateGcovCoverageFiles()', () => {
         })
 
         const projectRoot = process.cwd()
-        expect(await generateGcovCoverageFiles(projectRoot, [], [], ['NEWGCOVARG'])).toEqual("Matched")
+        expect(await generateGcovCoverageFiles(projectRoot, [], [], ['NEWGCOVARG'], 'gcov')).toEqual("Matched")
     })
 
     it('should return an error when no gcno files found', async () => {
@@ -58,6 +58,6 @@ describe('generateGcovCoverageFiles()', () => {
         td.when(spawnSync('gcov')).thenReturn({ error: "Command 'gcov' not found" })
 
         const projectRoot = process.cwd()
-        await expect(generateGcovCoverageFiles(projectRoot, [], [], ['NEWGCOVARG'])).rejects.toThrowError(/gcov is not installed/)
+        await expect(generateGcovCoverageFiles(projectRoot, [], [], ['NEWGCOVARG'], 'gcov')).rejects.toThrowError(/gcov is not installed/)
     })
 })