Repository: codecov/uploader
Pull request #577 (2022-01-13 16:02:31)

refactor: allow users to bypass blocklist with -f
In some cases, users would like to upload files that have names that would normally be blocked. If the file is passed with -f, we now emit a warning and allow it.


Patch:
diff --git a/src/helpers/files.ts b/src/helpers/files.ts
index 58dedf9..24df57d 100644
--- a/src/helpers/files.ts
+++ b/src/helpers/files.ts
@@ -335,7 +335,15 @@ export function getBlocklist(): string[] {
   return [...manualBlocklist(), ...globBlocklist()].map(globstar)
 }
 
-export function cleanCoverageFilePaths(projectRoot: string, paths: string[], ignoreGlobs: string[]): string[] {
+export function filterFilesAgainstBlockList(paths: string[], ignoreGlobs: string[]): string[] {
+  const ignoredFiles = micromatch(paths, ignoreGlobs)
+
+  return paths.filter(path => {
+    return !ignoredFiles.includes(path)
+  })
+}
+
+export function cleanCoverageFilePaths(projectRoot: string, paths: string[]): string[] {
   UploadLogger.verbose(`Preparing to clean the following coverage paths: ${paths.toString()}`)
   const coverageFilePaths = [... new Set(paths.filter(file => {
     return fileExists(projectRoot, file)
@@ -345,12 +353,6 @@ export function cleanCoverageFilePaths(projectRoot: string, paths: string[], ign
     throw new Error('Error while cleaning paths. No paths matched existing files!')  
   }
 
-  const ignoredFiles = micromatch(coverageFilePaths, ignoreGlobs)
-
-  const filesAfterCheckingIgnore = coverageFilePaths.filter(path => {
-    return !ignoredFiles.includes(path)
-  })
-
-  return filesAfterCheckingIgnore
+  return coverageFilePaths
 }
 
diff --git a/src/index.ts b/src/index.ts
index 7f00162..0170cc7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,7 +2,6 @@ import { UploaderArgs, UploaderInputs } from './types'
 
 import zlib from 'zlib'
 import { version } from '../package.json'
-import * as validateHelpers from './helpers/validate'
 import { detectProvider } from './helpers/provider'
 import * as webHelpers from './helpers/web'
 import { info, logError, UploadLogger } from './helpers/logger'
@@ -12,6 +11,7 @@ import {
   coverageFilePatterns,
   fetchGitRoot,
   fileHeader,
+  filterFilesAgainstBlockList,
   getBlocklist,
   getCoverageFiles,
   getFileListing,
@@ -150,15 +150,20 @@ export async function main(
   // #endregion
   // #region == Step 5: select coverage files (search or specify)
 
+  let requestedPaths: string[] = []
+  
   // Look for files
   let coverageFilePaths: string[] = []
   if (args.file) {
     if (typeof args.file === 'string') {
-      coverageFilePaths = [args.file]
+      requestedPaths = [args.file]
     } else {
-      coverageFilePaths = args.file
+      requestedPaths = args.file
     }
   }
+
+  coverageFilePaths = requestedPaths
+
   if (!args.feature || args.feature.split(',').includes('search') === false) {
     info('Searching for coverage files...')
     const isNegated = (path: string) => path.startsWith('!')
@@ -175,13 +180,43 @@ export async function main(
       })(),
     ))
 
-    // Remove invalid and duplicate file paths
-    coverageFilePaths = cleanCoverageFilePaths(args.dir || projectRoot, coverageFilePaths, getBlocklist())
+    // Generate what the file listing would be after the blocklist is applied
+
+    let coverageFilePathsAfterFilter = coverageFilePaths
+
+    if (coverageFilePaths.length > 0) { 
+      coverageFilePathsAfterFilter = filterFilesAgainstBlockList(coverageFilePaths, getBlocklist())
+    } 
+
+
+
+
+    // If args.file was passed, emit warning for 'filtered' filess
+
+    if (requestedPaths.length > 0) {
+      if (coverageFilePathsAfterFilter.length !== requestedPaths.length) {
+        info('Warning: Some files passed via the -f flag would normally be excluded from search.')
+        info('If Codecov encounters issues processing your reports, please review https://docs.codecov.com/docs/supported-report-formats')
+      }
+    } else {
+      // Overwrite coverageFilePaths with coverageFilePathsAfterFilter
+      info('Warning: Some files located via search were excluded from upload.')
+      info('If Codecov did not locate your files, please review https://docs.codecov.com/docs/supported-report-formats')
+
+      coverageFilePaths = coverageFilePathsAfterFilter
+    }
+
   }
 
+  let coverageFilePathsThatExist: string[] = []
+
   if (coverageFilePaths.length > 0) {
-    info(`=> Found ${coverageFilePaths.length} possible coverage files:\n  ` +
-        coverageFilePaths.join('\n  '))
+    coverageFilePathsThatExist = cleanCoverageFilePaths(args.dir || projectRoot, coverageFilePaths)
+  }
+
+  if (coverageFilePathsThatExist.length > 0) {
+    info(`=> Found ${coverageFilePathsThatExist.length} possible coverage files:\n  ` +
+    coverageFilePathsThatExist.join('\n  '))
   } else {
     const noFilesError = args.file ?
       'No coverage files found, exiting.' :
@@ -196,7 +231,7 @@ export async function main(
 
   // Get coverage report contents
   let coverageFileAdded = false
-  for (const coverageFile of coverageFilePaths) {
+  for (const coverageFile of coverageFilePathsThatExist) {
     let fileContents
     try {
       info(`Processing ${getFilePath(args.dir || projectRoot, coverageFile)}...`),
@@ -221,7 +256,7 @@ export async function main(
 
   // Cleanup
   if (args.clean) {
-    for (const coverageFile of coverageFilePaths) {
+    for (const coverageFile of coverageFilePathsThatExist) {
       removeFile(args.dir || projectRoot, coverageFile)
     }
   }


Test patch:
diff --git a/test/fixtures/.coverage b/test/fixtures/.coverage
new file mode 100644
index 0000000..df40430
--- /dev/null
+++ b/test/fixtures/.coverage
@@ -0,0 +1,1 @@
+{ "Hello": "!" }
diff --git a/test/fixtures/foo.coverage b/test/fixtures/foo.coverage
new file mode 100644
index 0000000..e69de29
diff --git a/test/helpers/files.test.ts b/test/helpers/files.test.ts
index 1fa1ee4..2016a95 100644
--- a/test/helpers/files.test.ts
+++ b/test/helpers/files.test.ts
@@ -282,7 +282,7 @@ describe('File Helpers', () => {
     })
   })
 
-  describe("cleanCoverageFilePaths()", () => {
+  describe("filterFilesAgainstBlockList()", () => {
     const getPaths = () => fileHelpers.getCoverageFiles(
       '.',
       fileHelpers.coverageFilePatterns(),
@@ -290,35 +290,35 @@ describe('File Helpers', () => {
     it("works", async () => {
       const paths = await getPaths()
 
-      expect(() => fileHelpers.cleanCoverageFilePaths(process.cwd(), paths, getBlocklist())).not.toThrow()
+      expect(() => fileHelpers.filterFilesAgainstBlockList(paths, getBlocklist())).not.toThrow()
     })
 
     it("returns the input array when passed an empty ignore array", async () => {
       const paths = await getPaths()
-      expect(fileHelpers.cleanCoverageFilePaths(process.cwd(), paths, [])).toEqual(paths)
+      expect(fileHelpers.filterFilesAgainstBlockList(paths, [])).toEqual(paths)
     })
 
     it("ignores an ignore filename", async () => {
-      expect(fileHelpers.cleanCoverageFilePaths(process.cwd(), await getPaths(), ["coverage-summary.json"])).not.toContain(expect.stringContaining('coverage-summary.json'))
+      expect(fileHelpers.filterFilesAgainstBlockList(await getPaths(), ["coverage-summary.json"])).not.toContain(expect.stringContaining('coverage-summary.json'))
     })
 
     it("ignores an ignore filename glob", async () => {
-      const foo = expect(fileHelpers.cleanCoverageFilePaths(process.cwd(), await getPaths(), ["**/coverage*"])).not.toContainEqual(expect.stringMatching('/coverage'))
+      const foo = expect(fileHelpers.filterFilesAgainstBlockList(await getPaths(), ["**/coverage*"])).not.toContainEqual(expect.stringMatching('/coverage'))
       console.log(foo)
     })
 
     it("ignores an ignore filename globstar", async () => {
-      expect(fileHelpers.cleanCoverageFilePaths(process.cwd(), await getPaths(), ["**/other/*"])).not.toContainEqual(expect.stringMatching('other'))
+      expect(fileHelpers.filterFilesAgainstBlockList(await getPaths(), ["**/other/*"])).not.toContainEqual(expect.stringMatching('other'))
     })
 
     it("ignores shell scripts by default", async () => {
-      expect(fileHelpers.cleanCoverageFilePaths(process.cwd(), await getPaths(), getBlocklist())).not.toContainEqual(expect.stringMatching('codecov.sh'))
+      expect(fileHelpers.filterFilesAgainstBlockList(await getPaths(), getBlocklist())).not.toContainEqual(expect.stringMatching('codecov.sh'))
     })
     it("ignores powershell scripts by default", async () => {
-      expect(fileHelpers.cleanCoverageFilePaths(process.cwd(), await getPaths(), getBlocklist())).not.toContainEqual(expect.stringMatching('codecov.ps1'))
+      expect(fileHelpers.filterFilesAgainstBlockList(await getPaths(), getBlocklist())).not.toContainEqual(expect.stringMatching('codecov.ps1'))
     })
     it("ignores codecov configs by default", async () => {
-      expect(fileHelpers.cleanCoverageFilePaths(process.cwd(), await getPaths(), getBlocklist())).not.toContainEqual(expect.stringMatching(/^\.?codecov\.ya?ml/))
+      expect(fileHelpers.filterFilesAgainstBlockList(await getPaths(), getBlocklist())).not.toContainEqual(expect.stringMatching(/^\.?codecov\.ya?ml/))
     })
   })
 
diff --git a/test/index.test.ts b/test/index.test.ts
index 9464fa0..ea90285 100644
--- a/test/index.test.ts
+++ b/test/index.test.ts
@@ -275,6 +275,47 @@ describe('Uploader Core', () => {
     )
   })
 
+  it('will process blocked files, if passed via -f', async () => {
+    const log = jest.spyOn(console, 'log').mockImplementation(() => {
+      // intentionally empty
+    })
+    await app.main({
+      dryRun: 'true',
+      file: [
+        'test/fixtures/.coverage',
+      ],
+      name: 'customname',
+      token: 'abcdefg',
+      url: 'https://codecov.io',
+      flags: '',
+      slug: '',
+      upstream: ''
+    })
+    expect(log).toHaveBeenCalledWith(
+      expect.stringMatching(/Processing.*test\/fixtures\/\.coverage\.\.\./),
+    )
+  })
+
+  it('will not process blocked files if not passed via -f', async () => {
+    const log = jest.spyOn(console, 'log').mockImplementation(() => {
+      // intentionally empty
+    })
+    await app.main({
+      dryRun: 'true',
+      file: [
+      ],
+      name: 'customname',
+      token: 'abcdefg',
+      url: 'https://codecov.io',
+      flags: '',
+      slug: '',
+      upstream: ''
+    })
+    expect(log).not.toHaveBeenCalledWith(
+      expect.stringMatching(/Processing.*test\/fixtures\/other\/\.coverage\.\.\./),
+    )
+  })
+
   it('Can handle glob files', async () => {
     const log = jest.spyOn(console, 'log').mockImplementation(() => {
       // intentionally empty