From 45fa76e89aee6a9c65ff0827060dfda4ace7d2c8 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 3 Dec 2019 09:19:12 -0500 Subject: [PATCH] cli: unnzip Cypress using `unzip` utility on Linux (#5851) * unzip using 'unzip' utility on linux * add example unzip test --- cli/lib/tasks/unzip.js | 76 +++++++++++++++++++++++++++----- cli/test/lib/tasks/unzip_spec.js | 38 ++++++++++++++++ 2 files changed, 104 insertions(+), 10 deletions(-) diff --git a/cli/lib/tasks/unzip.js b/cli/lib/tasks/unzip.js index c341d62eb3ad..be526194221c 100644 --- a/cli/lib/tasks/unzip.js +++ b/cli/lib/tasks/unzip.js @@ -3,7 +3,7 @@ const is = require('check-more-types') const cp = require('child_process') const os = require('os') const yauzl = require('yauzl') -const debug = require('debug')('cypress:cli') +const debug = require('debug')('cypress:cli:unzip') const extract = require('extract-zip') const Promise = require('bluebird') const readline = require('readline') @@ -12,6 +12,10 @@ const { throwFormErrorText, errors } = require('../errors') const fs = require('../fs') const util = require('../util') +const unzipTools = { + extract, +} + // expose this function for simple testing const unzip = ({ zipFilePath, installDir, progress }) => { debug('unzipping from %s', zipFilePath) @@ -21,15 +25,17 @@ const unzip = ({ zipFilePath, installDir, progress }) => { throw new Error('Missing zip filename') } + const startTime = Date.now() + let yauzlDoneTime = 0 + return fs.ensureDirAsync(installDir) .then(() => { return new Promise((resolve, reject) => { return yauzl.open(zipFilePath, (err, zipFile) => { + yauzlDoneTime = Date.now() + if (err) return reject(err) - // debug('zipfile.paths:', zipFile) - // zipFile.on('entry', debug) - // debug(zipFile.readEntry()) const total = zipFile.entryCount debug('zipFile entries count', total) @@ -57,6 +63,8 @@ const unzip = ({ zipFilePath, installDir, progress }) => { } const unzipWithNode = () => { + debug('unzipping with node.js (slow)') + const endFn = (err) => { if (err) { return reject(err) @@ -70,15 +78,50 @@ const unzip = ({ zipFilePath, installDir, progress }) => { onEntry: tick, } - return extract(zipFilePath, opts, endFn) + return unzipTools.extract(zipFilePath, opts, endFn) } - //# we attempt to first unzip with the native osx - //# ditto because its less likely to have problems - //# with corruption, symlinks, or icons causing failures - //# and can handle resource forks - //# http://automatica.com.au/2011/02/unzip-mac-os-x-zip-in-terminal/ + const unzipWithUnzipTool = () => { + debug('unzipping via `unzip`') + + const inflatingRe = /inflating:/ + + const sp = cp.spawn('unzip', ['-o', zipFilePath, '-d', installDir]) + + sp.on('error', unzipWithNode) + + sp.on('close', (code) => { + if (code === 0) { + percent = 100 + notify(percent) + + return resolve() + } + + debug('`unzip` failed %o', { code }) + + return unzipWithNode() + }) + + sp.stdout.on('data', (data) => { + if (inflatingRe.test(data)) { + return tick() + } + }) + + sp.stderr.on('data', (data) => { + debug('`unzip` stderr %s', data) + }) + } + + // we attempt to first unzip with the native osx + // ditto because its less likely to have problems + // with corruption, symlinks, or icons causing failures + // and can handle resource forks + // http://automatica.com.au/2011/02/unzip-mac-os-x-zip-in-terminal/ const unzipWithOsx = () => { + debug('unzipping via `ditto`') + const copyingFileRe = /^copying file/ const sp = cp.spawn('ditto', ['-xkV', zipFilePath, installDir]) @@ -96,6 +139,8 @@ const unzip = ({ zipFilePath, installDir, progress }) => { return resolve() } + debug('`ditto` failed %o', { code }) + return unzipWithNode() }) @@ -113,6 +158,7 @@ const unzip = ({ zipFilePath, installDir, progress }) => { case 'darwin': return unzipWithOsx() case 'linux': + return unzipWithUnzipTool() case 'win32': return unzipWithNode() default: @@ -120,6 +166,12 @@ const unzip = ({ zipFilePath, installDir, progress }) => { } }) }) + .tap(() => { + debug('unzip completed %o', { + yauzlMs: yauzlDoneTime - startTime, + unzipMs: Date.now() - yauzlDoneTime, + }) + }) }) } @@ -147,4 +199,8 @@ const start = ({ zipFilePath, installDir, progress }) => { module.exports = { start, + utils: { + unzip, + unzipTools, + }, } diff --git a/cli/test/lib/tasks/unzip_spec.js b/cli/test/lib/tasks/unzip_spec.js index 86260b043464..20035c0cd968 100644 --- a/cli/test/lib/tasks/unzip_spec.js +++ b/cli/test/lib/tasks/unzip_spec.js @@ -3,6 +3,7 @@ require('../../spec_helper') const os = require('os') const path = require('path') const snapshot = require('../../support/snapshot') +const cp = require('child_process') const fs = require(`${lib}/fs`) const util = require(`${lib}/util`) @@ -63,4 +64,41 @@ describe('lib/tasks/unzip', function () { return fs.statAsync(installDir) }) }) + + // NOTE: hmm, running this test for some reason breaks 4 tests in verify_spec.js with very weird errors + context.skip('on linux', () => { + beforeEach(() => { + os.platform.returns('linux') + }) + + it('can try unzip first then fall back to node unzip', function () { + sinon.stub(unzip.utils.unzipTools, 'extract').resolves() + + const unzipChildProcess = { + on: sinon.stub(), + stdout: { + on () {}, + }, + stderr: { + on () {}, + }, + } + + unzipChildProcess.on.withArgs('error').yieldsAsync(0) + unzipChildProcess.on.withArgs('close').yieldsAsync(0) + sinon.stub(cp, 'spawn').withArgs('unzip').returns(unzipChildProcess) + + const zipFilePath = path.join('test', 'fixture', 'example.zip') + + return unzip + .start({ + zipFilePath, + installDir, + }) + .then(() => { + expect(cp.spawn).to.have.been.calledWith('unzip') + expect(unzip.utils.unzipTools.extract).to.be.calledWith(zipFilePath) + }) + }) + }) })