From 2c355109383a4b570216a0e60e3ab26d328da29c Mon Sep 17 00:00:00 2001 From: Mike Plummer Date: Mon, 13 Feb 2023 16:21:41 -0600 Subject: [PATCH] fix: Suppress filesystem errors during glob search (#25774) --- cli/CHANGELOG.md | 1 + .../src/sources/FileDataSource.ts | 14 +++++++-- .../test/unit/sources/FileDataSource.spec.ts | 29 +++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 00fc661f372a..4c2141b646f7 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -16,6 +16,7 @@ _Released 02/14/2023 (PENDING)_ - Fixed an issue in middleware where error-handling code could itself generate an error and fail to report the original issue. Fixes [#22825](https://github.com/cypress-io/cypress/issues/22825). - Fixed an issue that could cause the Debug page to display a different number of specs for in-progress runs than shown in Cypress Cloud. Fixes [#25647](https://github.com/cypress-io/cypress/issues/25647). - Fixed an issue introduced in Cypress 12.3.0 where custom browsers that relied on process environment variables were not found on macOS arm64 architectures. Fixed in [#25753](https://github.com/cypress-io/cypress/pull/25753). +- Fixed an issue where Cypress would fail to load any specs if the project `specPattern` included a resource that could not be accessed due to filesystem permissions. Fixes [#24109](https://github.com/cypress-io/cypress/issues/24109). **Misc:** diff --git a/packages/data-context/src/sources/FileDataSource.ts b/packages/data-context/src/sources/FileDataSource.ts index 4ef6b9671b05..52b07410e46b 100644 --- a/packages/data-context/src/sources/FileDataSource.ts +++ b/packages/data-context/src/sources/FileDataSource.ts @@ -34,7 +34,7 @@ export class FileDataSource { return this.ctx.fs.readFile(path.join(this.ctx.currentProject, relative), 'utf-8') } - async getFilesByGlob (cwd: string, glob: string | string[], globOptions?: GlobbyOptions) { + async getFilesByGlob (cwd: string, glob: string | string[], globOptions: GlobbyOptions = {}): Promise { const globs = ([] as string[]).concat(glob).map((globPattern) => { const workingDirectoryPrefix = path.join(cwd, path.sep) @@ -49,7 +49,7 @@ export class FileDataSource { return globPattern }) - const ignoreGlob = (globOptions?.ignore ?? []).concat('**/node_modules/**') + const ignoreGlob = (globOptions.ignore ?? []).concat('**/node_modules/**') if (os.platform() === 'win32') { // globby can't work with backwards slashes @@ -72,7 +72,15 @@ export class FileDataSource { return files } catch (e) { - debug('error in getFilesByGlob %o', e) + if (!globOptions.suppressErrors) { + // Log error and retry with filesystem errors suppressed - this allows us to find partial + // results even if the glob search hits permission issues (#24109) + debug('Error in getFilesByGlob %o, retrying with filesystem errors suppressed', e) + + return await this.getFilesByGlob(cwd, glob, { ...globOptions, suppressErrors: true }) + } + + debug('Non-suppressible error in getFilesByGlob %o', e) return [] } diff --git a/packages/data-context/test/unit/sources/FileDataSource.spec.ts b/packages/data-context/test/unit/sources/FileDataSource.spec.ts index 2484c427cb1d..98da6e245d3f 100644 --- a/packages/data-context/test/unit/sources/FileDataSource.spec.ts +++ b/packages/data-context/test/unit/sources/FileDataSource.spec.ts @@ -272,6 +272,35 @@ describe('FileDataSource', () => { }, ) }) + + it('should retry search with `suppressErrors` if non-suppressed attempt fails', async () => { + matchGlobsStub.onFirstCall().rejects(new Error('mocked filesystem error')) + matchGlobsStub.onSecondCall().resolves(mockMatches) + + const files = await fileDataSource.getFilesByGlob( + '/', + '/cypress/e2e/**.cy.js', + { absolute: false, objectMode: true }, + ) + + expect(files).to.eq(mockMatches) + expect(matchGlobsStub).to.have.callCount(2) + expect(matchGlobsStub.getCall(0).args[1].suppressErrors).to.be.undefined + expect(matchGlobsStub.getCall(1).args[1].suppressErrors).to.equal(true) + }) + + it('should return empty array if retry with suppression fails', async () => { + matchGlobsStub.rejects(new Error('mocked filesystem error')) + + const files = await fileDataSource.getFilesByGlob( + '/', + '/cypress/e2e/**.cy.js', + { absolute: false, objectMode: true }, + ) + + expect(files).to.eql([]) + expect(matchGlobsStub).to.have.callCount(2) + }) }) }) })