diff --git a/tap-snapshots/test/dir.js.test.cjs b/tap-snapshots/test/dir.js.test.cjs index cf0b093f..152b4b7a 100644 --- a/tap-snapshots/test/dir.js.test.cjs +++ b/tap-snapshots/test/dir.js.test.cjs @@ -9,7 +9,7 @@ exports[`test/dir.js TAP basic > extract 1`] = ` Object { "from": "file:test/fixtures/abbrev", "integrity": "{integrity}", - "resolved": "\${CWD}/test/fixtures/abbrev", + "resolved": "{CWD}/test/fixtures/abbrev", } ` @@ -18,7 +18,7 @@ Object { "_from": "file:test/fixtures/abbrev", "_id": "abbrev@1.1.1", "_integrity": null, - "_resolved": "\${CWD}/test/fixtures/abbrev", + "_resolved": "{CWD}/test/fixtures/abbrev", "author": "Isaac Z. Schlueter ", "description": "Like ruby's abbrev module, but in js", "devDependencies": Object { @@ -76,7 +76,7 @@ Object { "_from": "file:test/fixtures/abbrev", "_id": "abbrev@1.1.1", "_integrity": null, - "_resolved": "\${CWD}/test/fixtures/abbrev", + "_resolved": "{CWD}/test/fixtures/abbrev", "author": "Isaac Z. Schlueter ", "description": "Like ruby's abbrev module, but in js", "devDependencies": Object { @@ -84,7 +84,7 @@ Object { }, "dist": Object { "integrity": null, - "tarball": "file:\${CWD}/test/fixtures/abbrev", + "tarball": "file:{CWD}/test/fixtures/abbrev", }, "files": Array [ "abbrev.js", @@ -110,7 +110,7 @@ Object { "_from": "file:test/fixtures/abbrev", "_id": "abbrev@1.1.1", "_integrity": null, - "_resolved": "\${CWD}/test/fixtures/abbrev", + "_resolved": "{CWD}/test/fixtures/abbrev", "author": "Isaac Z. Schlueter ", "description": "Like ruby's abbrev module, but in js", "devDependencies": Object { @@ -144,7 +144,7 @@ Object { "_from": "file:test/fixtures/abbrev", "_id": "abbrev@1.1.1", "_integrity": "sha512-whatever-this-is-only-checked-if-we-extract-it", - "_resolved": "\${CWD}/test/fixtures/abbrev", + "_resolved": "{CWD}/test/fixtures/abbrev", "author": "Isaac Z. Schlueter ", "description": "Like ruby's abbrev module, but in js", "devDependencies": Object { @@ -152,7 +152,7 @@ Object { }, "dist": Object { "integrity": "{integrity}", - "tarball": "file:\${CWD}/test/fixtures/abbrev", + "tarball": "file:{CWD}/test/fixtures/abbrev", }, "files": Array [ "abbrev.js", @@ -177,7 +177,7 @@ exports[`test/dir.js TAP make bins executable > results of unpack 1`] = ` Object { "from": "file:test/fixtures/bin-object", "integrity": "{integrity}", - "resolved": "\${CWD}/test/fixtures/bin-object", + "resolved": "{CWD}/test/fixtures/bin-object", } ` @@ -185,7 +185,7 @@ exports[`test/dir.js TAP responds to foregroundScripts: true > extract 1`] = ` Object { "from": "file:test/fixtures/prepare-script", "integrity": "{integrity}", - "resolved": "\${CWD}/test/fixtures/prepare-script", + "resolved": "{CWD}/test/fixtures/prepare-script", } ` @@ -202,7 +202,7 @@ Object { "_from": "file:test/fixtures/prepare-script", "_id": "git-prepare-script@1.0.0", "_integrity": null, - "_resolved": "\${CWD}/test/fixtures/prepare-script", + "_resolved": "{CWD}/test/fixtures/prepare-script", "devDependencies": Object { "abbrev": "^1.1.1", }, @@ -227,13 +227,13 @@ Object { "_from": "file:test/fixtures/prepare-script", "_id": "git-prepare-script@1.0.0", "_integrity": null, - "_resolved": "\${CWD}/test/fixtures/prepare-script", + "_resolved": "{CWD}/test/fixtures/prepare-script", "devDependencies": Object { "abbrev": "^1.1.1", }, "dist": Object { "integrity": null, - "tarball": "file:\${CWD}/test/fixtures/prepare-script", + "tarball": "file:{CWD}/test/fixtures/prepare-script", }, "license": "ISC", "main": "index.js", @@ -251,7 +251,7 @@ exports[`test/dir.js TAP with prepare script > extract 1`] = ` Object { "from": "file:test/fixtures/prepare-script", "integrity": "{integrity}", - "resolved": "\${CWD}/test/fixtures/prepare-script", + "resolved": "{CWD}/test/fixtures/prepare-script", } ` @@ -268,7 +268,7 @@ Object { "_from": "file:test/fixtures/prepare-script", "_id": "git-prepare-script@1.0.0", "_integrity": null, - "_resolved": "\${CWD}/test/fixtures/prepare-script", + "_resolved": "{CWD}/test/fixtures/prepare-script", "devDependencies": Object { "abbrev": "^1.1.1", }, @@ -293,13 +293,13 @@ Object { "_from": "file:test/fixtures/prepare-script", "_id": "git-prepare-script@1.0.0", "_integrity": null, - "_resolved": "\${CWD}/test/fixtures/prepare-script", + "_resolved": "{CWD}/test/fixtures/prepare-script", "devDependencies": Object { "abbrev": "^1.1.1", }, "dist": Object { "integrity": null, - "tarball": "file:\${CWD}/test/fixtures/prepare-script", + "tarball": "file:{CWD}/test/fixtures/prepare-script", }, "license": "ISC", "main": "index.js", @@ -317,7 +317,7 @@ exports[`test/dir.js TAP with prepare script with scriptshell configuration > ex Object { "from": "file:test/fixtures/prepare-script", "integrity": "{integrity}", - "resolved": "\${CWD}/test/fixtures/prepare-script", + "resolved": "{CWD}/test/fixtures/prepare-script", } ` @@ -334,7 +334,7 @@ Object { "_from": "file:test/fixtures/prepare-script", "_id": "git-prepare-script@1.0.0", "_integrity": null, - "_resolved": "\${CWD}/test/fixtures/prepare-script", + "_resolved": "{CWD}/test/fixtures/prepare-script", "devDependencies": Object { "abbrev": "^1.1.1", }, @@ -359,13 +359,13 @@ Object { "_from": "file:test/fixtures/prepare-script", "_id": "git-prepare-script@1.0.0", "_integrity": null, - "_resolved": "\${CWD}/test/fixtures/prepare-script", + "_resolved": "{CWD}/test/fixtures/prepare-script", "devDependencies": Object { "abbrev": "^1.1.1", }, "dist": Object { "integrity": null, - "tarball": "file:\${CWD}/test/fixtures/prepare-script", + "tarball": "file:{CWD}/test/fixtures/prepare-script", }, "license": "ISC", "main": "index.js", diff --git a/tap-snapshots/test/file.js.test.cjs b/tap-snapshots/test/file.js.test.cjs index 2b1aba8d..109cb4d9 100644 --- a/tap-snapshots/test/file.js.test.cjs +++ b/tap-snapshots/test/file.js.test.cjs @@ -9,7 +9,7 @@ exports[`test/file.js TAP basic > extract 1`] = ` Object { "from": "file:test/fixtures/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", } ` @@ -18,7 +18,7 @@ Object { "_from": "file:test/fixtures/abbrev-1.1.1.tgz", "_id": "abbrev@1.1.1", "_integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "_resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "_resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", "author": "Isaac Z. Schlueter ", "description": "Like ruby's abbrev module, but in js", "devDependencies": Object { @@ -76,7 +76,7 @@ Object { "_from": "file:test/fixtures/abbrev-1.1.1.tgz", "_id": "abbrev@1.1.1", "_integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "_resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "_resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", "author": "Isaac Z. Schlueter ", "description": "Like ruby's abbrev module, but in js", "devDependencies": Object { @@ -84,7 +84,7 @@ Object { }, "dist": Object { "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "tarball": "file:\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "tarball": "file:{CWD}/test/fixtures/abbrev-1.1.1.tgz", }, "files": Array [ "abbrev.js", @@ -109,7 +109,7 @@ exports[`test/file.js TAP make bins executable bin-good > results of unpack 1`] Object { "from": "file:test/fixtures/bin-good.tgz", "integrity": "sha512-Fx11OiHxV82CztnPk+k0S6H/66J4/eUzZEMGX2dJjP+Mxfrm8fSzE4SQG604zWk17ELZsOGENCdWSkvj4cpjUw==", - "resolved": "\${CWD}/test/fixtures/bin-good.tgz", + "resolved": "{CWD}/test/fixtures/bin-good.tgz", } ` @@ -117,7 +117,7 @@ exports[`test/file.js TAP make bins executable bin-object > results of unpack 1` Object { "from": "file:test/fixtures/bin-object.tgz", "integrity": "sha512-TqzCjecWyQe8vqLbT0nv/OaWf0ptRZ2DnPmiuGUYJJb70shp02+/uu37IJSkM2ZEP1SAOeKrYrWPVIIYW+d//g==", - "resolved": "\${CWD}/test/fixtures/bin-object.tgz", + "resolved": "{CWD}/test/fixtures/bin-object.tgz", } ` @@ -125,7 +125,7 @@ exports[`test/file.js TAP make bins executable bin-string > results of unpack 1` Object { "from": "file:test/fixtures/bin-string.tgz", "integrity": "sha512-iCc87DMYVMofO221ksAlMD88Zgsr4OIvqeX73KxTPikWaQPvBFZpzI9FGWnD4PTLTyJzOSETQh86+IwEidJRZg==", - "resolved": "\${CWD}/test/fixtures/bin-string.tgz", + "resolved": "{CWD}/test/fixtures/bin-string.tgz", } ` @@ -133,7 +133,7 @@ exports[`test/file.js TAP with readme > extract-slow-json 1`] = ` Object { "from": "file:test/fixtures/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", } ` @@ -142,7 +142,7 @@ Object { "_from": "file:test/fixtures/abbrev-1.1.1.tgz", "_id": "abbrev@1.1.1", "_integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "_resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "_resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", "author": Object { "email": "i@izs.me", "name": "Isaac Z. Schlueter", @@ -237,7 +237,7 @@ Object { "_from": "file:test/fixtures/abbrev-1.1.1.tgz", "_id": "abbrev@1.1.1", "_integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "_resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "_resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", "author": Object { "email": "i@izs.me", "name": "Isaac Z. Schlueter", @@ -251,7 +251,7 @@ Object { }, "dist": Object { "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "tarball": "file:\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "tarball": "file:{CWD}/test/fixtures/abbrev-1.1.1.tgz", }, "files": Array [ "abbrev.js", diff --git a/tap-snapshots/test/index.js.test.cjs b/tap-snapshots/test/index.js.test.cjs index 6056eab3..f4ff1850 100644 --- a/tap-snapshots/test/index.js.test.cjs +++ b/tap-snapshots/test/index.js.test.cjs @@ -9,7 +9,7 @@ exports[`test/index.js TAP > extract 1`] = ` Object { "from": "file:test/fixtures/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", } ` @@ -18,7 +18,7 @@ Object { "_from": "file:test/fixtures/abbrev-1.1.1.tgz", "_id": "abbrev@1.1.1", "_integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "_resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "_resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", "author": "Isaac Z. Schlueter ", "description": "Like ruby's abbrev module, but in js", "devDependencies": Object { @@ -46,7 +46,7 @@ Object { "_from": "file:test/fixtures/abbrev-1.1.1.tgz", "_id": "abbrev@1.1.1", "_integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "_resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "_resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", "author": "Isaac Z. Schlueter ", "description": "Like ruby's abbrev module, but in js", "devDependencies": Object { @@ -80,7 +80,7 @@ Object { "_from": "file:test/fixtures/abbrev-1.1.1.tgz", "_id": "abbrev@1.1.1", "_integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "_resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "_resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", "author": "Isaac Z. Schlueter ", "description": "Like ruby's abbrev module, but in js", "devDependencies": Object { @@ -88,7 +88,7 @@ Object { }, "dist": Object { "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "tarball": "file:\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "tarball": "file:{CWD}/test/fixtures/abbrev-1.1.1.tgz", }, "files": Array [ "abbrev.js", @@ -110,13 +110,13 @@ Object { ` exports[`test/index.js TAP > resolve 1`] = ` -\${CWD}/test/fixtures/abbrev-1.1.1.tgz +{CWD}/test/fixtures/abbrev-1.1.1.tgz ` exports[`test/index.js TAP > tarball to file 1`] = ` Object { "from": "file:test/fixtures/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "resolved": "\${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", } ` diff --git a/test/bin.js b/test/bin.js index 6ead82eb..d98a1ac6 100644 --- a/test/bin.js +++ b/test/bin.js @@ -4,10 +4,10 @@ const { Minipass } = require('minipass') const pkg = require('../package.json') const bin = require.resolve(`../${pkg.bin.pacote}`) const { main, run, parseArg, parse } = require(bin) +const cleanSnapshot = require('./helpers/clean-snapshot.js') -t.cleanSnapshot = str => - str.split(pkg.version).join('{VERSION}') - .split(process.env.HOME).join('{HOME}') +t.cleanSnapshot = str => cleanSnapshot(str) + .split(pkg.version).join('{VERSION}') const pacote = require('../') pacote.resolve = (spec, conf) => diff --git a/test/dir.js b/test/dir.js index 72eda0c0..f4da393a 100644 --- a/test/dir.js +++ b/test/dir.js @@ -4,6 +4,8 @@ const t = require('tap') const Arborist = require('@npmcli/arborist') const fs = require('node:fs') const { relative, resolve, basename } = require('node:path') +const cleanSnapshot = require('./helpers/clean-snapshot.js') +const scriptMode = require('./helpers/script-mode.js') const loadActual = async (path) => { const arb = new Arborist({ path }) @@ -21,8 +23,7 @@ const DirFetcher = t.mock('../lib/dir.js', { const me = t.testdir() -t.cleanSnapshot = str => str - .split(process.cwd()).join('${CWD}') +t.cleanSnapshot = str => cleanSnapshot(str) .replace(/"integrity": ".*",/g, '"integrity": "{integrity}",') const abbrev = resolve(__dirname, 'fixtures/abbrev') @@ -99,13 +100,23 @@ t.test('responds to foregroundScripts: true', async t => { t.test('missing dir cannot be packed', async t => { const f = new DirFetcher('file:/this/dir/doesnt/exist', { tree: await loadActual() }) - return t.rejects(f.extract(me + '/nope'), { - message: `no such file or directory, open '/this/dir/doesnt/exist/package.json`, - errno: Number, - code: 'ENOENT', - syscall: 'open', - path: '/this/dir/doesnt/exist/package.json', - }) + if (process.platform !== 'win32') { + return t.rejects(f.extract(me + '/nope'), { + message: `no such file or directory, open '/this/dir/doesnt/exist/package.json`, + errno: Number, + code: 'ENOENT', + syscall: 'open', + path: '/this/dir/doesnt/exist/package.json', + }) + } else { + return t.rejects(f.extract(me + '/nope'), { + errno: Number, + code: 'ENOENT', + syscall: 'open', + path: new RegExp('\\\\this\\\\dir\\\\doesnt\\\\exist\\\\package.json'), + name: 'Error', + }) + } }) t.test('when read fails', async t => { @@ -140,7 +151,7 @@ t.test('make bins executable', async t => { ...(res.integrity === newIntegrity ? { integrity: oldIntegrity } : {}), } t.matchSnapshot(resTest, 'results of unpack') - t.equal(fs.statSync(target + '/script.js').mode & 0o111, 0o111) + t.equal(fs.statSync(target + '/script.js').mode & scriptMode(), scriptMode()) }) t.test('exposes tarCreateOptions method', async t => { diff --git a/test/fetcher.js b/test/fetcher.js index 6f857b35..71739033 100644 --- a/test/fetcher.js +++ b/test/fetcher.js @@ -9,6 +9,8 @@ const Fetcher = require('../lib/fetcher.js') // we actually use a file fetcher for this, because we need implementations const FileFetcher = require('../lib/file.js') const abbrevMani = require('./fixtures/abbrev-manifest-min.json') +const cleanSnapshot = require('./helpers/clean-snapshot.js') +const scriptMode = require('./helpers/script-mode.js') const byDigest = cacache.get.stream.byDigest @@ -25,7 +27,7 @@ fs.futimesSync = () => { throw new Error('do not call futimesSync') } -t.cleanSnapshot = s => s.split(process.cwd()).join('{CWD}') +t.cleanSnapshot = str => cleanSnapshot(str) const me = t.testdir() const abbrev = resolve(__dirname, 'fixtures/abbrev-1.1.1.tgz') @@ -34,6 +36,10 @@ const weird = resolve(__dirname, 'fixtures/weird-pkg.tgz') const weirdspec = `file:${relative(process.cwd(), weird)}` const cache = resolve(me, 'cache') +t.teardown(async () => { + fs.rmSync(me, { recursive: true, force: true, maxRetries: 3 }) +}) + t.test('do not mutate opts object passed in', t => { const opts = {} const f = new FileFetcher(abbrevspec, opts) @@ -150,7 +156,8 @@ t.test('tarballFile', t => { .then(() => fsm.WriteStream = WriteStream) }) - t.test('file not found', t => { + // Blocks removal of the test directory on Windows + t.test('file not found', { skip: process.platform === 'win32' }, t => { const f = abbrev + '-not-found.tgz' return t.rejects(new FileFetcher(f, { cache }) .tarballFile(target + '/not-found.tgz'), { @@ -206,37 +213,73 @@ t.test('extract', t => { preferOnline: false, }).extract(target + '/badcache') .then(({ resolved, integrity }) => { - t.match(logs, [ - ['http', - 'cache', - /file:test\/fixtures\/abbrev-1.1.1.tgz.*(cache hit)/, - ], - ['warn', 'tar', 'zlib: incorrect header check'], - [ - 'silly', - 'tar', - { message: 'zlib: incorrect header check', - errno: Number, - code: 'Z_DATA_ERROR', - recoverable: false, - tarCode: 'TAR_ABORT', - }, - ], - [ - 'warn', - 'tarball', - 'cached data for file:test/fixtures/abbrev-1.1.1.tgz (sha512-' + - 'nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTN' + - 'NfNtAfZ9/1RtehkszU9qcTii0Q==) seems to be corrupted. ' + - 'Refreshing cache.', - ], - [ - 'silly', - 'tarball', - 'no local data for file:test/fixtures/abbrev-1.1.1.tgz. ' + - 'Extracting by manifest.', - ], - ], 'got expected logs') + if (process.platform !== 'win32') { + t.match(logs, [ + ['http', + 'cache', + /file:test\/fixtures\/abbrev-1.1.1.tgz.*(cache hit)/, + ], + ['warn', 'tar', 'zlib: incorrect header check'], + [ + 'silly', + 'tar', + { + message: 'zlib: incorrect header check', + errno: Number, + code: 'Z_DATA_ERROR', + recoverable: false, + tarCode: 'TAR_ABORT', + }, + ], + [ + 'warn', + 'tarball', + 'cached data for file:test/fixtures/abbrev-1.1.1.tgz (sha512-' + + 'nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTN' + + 'NfNtAfZ9/1RtehkszU9qcTii0Q==) seems to be corrupted. ' + + 'Refreshing cache.', + ], + [ + 'silly', + 'tarball', + 'no local data for file:test/fixtures/abbrev-1.1.1.tgz. ' + + 'Extracting by manifest.', + ], + ], 'got expected logs') + } else { + t.match(logs, [ + ['http', + 'cache', + /file:test\\fixtures\\abbrev-1.1.1.tgz.*(cache hit)/, + ], + ['warn', 'tar', 'zlib: incorrect header check'], + [ + 'silly', + 'tar', + { + message: 'zlib: incorrect header check', + errno: Number, + code: 'Z_DATA_ERROR', + recoverable: false, + tarCode: 'TAR_ABORT', + }, + ], + [ + 'warn', + 'tarball', + 'cached data for file:test\\fixtures\\abbrev-1.1.1.tgz (sha512-' + + 'nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTN' + + 'NfNtAfZ9/1RtehkszU9qcTii0Q==) seems to be corrupted. ' + + 'Refreshing cache.', + ], + [ + 'silly', + 'tarball', + 'no local data for file:test\\fixtures\\abbrev-1.1.1.tgz. ' + + 'Extracting by manifest.', + ], + ], 'got expected logs') + } process.removeListener('log', onlog) cacache.get.stream.byDigest = byDigest return check('badcache')({ resolved, integrity }) @@ -286,9 +329,9 @@ t.test('extract', t => { .catch(er => { t.match(er, { message: 'sha512-0 ' + - 'integrity checksum failed when using sha512: wanted sha512-' + - '0 but got sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5p' + - 'OFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==. (2301 bytes)', + 'integrity checksum failed when using sha512: wanted sha512-' + + '0 but got sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5p' + + 'OFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==. (2301 bytes)', code: 'EINTEGRITY', found: Object, expected: [ @@ -302,30 +345,62 @@ t.test('extract', t => { algorithm: 'sha512', sri: Object, }, 'got expected error') - t.match(logs, [ - ['http', - 'cache', - /file:test\/fixtures\/abbrev-1.1.1.tgz.*(cache hit)/, - ], - [ - 'silly', - 'tarball', - 'no local data for file:test/fixtures/abbrev-1.1.1.tgz. ' + - 'Extracting by manifest.', - ], - [ - 'warn', - 'tarball', - 'tarball data for file:test/fixtures/abbrev-1.1.1.tgz ' + - '(sha512-0) seems to be corrupted. Trying again.', - ], - [ - 'warn', - 'tarball', - 'tarball data for file:test/fixtures/abbrev-1.1.1.tgz ' + - '(sha512-0) seems to be corrupted. Trying again.', - ], - ], 'got expected logs') + if (process.platform !== 'win32') { + t.match(logs, [ + [ + 'http', + 'cache', + /file:test\/fixtures\/abbrev-1.1.1.tgz.*(cache hit)/, + ], + [ + 'silly', + 'tarball', + 'no local data for file:test/fixtures/abbrev-1.1.1.tgz. ' + + 'Extracting by manifest.', + ], + [ + 'warn', + 'tarball', + 'tarball data for file:test/fixtures/abbrev-1.1.1.tgz ' + + '(sha512-0) seems to be corrupted. Trying again.', + ], + [ + 'warn', + 'tarball', + 'tarball data for file:test/fixtures/abbrev-1.1.1.tgz ' + + '(sha512-0) seems to be corrupted. Trying again.', + ], + ], 'got expected logs') + } else { + t.match(logs, [ + [ + 'http', + 'cache', + /file:test\\+fixtures\\+abbrev-1.1.1.tgz.*(cache hit)/, + ], + [ + 'warn', + 'tar', + 'TAR_BAD_ARCHIVE: Unrecognized archive format', + ], + [ + 'silly', + 'tar', + ], + [ + 'warn', + 'tarball', + 'cached data for file:test\\fixtures\\abbrev-1.1.1.tgz ' + + '(sha512-0) seems to be corrupted. Refreshing cache.', + ], + [ + 'silly', + 'tarball', + 'no local data for file:test\\fixtures\\abbrev-1.1.1.tgz. ' + + 'Extracting by manifest.', + ], + ], 'got expected logs') + } process.removeListener('log', onlog) }) }) @@ -472,7 +547,7 @@ t.test('make bins executable', async t => { const target = resolve(me, basename(file, '.tgz')) const res = await f.extract(target) t.matchSnapshot(res, 'results of unpack') - t.equal(fs.statSync(target + '/script.js').mode & 0o111, 0o111) + t.equal(fs.statSync(target + '/script.js').mode & scriptMode(), scriptMode()) }) t.test('set integrity, pick default algo', t => { diff --git a/test/file.js b/test/file.js index f3debfcf..4244aaab 100644 --- a/test/file.js +++ b/test/file.js @@ -2,8 +2,10 @@ const t = require('tap') const { relative, resolve, basename } = require('node:path') const fs = require('node:fs') const FileFetcher = require('../lib/file.js') +const cleanSnapshot = require('./helpers/clean-snapshot.js') +const scriptMode = require('./helpers/script-mode.js') -t.cleanSnapshot = str => str.split(process.cwd()).join('${CWD}') +t.cleanSnapshot = str => cleanSnapshot(str) const me = t.testdir({ cache: {} }) const cache = resolve(me, 'cache') @@ -42,7 +44,7 @@ t.test('make bins executable', t => { const target = resolve(me, basename(file, '.tgz')) const res = await f.extract(target) t.matchSnapshot(res, 'results of unpack') - t.equal(fs.statSync(target + '/script.js').mode & 0o111, 0o111) + t.equal(fs.statSync(target + '/script.js').mode & scriptMode(), scriptMode()) })) }) diff --git a/test/fixtures/abbrev-manifest-file.json b/test/fixtures/abbrev-manifest-file.json index 106e7a42..fdad4ada 100644 --- a/test/fixtures/abbrev-manifest-file.json +++ b/test/fixtures/abbrev-manifest-file.json @@ -32,5 +32,5 @@ "homepage": "https://github.com/isaacs/abbrev-js#readme", "_id": "abbrev@1.1.1", "_integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "_resolved": "${CWD}/test/fixtures/abbrev-1.1.1.tgz" + "_resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz" } diff --git a/test/fixtures/abbrev-packument-file.json b/test/fixtures/abbrev-packument-file.json index 7aa59d66..74af545b 100644 --- a/test/fixtures/abbrev-packument-file.json +++ b/test/fixtures/abbrev-packument-file.json @@ -38,9 +38,9 @@ "homepage": "https://github.com/isaacs/abbrev-js#readme", "_id": "abbrev@1.1.1", "_integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "_resolved": "${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "_resolved": "{CWD}/test/fixtures/abbrev-1.1.1.tgz", "dist": { - "tarball": "file:${CWD}/test/fixtures/abbrev-1.1.1.tgz", + "tarball": "file:{CWD}/test/fixtures/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" } } diff --git a/test/git.js b/test/git.js index c48d402f..007446fc 100644 --- a/test/git.js +++ b/test/git.js @@ -18,6 +18,14 @@ const httpPort = 18000 + (+process.env.TAP_CHILD_ID || 0) const hostedUrl = `http://localhost:${httpPort}` const gitPort = 12345 + (+process.env.TAP_CHILD_ID || 0) +// PromiseSpawn does not work reliably on Windows and leads to "spawn NOENT npm" errors. +// https://github.com/npm/promise-spawn/issues/73 +// Until that is fixed, we skip these tests on Windows. +const isWindows = process.platform === 'win32' + +// script for creating a file +const touchFile = isWindows ? 'type nul > foo' : 'touch foo' + HostedGit.addHost('localhost', { domain: 'localhost', protocols: ['git+https:', 'git+ssh:'], @@ -83,7 +91,7 @@ const opts = { cache, Arborist } const abbrevSpec = `file:${abbrev}` let REPO_HEAD = '' -t.test('setup', { bail: true }, t => { +t.test('setup', { bail: true, skip: isWindows && 'posix only' }, t => { t.test('create repo', () => { const git = (...cmd) => spawnGit(cmd, { cwd: repo }) const write = (f, c) => fs.writeFileSync(`${repo}/${f}`, c) @@ -136,7 +144,7 @@ t.test('setup', { bail: true }, t => { .then(() => npm('version', '1.0.0')) - // for our hosted service + // for our hosted service .then(() => tar.c({ file: me + '/repo-1.0.0.tgz', gzip: true, @@ -305,7 +313,7 @@ t.test('setup', { bail: true }, t => { name: 'a', version: '1.0.0', scripts: { - prepare: 'touch foo', + prepare: touchFile, }, }))) .then(() => git('add', 'a/package.json')) @@ -327,7 +335,7 @@ t.test('setup', { bail: true }, t => { name: 'prepack-root', version: '1.0.0', scripts: { - prepare: 'touch foo', + prepare: touchFile, }, }))) .then(() => git('add', 'package.json')) @@ -376,7 +384,7 @@ t.test('setup', { bail: true }, t => { t.end() }) -t.test('basic stuff', async t => { +t.test('basic stuff', { skip: isWindows && 'posix only' }, async t => { const g = new GitFetcher(remote, opts) t.same(g.types, ['git']) t.equal(g.resolved, null) @@ -419,7 +427,7 @@ t.test('basic stuff', async t => { fs.statSync(me + '/s/fooblz/package.json') }) -t.test('ignores integrity for git deps', async (t) => { +t.test('ignores integrity for git deps', { skip: isWindows && 'posix only' }, async (t) => { t.plan(3) const logHandler = (level, msg) => { t.equal(level, 'warn') @@ -443,150 +451,154 @@ t.test('ignores integrity for git deps', async (t) => { t.end() }) -t.test('weird hosted that doesnt provide any fetch targets', t => { - const hosted = { - git () { - return null - }, - ssh () { - return null - }, - sshurl () { - return null - }, - https () { - return null - }, - tarball () { - return null - }, - shortcut () { - return `weird:${remote}` - }, - } +t.test('weird hosted that doesnt provide any fetch targets', { skip: isWindows && 'posix only' }, + t => { + const hosted = { + git () { + return null + }, + ssh () { + return null + }, + sshurl () { + return null + }, + https () { + return null + }, + tarball () { + return null + }, + shortcut () { + return `weird:${remote}` + }, + } - const spec = npa(remote) - spec.hosted = hosted - t.rejects(new GitFetcher(Object.assign(spec, { hosted }), opts).resolve(), { - message: `No git url for ${remote}`, - }) + const spec = npa(remote) + spec.hosted = hosted + t.rejects(new GitFetcher(Object.assign(spec, { hosted }), opts).resolve(), { + message: `No git url for ${remote}`, + }) - const resSpec = npa(`${remote}#${REPO_HEAD}`) - resSpec.hosted = hosted - t.rejects(new GitFetcher(Object.assign(resSpec, { hosted }), opts) - .extract(`${me}/weird-hosted-extract`), { - message: `No git url for ${remote}`, - }) + const resSpec = npa(`${remote}#${REPO_HEAD}`) + resSpec.hosted = hosted + t.rejects(new GitFetcher(Object.assign(resSpec, { hosted }), opts) + .extract(`${me}/weird-hosted-extract`), { + message: `No git url for ${remote}`, + }) - t.end() -}) + t.end() + }) -t.test('extract from tarball from hosted git service', async t => { - // run in both ssh and https url types from a hosted service - // both of these actually produce a git:// url so that the test - // doesn't hang waiting for SSH key approval/passphrases. - const domains = ['localhost', 'localhostssh'] - - for (const domain of domains) { - t.test(domain, async t => { - const runTest = nameat => async t => { - const spec = npa(`${nameat}${domain}:repo/x#${REPO_HEAD}`) - const g = new GitFetcher(spec, opts) - const m = await g.manifest() - t.match(m, { - name: 'repo', - version: '1.0.0', - description: 'just some random thing', - devDependencies: { - abbrev: abbrevSpec, - }, - scripts: { prepare: 'node prepare.js', test: 'node index.js' }, - files: ['index.js'], - _id: 'repo@1.0.0', - _integrity: /^sha512-/, - _resolved: `${remoteHosted}#${REPO_HEAD}`, - }) - const p = await g.packument() - t.match(p, { - name: 'repo', - 'dist-tags': { latest: '1.0.0' }, - versions: { - '1.0.0': { - name: 'repo', - version: '1.0.0', - description: 'just some random thing', - devDependencies: { - abbrev: abbrevSpec, +t.test('extract from tarball from hosted git service', { skip: isWindows && 'posix only' }, + async t => { + // run in both ssh and https url types from a hosted service + // both of these actually produce a git:// url so that the test + // doesn't hang waiting for SSH key approval/passphrases. + const domains = ['localhost', 'localhostssh'] + + for (const domain of domains) { + t.test(domain, async t => { + const runTest = nameat => async t => { + const spec = npa(`${nameat}${domain}:repo/x#${REPO_HEAD}`) + const g = new GitFetcher(spec, opts) + const m = await g.manifest() + t.match(m, { + name: 'repo', + version: '1.0.0', + description: 'just some random thing', + devDependencies: { + abbrev: abbrevSpec, + }, + scripts: { prepare: 'node prepare.js', test: 'node index.js' }, + files: ['index.js'], + _id: 'repo@1.0.0', + _integrity: /^sha512-/, + _resolved: `${remoteHosted}#${REPO_HEAD}`, + }) + const p = await g.packument() + t.match(p, { + name: 'repo', + 'dist-tags': { latest: '1.0.0' }, + versions: { + '1.0.0': { + name: 'repo', + version: '1.0.0', + description: 'just some random thing', + devDependencies: { + abbrev: abbrevSpec, + }, + scripts: { prepare: 'node prepare.js', test: 'node index.js' }, + files: ['index.js'], + _id: 'repo@1.0.0', + _integrity: /^sha512-/, + _resolved: `${remoteHosted}#${REPO_HEAD}`, + dist: {}, }, - scripts: { prepare: 'node prepare.js', test: 'node index.js' }, - files: ['index.js'], - _id: 'repo@1.0.0', - _integrity: /^sha512-/, - _resolved: `${remoteHosted}#${REPO_HEAD}`, - dist: {}, }, - }, - }) - await g.extract(me + '/hosted') - t.throws(() => fs.statSync(me + '/hosted/prepare.js')) - fs.statSync(me + '/hosted/index.js') - } + }) + await g.extract(me + '/hosted') + t.throws(() => fs.statSync(me + '/hosted/prepare.js')) + fs.statSync(me + '/hosted/index.js') + } - t.test('with repo@ on the spec', runTest('repo@')) - t.test('without repo@on the spec', runTest('')) - }) - } -}) + t.test('with repo@ on the spec', runTest('repo@')) + t.test('without repo@on the spec', runTest('')) + }) + } + }) -t.test('include auth with hosted https when provided', async t => { - const spec = `git+https://user:pass@127.0.0.1/repo` - const g = new GitFetcher(spec, opts) - const resolved = await g.resolve() - // this weird url is because our fakey mock hosted service's - // https() method returns a git:// url, since the git daemon we - // spun up only responds to the git:// protocol, not https. - // But! we are verifying that it IS using the https() method, - // and not the sshurl() method, because that one returns a - // 'do not use this' string. - t.equal(resolved, `git+git://127.0.0.1:${gitPort}/repo#${REPO_HEAD}`) - t.equal(g.spec.hosted.shortcut(), 'localhosthttps:repo/x', - 'using the correct dummy hosted service') - - t.test('fail, but do not fall through to sshurl', async t => { - const badSpec = `git+https://user:pass@127.0.0.1/no-repo-here` - const failer = new GitFetcher(badSpec, { - ...opts, - resolved: resolved.replace(/\/repo/, '/no-repo-here'), - }) - t.equal(failer.spec.hosted.shortcut(), 'localhosthttps:no-repo-here/x', +t.test('include auth with hosted https when provided', { skip: isWindows && 'posix only' }, + async t => { + const spec = `git+https://user:pass@127.0.0.1/repo` + const g = new GitFetcher(spec, opts) + const resolved = await g.resolve() + // this weird url is because our fakey mock hosted service's + // https() method returns a git:// url, since the git daemon we + // spun up only responds to the git:// protocol, not https. + // But! we are verifying that it IS using the https() method, + // and not the sshurl() method, because that one returns a + // 'do not use this' string. + t.equal(resolved, `git+git://127.0.0.1:${gitPort}/repo#${REPO_HEAD}`) + t.equal(g.spec.hosted.shortcut(), 'localhosthttps:repo/x', 'using the correct dummy hosted service') - const path = t.testdir({}) - await t.rejects(failer.extract(path), { - args: ['--no-replace-objects', 'ls-remote', `git://127.0.0.1:${gitPort}/no-repo-here`], + + t.test('fail, but do not fall through to sshurl', async t => { + const badSpec = `git+https://user:pass@127.0.0.1/no-repo-here` + const failer = new GitFetcher(badSpec, { + ...opts, + resolved: resolved.replace(/\/repo/, '/no-repo-here'), + }) + t.equal(failer.spec.hosted.shortcut(), 'localhosthttps:no-repo-here/x', + 'using the correct dummy hosted service') + const path = t.testdir({}) + await t.rejects(failer.extract(path), { + args: ['--no-replace-objects', 'ls-remote', `git://127.0.0.1:${gitPort}/no-repo-here`], + }) }) }) -}) -t.test('include .gitignore in hosted tarballs for preparation', async t => { - const spec = npa(`localhost:foo/y#${REPO_HEAD}`) - spec.hosted.tarball = () => - `http://localhost:${httpPort}/prepare-requires-gitignore-1.2.3.tgz` - const g = new GitFetcher(spec, opts) - const dir = t.testdir() - await g.extract(dir) - t.strictSame(fs.readdirSync(dir).sort((a, b) => a.localeCompare(b)), [ - 'index.js', - 'package.json', - 'prepare_ran_successfully', - ]) -}) +t.test('include .gitignore in hosted tarballs for preparation', { skip: isWindows && 'posix only' }, + async t => { + const spec = npa(`localhost:foo/y#${REPO_HEAD}`) + spec.hosted.tarball = () => + `http://localhost:${httpPort}/prepare-requires-gitignore-1.2.3.tgz` + const g = new GitFetcher(spec, opts) + const dir = t.testdir() + await g.extract(dir) + t.strictSame(fs.readdirSync(dir).sort((a, b) => a.localeCompare(b)), [ + 'index.js', + 'package.json', + 'prepare_ran_successfully', + ]) + }) -t.test('add git sha to hosted git shorthand', t => +t.test('add git sha to hosted git shorthand', { skip: isWindows && 'posix only' }, t => new GitFetcher('localhost:repo/x', opts) // it adds the git+ because it thinks it's https .resolve().then(r => t.equal(r, `git+${remoteHosted}#${REPO_HEAD}`))) -t.test('fetch a weird ref', t => { +t.test('fetch a weird ref', { skip: isWindows && 'posix only' }, t => { let head3 = '' t.test('hosted', async t => { const result = await new GitFetcher('localhost:repo/x#HEAD~3', opts).extract(me + '/h3h') @@ -605,44 +617,48 @@ t.test('fetch a weird ref', t => { t.end() }) -t.test('fetch a private repo where the tgz is a 404', () => { +t.test('fetch a private repo where the tgz is a 404', { skip: isWindows && 'posix only' }, () => { const gf = new GitFetcher(`localhost:repo/x#${REPO_HEAD}`, opts) gf.spec.hosted.tarball = () => `${hostedUrl}/not-found.tgz` // should fetch it by falling back to ssh when it gets an http error return gf.extract(me + '/no-tgz') }) -t.test('fetch a private repo where the tgz is not a tarball', t => { - const gf = new GitFetcher(`localhost:repo/x#${REPO_HEAD}`, opts) - gf.spec.hosted.tarball = () => `${hostedUrl}/not-tar.tgz` - // should NOT retry, because the error was not an HTTP fetch error - return t.rejects(gf.extract(me + '/bad-tgz'), { - code: 'TAR_BAD_ARCHIVE', +t.test('fetch a private repo where the tgz is not a tarball', { skip: isWindows && 'posix only' }, + t => { + const gf = new GitFetcher(`localhost:repo/x#${REPO_HEAD}`, opts) + gf.spec.hosted.tarball = () => `${hostedUrl}/not-tar.tgz` + // should NOT retry, because the error was not an HTTP fetch error + return t.rejects(gf.extract(me + '/bad-tgz'), { + code: 'TAR_BAD_ARCHIVE', + }) }) -}) -t.test('resolved is a git+ssh url for hosted repos that support it', t => { - const hash = '0000000000000000000000000000000000000000' - const gf = new GitFetcher(`github:x/y#${hash}`, { ...opts, cache: null }) - t.equal(gf.resolved, `git+ssh://git@github.com/x/y.git#${hash}`) - t.end() -}) +t.test('resolved is a git+ssh url for hosted repos that support it', + { skip: isWindows && 'posix only' }, t => { + const hash = '0000000000000000000000000000000000000000' + const gf = new GitFetcher(`github:x/y#${hash}`, { ...opts, cache: null }) + t.equal(gf.resolved, `git+ssh://git@github.com/x/y.git#${hash}`) + t.end() + }) -t.test('resolved preserves git+ssh url for non-hosted repos', t => { - const hash = '0000000000000000000000000000000000000000' - const url = `git+ssh://git@test/x/y.git#${hash}` - const gf = new GitFetcher(url, { ...opts, cache: null }) - t.equal(gf.resolved, url) - t.end() -}) +t.test('resolved preserves git+ssh url for non-hosted repos', { skip: isWindows && 'posix only' }, + t => { + const hash = '0000000000000000000000000000000000000000' + const url = `git+ssh://git@test/x/y.git#${hash}` + const gf = new GitFetcher(url, { ...opts, cache: null }) + t.equal(gf.resolved, url) + t.end() + }) -t.test('fall back to ssh url if https url fails or is missing', t => { - const gf = new GitFetcher(`localhostssh:repo/x`, opts) - return gf.extract(`${me}/localhostssh`).then(({ resolved }) => - t.equal(resolved, `git+git://127.0.0.1:${gitPort}/repo#${REPO_HEAD}`)) -}) +t.test('fall back to ssh url if https url fails or is missing', { skip: isWindows && 'posix only' }, + t => { + const gf = new GitFetcher(`localhostssh:repo/x`, opts) + return gf.extract(`${me}/localhostssh`).then(({ resolved }) => + t.equal(resolved, `git+git://127.0.0.1:${gitPort}/repo#${REPO_HEAD}`)) + }) -t.test('repoUrl function', async t => { +t.test('repoUrl function', { skip: isWindows && 'posix only' }, async t => { const proj = 'isaacs/abbrev-js' const { hosted: shortcut } = npa(`github:${proj}`) const { hosted: hasAuth } = npa(`git+https://user:pass@github.com/${proj}`) @@ -659,74 +675,80 @@ t.test('repoUrl function', async t => { t.match(repoUrl(git), expectNoAuth) }) -t.test('handle it when prepared git deps depend on each other', async t => { - // now we've created both repos, and they each depend on the other - // our mocked npm bin should safely prevent an infinite regress if - // this test fails, and just log that the appropriate env got set. - const path = t.testdir({ - 'npm-mock.js': fs.readFileSync(resolve(fixtures, 'npm-mock.js')).toString(), - }) - - process.env._PACOTE_TEST_PATH_ = dirname(__dirname) - process.env._PACOTE_TEST_OPTS_ = JSON.stringify(opts) - t.teardown(() => { - delete process.env._PACOTE_TEST_PATH_ - delete process.env._PACOTE_TEST_OPTS_ - }) +t.test('handle it when prepared git deps depend on each other', { skip: isWindows && 'posix only' }, + async t => { + // now we've created both repos, and they each depend on the other + // our mocked npm bin should safely prevent an infinite regress if + // this test fails, and just log that the appropriate env got set. + const path = t.testdir({ + 'npm-mock.js': fs.readFileSync(resolve(fixtures, 'npm-mock.js')).toString(), + }) - for (const project of ['cycle-a', 'cycle-b']) { - const localRemote = `git://localhost:${gitPort}/${project}` - const local = `${path}/${project}` - const npmBin = `${path}/npm-mock.js` - const g = new GitFetcher(localRemote, { ...opts, npmBin }) - await g.extract(local) - const log = JSON.parse(fs.readFileSync(`${local}/log`, 'utf8')) - t.match(log, { - argv: [ - process.execPath, - npmBin, - ], - noPrepare: [g.resolved], - cwd: new RegExp(`${me}/cache/tmp/git-clone[a-zA-Z0-9]{6,8}`), + process.env._PACOTE_TEST_PATH_ = dirname(__dirname) + process.env._PACOTE_TEST_OPTS_ = JSON.stringify(opts) + t.teardown(() => { + delete process.env._PACOTE_TEST_PATH_ + delete process.env._PACOTE_TEST_OPTS_ }) - // our rudimentary package manager dumps the deps into the pkg root - // but it doesn't get installed once the loop is detected. - const base = basename(local) - const other = base === 'cycle-a' ? 'cycle-b' : 'cycle-a' - t.strictSame(require(`${path}/${base}/${other}/${base}/package.json`), - require(`${local}/package.json`)) - t.throws(() => { - require(`${path}/${base}/${other}/${base}/${other}/package.json`) - }, 'does not continue installing once loop is detected') - } -}) -t.test('missing branch name throws pathspec error', async (t) => { - const domains = ['localhostssh', 'localhosthttps', 'localhost'] - - for (const domain of domains) { - await t.rejects( - new GitFetcher( - `${domain}:repo/x#this-branch-does-not-exist`, - opts - ).resolve(), - { - constructor: /GitPathspecError/, - }, - domain - ) - await t.rejects( - new GitFetcher( - `${domain}:repo/x#this-branch-does-not-exist`, - opts - ).manifest(), - { - constructor: /GitPathspecError/, - }, domain) - } -}) + for (const project of ['cycle-a', 'cycle-b']) { + const localRemote = `git://localhost:${gitPort}/${project}` + const local = `${path}/${project}` + const npmBin = process.platform !== 'win32' ? + `${path}/npm-mock.js` : `${path}\\npm-mock.js` + const g = new GitFetcher(localRemote, { ...opts, npmBin }) + await g.extract(local) + const log = JSON.parse(fs.readFileSync(`${local}/log`, 'utf8')) + const cwdRegex = process.platform !== 'win32' ? + new RegExp(`${me}/cache/tmp/git-clone[a-zA-Z0-9]{6,8}`) : + new RegExp(`${me}\\cache\\tmp\\git-clone[a-zA-Z0-9]{6,8}`.replace(/\\/g, '\\\\')) + t.match(log, { + argv: [ + process.execPath, + npmBin, + ], + noPrepare: [g.resolved], + cwd: cwdRegex, + }) + // our rudimentary package manager dumps the deps into the pkg root + // but it doesn't get installed once the loop is detected. + const base = basename(local) + const other = base === 'cycle-a' ? 'cycle-b' : 'cycle-a' + t.strictSame(require(`${path}/${base}/${other}/${base}/package.json`), + require(`${local}/package.json`)) + t.throws(() => { + require(`${path}/${base}/${other}/${base}/${other}/package.json`) + }, 'does not continue installing once loop is detected') + } + }) + +t.test('missing branch name throws pathspec error', { skip: isWindows && 'posix only' }, + async (t) => { + const domains = ['localhostssh', 'localhosthttps', 'localhost'] + + for (const domain of domains) { + await t.rejects( + new GitFetcher( + `${domain}:repo/x#this-branch-does-not-exist`, + opts + ).resolve(), + { + constructor: /GitPathspecError/, + }, + domain + ) + await t.rejects( + new GitFetcher( + `${domain}:repo/x#this-branch-does-not-exist`, + opts + ).manifest(), + { + constructor: /GitPathspecError/, + }, domain) + } + }) -t.test('simple repo with workspaces', async t => { +t.test('simple repo with workspaces', { skip: isWindows && 'posix only' }, async t => { const ws = new GitFetcher(workspacesRemote, opts) const extract = resolve(me, 'extract-workspaces') await ws.extract(extract) @@ -738,7 +760,7 @@ t.test('simple repo with workspaces', async t => { ) }) -t.test('simple repo with only a prepack script', async t => { +t.test('simple repo with only a prepack script', { skip: isWindows && 'posix only' }, async t => { const ws = new GitFetcher(prepackRemote, opts) const extract = resolve(me, 'extract-prepack') await ws.extract(extract) @@ -750,7 +772,7 @@ t.test('simple repo with only a prepack script', async t => { ) }) -t.test('fails without arborist constructor', async t => { +t.test('fails without arborist constructor', { skip: isWindows && 'posix only' }, async t => { const ws = new GitFetcher(prepackRemote, { cache }) const extract = resolve(me, 'extract-prepack') t.rejects(() => ws.extract(extract)) diff --git a/test/helpers/clean-snapshot.js b/test/helpers/clean-snapshot.js new file mode 100644 index 00000000..fbd95abe --- /dev/null +++ b/test/helpers/clean-snapshot.js @@ -0,0 +1,29 @@ +// paths used in snapshot testing +const cwd = process.cwd() +const home = process.env.HOME +const execPath = process.execPath + +const cleanSnapshot = (input) => { + // On MacOS in GitHub Actions, NODE is a subdirectory of HOME so replace NODE first + let output = input + .split(cwd).join('{CWD}') + .split(execPath).join('{NODE}') + .split(home).join('{HOME}') + + // On Windows, also replace variations with escaped backslashes + if (process.platform === 'win32') { + output = output + .split(cwd.replace(/\\/g, '\\\\')).join('{CWD}') + .split(cwd.replace(/\\/g, '\\\\\\\\')).join('{CWD}') + .split(execPath.replace(/\\/g, '\\\\')).join('{NODE}') + .split(execPath.replace(/\\/g, '\\\\\\\\')).join('{NODE}') + .split(home.replace(/\\/g, '\\\\')).join('{HOME}') + .split(home.replace(/\\/g, '\\\\\\\\')).join('{HOME}') + .replace(/\\\\/g, '/') + .replace(/\\(?!")/g, '/') + } + + return output +} + +module.exports = cleanSnapshot diff --git a/test/helpers/script-mode.js b/test/helpers/script-mode.js new file mode 100644 index 00000000..b59f8135 --- /dev/null +++ b/test/helpers/script-mode.js @@ -0,0 +1,11 @@ +const scriptMode = () => { + // On Windows, scripts are r/w but not executable + if (process.platform === 'win32') { + return 0o666 + } else { + // On Unix, scripts are executable + return 0o111 + } +} + +module.exports = scriptMode diff --git a/test/index.js b/test/index.js index d397c21e..ba811abc 100644 --- a/test/index.js +++ b/test/index.js @@ -7,15 +7,14 @@ const GitFetcher = require('../lib/git.js') const RegistryFetcher = require('../lib/registry.js') const RemoteFetcher = require('../lib/remote.js') const pacote = require('../lib/index.js') +const cleanSnapshot = require('./helpers/clean-snapshot.js') const abbrev = resolve(__dirname, 'fixtures/abbrev-1.1.1.tgz') const abbrevspec = `file:${relative(process.cwd(), abbrev)}` const me = t.testdir() -t.cleanSnapshot = str => str - .split(process.cwd()).join('${CWD}') - .replace(/\\/g, '/') +t.cleanSnapshot = str => cleanSnapshot(str) // Putting all these tests inside a `t.test` suite broke the tests. They either // didn't run or failed w/ no message. Ignoring promise/catch-or-return for now. diff --git a/test/util/npm.js b/test/util/npm.js index f6148d34..f6d07f1a 100644 --- a/test/util/npm.js +++ b/test/util/npm.js @@ -2,6 +2,7 @@ const t = require('tap') const cp = require('node:child_process') const EventEmitter = require('node:events') const { Minipass } = require('minipass') +const cleanSnapshot = require('../helpers/clean-snapshot.js') const { spawn } = cp @@ -14,7 +15,7 @@ cp.spawn = (...args) => { } t.teardown = () => cp.spawn = spawn -t.cleanSnapshot = s => s.split(process.execPath).join('{NODE}') +t.cleanSnapshot = str => cleanSnapshot(str) const npm = require('../../lib/util/npm.js') t.test('do the things', t => {