From b4f112b2d44f016aaf48590b86d89dd75d504230 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 12 Apr 2022 11:12:03 -0400 Subject: [PATCH 1/4] convert unzip.start to async/await --- cli/lib/tasks/unzip.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cli/lib/tasks/unzip.js b/cli/lib/tasks/unzip.js index ce665dfcd738..7cb76071a107 100644 --- a/cli/lib/tasks/unzip.js +++ b/cli/lib/tasks/unzip.js @@ -195,7 +195,7 @@ const unzip = ({ zipFilePath, installDir, progress }) => { }) } -const start = ({ zipFilePath, installDir, progress }) => { +const start = async ({ zipFilePath, installDir, progress }) => { la(is.unemptyString(installDir), 'missing installDir') if (!progress) { progress = { onProgress: () => { @@ -203,18 +203,19 @@ const start = ({ zipFilePath, installDir, progress }) => { } } } - return fs.pathExists(installDir) - .then((exists) => { - if (exists) { + try { + const installDirExists = await fs.pathExists(installDir) + + if (installDirExists) { debug('removing existing unzipped binary', installDir) - return fs.removeAsync(installDir) + await fs.removeAsync(installDir) } - }) - .then(() => { - return unzip({ zipFilePath, installDir, progress }) - }) - .catch(throwFormErrorText(errors.failedUnzip)) + + await unzip({ zipFilePath, installDir, progress }) + } catch (err) { + await throwFormErrorText(errors.failedUnzip)(err) + } } module.exports = { From 155742b0131fd5166d61820ef87c57a3fce3f650 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 12 Apr 2022 11:25:18 -0400 Subject: [PATCH 2/4] add intense isWindowsMaxFilePathError check --- cli/lib/tasks/unzip.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/cli/lib/tasks/unzip.js b/cli/lib/tasks/unzip.js index 7cb76071a107..43aba26d478a 100644 --- a/cli/lib/tasks/unzip.js +++ b/cli/lib/tasks/unzip.js @@ -3,6 +3,7 @@ const la = require('lazy-ass') const is = require('check-more-types') const cp = require('child_process') const os = require('os') +const path = require('path') const yauzl = require('yauzl') const debug = require('debug')('cypress:cli:unzip') const extract = require('extract-zip') @@ -195,6 +196,26 @@ const unzip = ({ zipFilePath, installDir, progress }) => { }) } +async function isWindowsMaxFilePathError (err) { + if (!(os.platform === 'win32' && err.code === 'ENOENT' && err.syscall === 'realpath')) return false + + // an ENOENT on realpath on Windows is usually due to the max path length restriction. + // do a test write to confirm if they have this restriction. + const tmp = os.tmpdir() + let longPath = path.join(tmp, `cy-long-filename-test-`) + + longPath += 'a'.repeat(Math.max(270 - longPath.length, 0)) + + try { + await fs.writeFile(longPath, 'this is a test to see if this system supports extra-long filenames') + } catch (err) { + // if an ENOENT error occurred, we are hitting the max path length + return err.code === 'ENOENT' + } + + return false +} + const start = async ({ zipFilePath, installDir, progress }) => { la(is.unemptyString(installDir), 'missing installDir') if (!progress) { @@ -214,7 +235,11 @@ const start = async ({ zipFilePath, installDir, progress }) => { await unzip({ zipFilePath, installDir, progress }) } catch (err) { - await throwFormErrorText(errors.failedUnzip)(err) + const errorTemplate = (await isWindowsMaxFilePathError(err)) ? + errors.failedUnzipWindowsMaxFilePath + : errors.failedUnzip + + await throwFormErrorText(errorTemplate)(err) } } From 2fa12d0b5805e8987ebc2a2c99b50717e313efdb Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 12 Apr 2022 11:58:42 -0400 Subject: [PATCH 3/4] add unit test --- cli/__snapshots__/errors_spec.js | 1 + cli/__snapshots__/unzip_spec.js | 20 +++++++++++++- cli/lib/errors.js | 8 ++++++ cli/lib/tasks/unzip.js | 25 +++-------------- cli/test/lib/tasks/unzip_spec.js | 46 ++++++++++++++++++++++---------- 5 files changed, 64 insertions(+), 36 deletions(-) diff --git a/cli/__snapshots__/errors_spec.js b/cli/__snapshots__/errors_spec.js index e254269f2d30..e5ce248b8a71 100644 --- a/cli/__snapshots__/errors_spec.js +++ b/cli/__snapshots__/errors_spec.js @@ -32,6 +32,7 @@ exports['errors individual has the following errors 1'] = [ "childProcessKilled", "failedDownload", "failedUnzip", + "failedUnzipWindowsMaxPathLength", "incompatibleHeadlessFlags", "invalidCacheDirectory", "invalidCypressEnv", diff --git a/cli/__snapshots__/unzip_spec.js b/cli/__snapshots__/unzip_spec.js index 830187db819b..5614600cc38a 100644 --- a/cli/__snapshots__/unzip_spec.js +++ b/cli/__snapshots__/unzip_spec.js @@ -1,4 +1,4 @@ -exports['unzip error 1'] = ` +exports['lib/tasks/unzip throws when cannot unzip 1'] = ` Error: The Cypress App could not be unzipped. Search for an existing issue or open a GitHub issue at @@ -15,3 +15,21 @@ Platform: darwin-x64 (Foo-OsVersion) Cypress Version: 1.2.3 ` + +exports['lib/tasks/unzip throws max path length error when cannot unzip due to realpath ENOENT on windows 1'] = ` +Error: The Cypress App could not be unzipped. + +This is most likely because the maximum path length is being exceeded on your system. + +Read here for solutions to this problem: https://on.cypress.io/error-messages#win-max-path-length + +---------- + +Error: failed + +---------- + +Platform: win32-x64 (Foo-OsVersion) +Cypress Version: 1.2.3 + +` diff --git a/cli/lib/errors.js b/cli/lib/errors.js index ca460ec41a37..05cc10425280 100644 --- a/cli/lib/errors.js +++ b/cli/lib/errors.js @@ -58,6 +58,13 @@ const failedUnzip = { solution: genericErrorSolution, } +const failedUnzipWindowsMaxPathLength = { + description: 'The Cypress App could not be unzipped.', + solution: `This is most likely because the maximum path length is being exceeded on your system. + + Read here for solutions to this problem: https://on.cypress.io/error-messages#win-max-path-length`, +} + const missingApp = (binaryDir) => { return { description: `No version of Cypress is installed in: ${chalk.cyan( @@ -404,6 +411,7 @@ module.exports = { unexpected, failedDownload, failedUnzip, + failedUnzipWindowsMaxPathLength, invalidCypressEnv, invalidCacheDirectory, CYPRESS_RUN_BINARY, diff --git a/cli/lib/tasks/unzip.js b/cli/lib/tasks/unzip.js index 43aba26d478a..5993bd2700a3 100644 --- a/cli/lib/tasks/unzip.js +++ b/cli/lib/tasks/unzip.js @@ -3,7 +3,6 @@ const la = require('lazy-ass') const is = require('check-more-types') const cp = require('child_process') const os = require('os') -const path = require('path') const yauzl = require('yauzl') const debug = require('debug')('cypress:cli:unzip') const extract = require('extract-zip') @@ -196,24 +195,8 @@ const unzip = ({ zipFilePath, installDir, progress }) => { }) } -async function isWindowsMaxFilePathError (err) { - if (!(os.platform === 'win32' && err.code === 'ENOENT' && err.syscall === 'realpath')) return false - - // an ENOENT on realpath on Windows is usually due to the max path length restriction. - // do a test write to confirm if they have this restriction. - const tmp = os.tmpdir() - let longPath = path.join(tmp, `cy-long-filename-test-`) - - longPath += 'a'.repeat(Math.max(270 - longPath.length, 0)) - - try { - await fs.writeFile(longPath, 'this is a test to see if this system supports extra-long filenames') - } catch (err) { - // if an ENOENT error occurred, we are hitting the max path length - return err.code === 'ENOENT' - } - - return false +function isMaybeWindowsMaxPathLengthError (err) { + return os.platform() === 'win32' && err.code === 'ENOENT' && err.syscall === 'realpath' } const start = async ({ zipFilePath, installDir, progress }) => { @@ -235,8 +218,8 @@ const start = async ({ zipFilePath, installDir, progress }) => { await unzip({ zipFilePath, installDir, progress }) } catch (err) { - const errorTemplate = (await isWindowsMaxFilePathError(err)) ? - errors.failedUnzipWindowsMaxFilePath + const errorTemplate = isMaybeWindowsMaxPathLengthError(err) ? + errors.failedUnzipWindowsMaxPathLength : errors.failedUnzip await throwFormErrorText(errorTemplate)(err) diff --git a/cli/test/lib/tasks/unzip_spec.js b/cli/test/lib/tasks/unzip_spec.js index c16cac91c3a5..286439f67010 100644 --- a/cli/test/lib/tasks/unzip_spec.js +++ b/cli/test/lib/tasks/unzip_spec.js @@ -30,26 +30,44 @@ describe('lib/tasks/unzip', function () { afterEach(function () { stdout.restore() + }) + + it('throws when cannot unzip', async function () { + try { + await unzip.start({ + zipFilePath: path.join('test', 'fixture', 'bad_example.zip'), + installDir, + }) + } catch (err) { + logger.error(err) - // return fs.removeAsync(installationDir) + return snapshot(normalize(this.stdout.toString())) + } + + throw new Error('should have failed') }) - it('throws when cannot unzip', function () { - const ctx = this + it('throws max path length error when cannot unzip due to realpath ENOENT on windows', async function () { + const err = new Error('failed') - return unzip - .start({ - zipFilePath: path.join('test', 'fixture', 'bad_example.zip'), - installDir, - }) - .then(() => { - throw new Error('should have failed') - }) - .catch((err) => { + err.code = 'ENOENT' + err.syscall = 'realpath' + + os.platform.returns('win32') + sinon.stub(fs, 'ensureDirAsync').rejects(err) + + try { + await unzip.start({ + zipFilePath: path.join('test', 'fixture', 'bad_example.zip'), + installDir, + }) + } catch (err) { logger.error(err) - snapshot('unzip error 1', normalize(ctx.stdout.toString())) - }) + return snapshot(normalize(this.stdout.toString())) + } + + throw new Error('should have failed') }) it('can really unzip', function () { From 999c805c58b5c26e03774a59eeb045d2c0974e36 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Wed, 13 Apr 2022 13:58:40 -0400 Subject: [PATCH 4/4] use an onlink --- cli/__snapshots__/unzip_spec.js | 2 +- cli/lib/errors.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/__snapshots__/unzip_spec.js b/cli/__snapshots__/unzip_spec.js index 5614600cc38a..ec4edf9dc004 100644 --- a/cli/__snapshots__/unzip_spec.js +++ b/cli/__snapshots__/unzip_spec.js @@ -21,7 +21,7 @@ Error: The Cypress App could not be unzipped. This is most likely because the maximum path length is being exceeded on your system. -Read here for solutions to this problem: https://on.cypress.io/error-messages#win-max-path-length +Read here for solutions to this problem: https://on.cypress.io/win-max-path-length-error ---------- diff --git a/cli/lib/errors.js b/cli/lib/errors.js index 05cc10425280..397123d5da6e 100644 --- a/cli/lib/errors.js +++ b/cli/lib/errors.js @@ -62,7 +62,7 @@ const failedUnzipWindowsMaxPathLength = { description: 'The Cypress App could not be unzipped.', solution: `This is most likely because the maximum path length is being exceeded on your system. - Read here for solutions to this problem: https://on.cypress.io/error-messages#win-max-path-length`, + Read here for solutions to this problem: https://on.cypress.io/win-max-path-length-error`, } const missingApp = (binaryDir) => {