diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 779897573a..da362b8ccf 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,7 +7,7 @@ Contributor guide: https://github.com/nodejs/node/blob/main/CONTRIBUTING.md ##### Checklist -- [ ] `npm install && npm test` passes +- [ ] `npm install && npm run lint && npm test` passes - [ ] tests are included - [ ] documentation is changed or added - [ ] commit message follows [commit guidelines](https://github.com/googleapis/release-please#how-should-i-write-my-commits) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 29525b8a73..4aa041df9e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,11 +18,24 @@ jobs: - uses: actions/checkout@v4 - run: pip install --user ruff - run: ruff --output-format=github --select="E,F,PLC,PLE,UP,W,YTT" --ignore="E721,PLC1901,S101,UP031" --target-version=py38 . + Lint_JS: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Use Node.js 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x + - name: Install Dependencies + run: npm install --no-progress + - name: Lint + run: npm run lint Engines: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Use Node.js 20.x uses: actions/setup-node@v3 with: @@ -41,10 +54,11 @@ jobs: fail-fast: false max-parallel: 15 matrix: - node: [16.x, 18.x, 20.x] + os: [macos, ubuntu, windows] python: ["3.8", "3.10", "3.12"] - os: [macos-latest, ubuntu-latest, windows-latest] - runs-on: ${{ matrix.os }} + node: [16.x, 18.x, 20.x] + name: ${{ matrix.os }} - ${{ matrix.python }} - ${{ matrix.node }} + runs-on: ${{ matrix.os }}-latest steps: - name: Checkout Repository uses: actions/checkout@v4 @@ -63,7 +77,7 @@ jobs: npm install --no-progress pip install pytest - name: Set Windows environment - if: startsWith(matrix.os, 'windows') + if: matrix.os == 'windows' run: | echo 'GYP_MSVS_VERSION=2015' >> $Env:GITHUB_ENV echo 'GYP_MSVS_OVERRIDE_PATH=C:\\Dummy' >> $Env:GITHUB_ENV @@ -77,7 +91,11 @@ jobs: if: runner.os != 'Windows' shell: bash run: npm test --python="${pythonLocation}/python" + env: + FULL_TEST: ${{ (matrix.node == '20.x' && matrix.python == '3.12') && '1' || '0' }} - name: Run tests (Windows) if: runner.os == 'Windows' shell: pwsh run: npm run test --python="${env:pythonLocation}\\python.exe" + env: + FULL_TEST: ${{ (matrix.node == '20.x' && matrix.python == '3.12') && '1' || '0' }} diff --git a/bin/node-gyp.js b/bin/node-gyp.js index a6f3e73a50..f8317b47b3 100755 --- a/bin/node-gyp.js +++ b/bin/node-gyp.js @@ -32,9 +32,9 @@ if (prog.devDir) { if (prog.todo.length === 0) { if (~process.argv.indexOf('-v') || ~process.argv.indexOf('--version')) { - console.log('v%s', prog.version) + log.stdout('v%s', prog.version) } else { - console.log('%s', prog.usage()) + log.stdout('%s', prog.usage()) } process.exit(0) } @@ -82,12 +82,12 @@ async function run () { if (command.name === 'list') { if (args.length) { - args.forEach((version) => console.log(version)) + args.forEach((version) => log.stdout(version)) } else { - console.log('No node development files installed. Use `node-gyp install` to install a version.') + log.stdout('No node development files installed. Use `node-gyp install` to install a version.') } } else if (args.length >= 1) { - console.log(...args.slice(1)) + log.stdout(...args.slice(1)) } // now run the next command in the queue diff --git a/lib/configure.js b/lib/configure.js index 8d59e33497..38fd2b9718 100644 --- a/lib/configure.js +++ b/lib/configure.js @@ -1,14 +1,14 @@ 'use strict' -const { openSync, closeSync, promises: fs } = require('graceful-fs') +const { promises: fs } = require('graceful-fs') const path = require('path') const log = require('./log') const os = require('os') const processRelease = require('./process-release') const win = process.platform === 'win32' const findNodeDirectory = require('./find-node-directory') -const createConfigGypi = require('./create-config-gypi') -const { format: msgFormat } = require('util') +const { createConfigGypi } = require('./create-config-gypi') +const { format: msgFormat, findAccessibleSync } = require('util') const { findPython } = require('./find-python') const { findVisualStudio } = win ? require('./find-visualstudio') : {} @@ -277,32 +277,5 @@ async function configure (gyp, argv) { } } -/** - * Returns the first file or directory from an array of candidates that is - * readable by the current user, or undefined if none of the candidates are - * readable. - */ -function findAccessibleSync (logprefix, dir, candidates) { - for (let next = 0; next < candidates.length; next++) { - const candidate = path.resolve(dir, candidates[next]) - let fd - try { - fd = openSync(candidate, 'r') - } catch (e) { - // this candidate was not found or not readable, do nothing - log.silly(logprefix, 'Could not open %s: %s', candidate, e.message) - continue - } - closeSync(fd) - log.silly(logprefix, 'Found readable %s', candidate) - return candidate - } - - return undefined -} - module.exports = configure -module.exports.test = { - findAccessibleSync -} module.exports.usage = 'Generates ' + (win ? 'MSVC project files' : 'a Makefile') + ' for the current module' diff --git a/lib/create-config-gypi.js b/lib/create-config-gypi.js index a9c427e51d..d598dea6e2 100644 --- a/lib/create-config-gypi.js +++ b/lib/create-config-gypi.js @@ -143,8 +143,8 @@ async function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo, python }) { return configPath } -module.exports = createConfigGypi -module.exports.test = { +module.exports = { + createConfigGypi, parseConfigGypi, getCurrentConfigGypi } diff --git a/lib/download.js b/lib/download.js new file mode 100644 index 0000000000..ed0aa37f44 --- /dev/null +++ b/lib/download.js @@ -0,0 +1,39 @@ +const fetch = require('make-fetch-happen') +const { promises: fs } = require('graceful-fs') +const log = require('./log') + +async function download (gyp, url) { + log.http('GET', url) + + const requestOpts = { + headers: { + 'User-Agent': `node-gyp v${gyp.version} (node ${process.version})`, + Connection: 'keep-alive' + }, + proxy: gyp.opts.proxy, + noProxy: gyp.opts.noproxy + } + + const cafile = gyp.opts.cafile + if (cafile) { + requestOpts.ca = await readCAFile(cafile) + } + + const res = await fetch(url, requestOpts) + log.http(res.status, res.url) + + return res +} + +async function readCAFile (filename) { + // The CA file can contain multiple certificates so split on certificate + // boundaries. [\S\s]*? is used to match everything including newlines. + const ca = await fs.readFile(filename, 'utf8') + const re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g + return ca.match(re) +} + +module.exports = { + download, + readCAFile +} diff --git a/lib/install.js b/lib/install.js index b338102c95..7196a31629 100644 --- a/lib/install.js +++ b/lib/install.js @@ -9,12 +9,13 @@ const { Transform, promises: { pipeline } } = require('stream') const crypto = require('crypto') const log = require('./log') const semver = require('semver') -const fetch = require('make-fetch-happen') +const { download } = require('./download') const processRelease = require('./process-release') + const win = process.platform === 'win32' async function install (gyp, argv) { - console.log() + log.stdout() const release = processRelease(argv, gyp, process.version, process.release) // Detecting target_arch based on logic from create-cnfig-gyp.js. Used on Windows only. const arch = win ? (gyp.opts.target_arch || gyp.opts.arch || process.arch || 'ia32') : '' @@ -410,41 +411,5 @@ class ShaSum extends Transform { } } -async function download (gyp, url) { - log.http('GET', url) - - const requestOpts = { - headers: { - 'User-Agent': `node-gyp v${gyp.version} (node ${process.version})`, - Connection: 'keep-alive' - }, - proxy: gyp.opts.proxy, - noProxy: gyp.opts.noproxy - } - - const cafile = gyp.opts.cafile - if (cafile) { - requestOpts.ca = await readCAFile(cafile) - } - - const res = await fetch(url, requestOpts) - log.http(res.status, res.url) - - return res -} - -async function readCAFile (filename) { - // The CA file can contain multiple certificates so split on certificate - // boundaries. [\S\s]*? is used to match everything including newlines. - const ca = await fs.readFile(filename, 'utf8') - const re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g - return ca.match(re) -} - module.exports = install -module.exports.test = { - download, - install, - readCAFile -} module.exports.usage = 'Install node development files for the specified node version.' diff --git a/lib/log.js b/lib/log.js index 47fa8a3372..6841719aba 100644 --- a/lib/log.js +++ b/lib/log.js @@ -73,11 +73,11 @@ class Logger { style: { fg: 'red', bg: 'black' } }] - constructor () { + constructor (stream) { process.on('log', (...args) => this.#onLog(...args)) this.#levels = new Map(this.#levels.map((level, index) => [level.id, { ...level, index }])) this.level = 'info' - this.stream = process.stderr + this.stream = stream procLog.pause() } @@ -158,8 +158,12 @@ class Logger { } } +// used to suppress logs in tests +const NULL_LOGGER = !!process.env.NODE_GYP_NULL_LOGGER + module.exports = { - logger: new Logger(), + logger: new Logger(NULL_LOGGER ? null : process.stderr), + stdout: NULL_LOGGER ? () => {} : (...args) => console.log(...args), withPrefix, ...procLog } diff --git a/lib/util.js b/lib/util.js index ef2bfb0827..3f6aeeb7dc 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,8 +1,9 @@ 'use strict' -const log = require('./log') const cp = require('child_process') const path = require('path') +const { openSync, closeSync } = require('graceful-fs') +const log = require('./log') const execFile = async (...args) => new Promise((resolve) => { const child = cp.execFile(...args, (...a) => resolve(a)) @@ -48,8 +49,33 @@ async function regSearchKeys (keys, value, addOpts) { } } +/** + * Returns the first file or directory from an array of candidates that is + * readable by the current user, or undefined if none of the candidates are + * readable. + */ +function findAccessibleSync (logprefix, dir, candidates) { + for (let next = 0; next < candidates.length; next++) { + const candidate = path.resolve(dir, candidates[next]) + let fd + try { + fd = openSync(candidate, 'r') + } catch (e) { + // this candidate was not found or not readable, do nothing + log.silly(logprefix, 'Could not open %s: %s', candidate, e.message) + continue + } + closeSync(fd) + log.silly(logprefix, 'Found readable %s', candidate) + return candidate + } + + return undefined +} + module.exports = { execFile, regGetValue, - regSearchKeys + regSearchKeys, + findAccessibleSync } diff --git a/package.json b/package.json index 409e228001..dadbf1d25a 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ }, "devDependencies": { "bindings": "^1.5.0", + "cross-env": "^7.0.3", "mocha": "^10.2.0", "nan": "^2.14.2", "require-inject": "^1.4.4", @@ -45,6 +46,6 @@ }, "scripts": { "lint": "standard \"*/*.js\" \"test/**/*.js\" \".github/**/*.js\"", - "test": "npm run lint && mocha --timeout 15000 --reporter=test/reporter.js test/test-download.js test/test-*" + "test": "cross-env NODE_GYP_NULL_LOGGER=true mocha --timeout 15000 test/test-download.js test/test-*" } } diff --git a/test/common.js b/test/common.js index f7e2e2a34c..aef6bacfe1 100644 --- a/test/common.js +++ b/test/common.js @@ -1,6 +1,7 @@ const envPaths = require('env-paths') +const semver = require('semver') -module.exports.devDir = () => envPaths('node-gyp', { suffix: '' }).cache +module.exports.devDir = envPaths('node-gyp', { suffix: '' }).cache module.exports.poison = (object, property) => { function fail () { @@ -15,3 +16,9 @@ module.exports.poison = (object, property) => { } Object.defineProperty(object, property, descriptor) } + +// Only run full test suite when instructed and on a non-prerelease version of node +module.exports.FULL_TEST = + process.env.FULL_TEST === '1' && + process.release.name === 'node' && + semver.prerelease(process.version) === null diff --git a/test/reporter.js b/test/reporter.js deleted file mode 100644 index 9964b1b5d5..0000000000 --- a/test/reporter.js +++ /dev/null @@ -1,75 +0,0 @@ -const Mocha = require('mocha') - -class Reporter { - constructor (runner) { - this.failedTests = [] - - runner.on(Mocha.Runner.constants.EVENT_RUN_BEGIN, () => { - console.log('Starting tests') - }) - - runner.on(Mocha.Runner.constants.EVENT_RUN_END, () => { - console.log('Tests finished') - console.log() - console.log('****************') - console.log('* TESTS REPORT *') - console.log('****************') - console.log() - console.log(`Executed ${runner.stats.suites} suites with ${runner.stats.tests} tests in ${runner.stats.duration} ms`) - console.log(` Passed: ${runner.stats.passes}`) - console.log(` Skipped: ${runner.stats.pending}`) - console.log(` Failed: ${runner.stats.failures}`) - if (this.failedTests.length > 0) { - console.log() - console.log(' Failed test details') - this.failedTests.forEach((failedTest, index) => { - console.log() - console.log(` ${index + 1}.'${failedTest.test.fullTitle()}'`) - console.log(` Name: ${failedTest.error.name}`) - console.log(` Message: ${failedTest.error.message}`) - console.log(` Code: ${failedTest.error.code}`) - console.log(` Stack: ${failedTest.error.stack}`) - }) - } - console.log() - }) - - runner.on(Mocha.Runner.constants.EVENT_SUITE_BEGIN, (suite) => { - if (suite.root) { - return - } - console.log(`Starting suite '${suite.title}'`) - }) - - runner.on(Mocha.Runner.constants.EVENT_SUITE_END, (suite) => { - if (suite.root) { - return - } - console.log(`Suite '${suite.title}' finished`) - console.log() - }) - - runner.on(Mocha.Runner.constants.EVENT_TEST_BEGIN, (test) => { - console.log(`Starting test '${test.title}'`) - }) - - runner.on(Mocha.Runner.constants.EVENT_TEST_PASS, (test) => { - console.log(`Test '${test.title}' passed in ${test.duration} ms`) - }) - - runner.on(Mocha.Runner.constants.EVENT_TEST_PENDING, (test) => { - console.log(`Test '${test.title}' skipped in ${test.duration} ms`) - }) - - runner.on(Mocha.Runner.constants.EVENT_TEST_FAIL, (test, error) => { - this.failedTests.push({ test, error }) - console.log(`Test '${test.title}' failed in ${test.duration} ms with ${error}`) - }) - - runner.on(Mocha.Runner.constants.EVENT_TEST_END, (test) => { - console.log() - }) - } -} - -module.exports = Reporter diff --git a/test/test-addon.js b/test/test-addon.js index 0a0b9d56c5..b8d90b65bb 100644 --- a/test/test-addon.js +++ b/test/test-addon.js @@ -4,66 +4,66 @@ const { describe, it } = require('mocha') const assert = require('assert') const path = require('path') const fs = require('graceful-fs') -const { execFileSync, execFile } = require('child_process') const os = require('os') +const cp = require('child_process') +const util = require('../lib/util') + const addonPath = path.resolve(__dirname, 'node_modules', 'hello_world') const nodeGyp = path.resolve(__dirname, '..', 'bin', 'node-gyp.js') -function runHello (hostProcess) { - if (!hostProcess) { - hostProcess = process.execPath - } +const execFileSync = (...args) => cp.execFileSync(...args).toString().trim() + +const execFile = async (cmd) => { + const [err,, stderr] = await util.execFile(process.execPath, cmd, { + env: { ...process.env, NODE_GYP_NULL_LOGGER: undefined }, + encoding: 'utf-8' + }) + return [err, stderr.toString().trim().split(/\r?\n/)] +} + +function runHello (hostProcess = process.execPath) { const testCode = "console.log(require('hello_world').hello())" - return execFileSync(hostProcess, ['-e', testCode], { cwd: __dirname }).toString() + return execFileSync(hostProcess, ['-e', testCode], { cwd: __dirname }) } function getEncoding () { const code = 'import locale;print(locale.getdefaultlocale()[1])' - return execFileSync('python', ['-c', code]).toString().trim() + return execFileSync('python', ['-c', code]) } function checkCharmapValid () { - let data try { - data = execFileSync('python', ['fixtures/test-charmap.py'], - { cwd: __dirname }) - } catch (err) { + const data = execFileSync('python', ['fixtures/test-charmap.py'], { cwd: __dirname }) + return data.split('\n').pop() === 'True' + } catch { return false } - const lines = data.toString().trim().split('\n') - return lines.pop() === 'True' } describe('addon', function () { this.timeout(300000) - it('build simple addon', function (done) { + it('build simple addon', async function () { // Set the loglevel otherwise the output disappears when run via 'npm test' const cmd = [nodeGyp, 'rebuild', '-C', addonPath, '--loglevel=verbose'] - const proc = execFile(process.execPath, cmd, function (err, stdout, stderr) { - const logLines = stderr.toString().trim().split(/\r?\n/) - const lastLine = logLines[logLines.length - 1] - assert.strictEqual(err, null) - assert.strictEqual(lastLine, 'gyp info ok', 'should end in ok') - assert.strictEqual(runHello().trim(), 'world') - done() - }) - proc.stdout.setEncoding('utf-8') - proc.stderr.setEncoding('utf-8') + const [err, logLines] = await execFile(cmd) + const lastLine = logLines[logLines.length - 1] + assert.strictEqual(err, null) + assert.strictEqual(lastLine, 'gyp info ok', 'should end in ok') + assert.strictEqual(runHello(), 'world') }) - it('build simple addon in path with non-ascii characters', function (done) { + it('build simple addon in path with non-ascii characters', async function () { if (!checkCharmapValid()) { return this.skip('python console app can\'t encode non-ascii character.') } - const testDirNames = { + // Select non-ascii characters by current encoding + const testDirName = { cp936: '文件夹', cp1252: 'Latīna', cp932: 'フォルダ' - } - // Select non-ascii characters by current encoding - const testDirName = testDirNames[getEncoding()] + }[getEncoding()] // If encoding is UTF-8 or other then no need to test if (!testDirName) { return this.skip('no need to test') @@ -105,46 +105,30 @@ describe('addon', function () { '--loglevel=verbose', '-nodedir=' + testNodeDir ] - const proc = execFile(process.execPath, cmd, function (err, stdout, stderr) { - try { - fs.unlink(testNodeDir) - } catch (err) { - assert.fail(err) - } - - const logLines = stderr.toString().trim().split(/\r?\n/) - const lastLine = logLines[logLines.length - 1] - assert.strictEqual(err, null) - assert.strictEqual(lastLine, 'gyp info ok', 'should end in ok') - assert.strictEqual(runHello().trim(), 'world') - done() - }) - proc.stdout.setEncoding('utf-8') - proc.stderr.setEncoding('utf-8') - }) - - it('addon works with renamed host executable', function (done) { - // No `fs.copyFileSync` before node8. - if (process.version.substr(1).split('.')[0] < 8) { - return this.skip('skipping test for old node version') + const [err, logLines] = await execFile(cmd) + try { + fs.unlink(testNodeDir) + } catch (err) { + assert.fail(err) } + const lastLine = logLines[logLines.length - 1] + assert.strictEqual(err, null) + assert.strictEqual(lastLine, 'gyp info ok', 'should end in ok') + assert.strictEqual(runHello(), 'world') + }) + it('addon works with renamed host executable', async function () { this.timeout(300000) const notNodePath = path.join(os.tmpdir(), 'notnode' + path.extname(process.execPath)) fs.copyFileSync(process.execPath, notNodePath) const cmd = [nodeGyp, 'rebuild', '-C', addonPath, '--loglevel=verbose'] - const proc = execFile(process.execPath, cmd, function (err, stdout, stderr) { - const logLines = stderr.toString().trim().split(/\r?\n/) - const lastLine = logLines[logLines.length - 1] - assert.strictEqual(err, null) - assert.strictEqual(lastLine, 'gyp info ok', 'should end in ok') - assert.strictEqual(runHello(notNodePath).trim(), 'world') - fs.unlinkSync(notNodePath) - done() - }) - proc.stdout.setEncoding('utf-8') - proc.stderr.setEncoding('utf-8') + const [err, logLines] = await execFile(cmd) + const lastLine = logLines[logLines.length - 1] + assert.strictEqual(err, null) + assert.strictEqual(lastLine, 'gyp info ok', 'should end in ok') + assert.strictEqual(runHello(notNodePath), 'world') + fs.unlinkSync(notNodePath) }) }) diff --git a/test/test-configure-python.js b/test/test-configure-python.js index fd8e8d24ef..eee230a496 100644 --- a/test/test-configure-python.js +++ b/test/test-configure-python.js @@ -3,10 +3,10 @@ const { describe, it } = require('mocha') const assert = require('assert') const path = require('path') -const devDir = require('./common').devDir() +const { devDir } = require('./common') const gyp = require('../lib/node-gyp') -const log = require('../lib/log') const requireInject = require('require-inject') + const configure = requireInject('../lib/configure', { 'graceful-fs': { openSync: () => 0, @@ -19,8 +19,6 @@ const configure = requireInject('../lib/configure', { } }) -log.logger.stream = null - const EXPECTED_PYPATH = path.join(__dirname, '..', 'gyp', 'pylib') const SEPARATOR = process.platform === 'win32' ? ';' : ':' const SPAWN_RESULT = cb => ({ on: function () { cb() } }) diff --git a/test/test-create-config-gypi.js b/test/test-create-config-gypi.js index 725819b6aa..3c77b87859 100644 --- a/test/test-create-config-gypi.js +++ b/test/test-create-config-gypi.js @@ -4,8 +4,7 @@ const path = require('path') const { describe, it } = require('mocha') const assert = require('assert') const gyp = require('../lib/node-gyp') -const createConfigGypi = require('../lib/create-config-gypi') -const { parseConfigGypi, getCurrentConfigGypi } = createConfigGypi.test +const { parseConfigGypi, getCurrentConfigGypi } = require('../lib/create-config-gypi') describe('create-config-gypi', function () { it('config.gypi with no options', async function () { diff --git a/test/test-download.js b/test/test-download.js index 5b65641f5d..e8e9df4a76 100644 --- a/test/test-download.js +++ b/test/test-download.js @@ -7,14 +7,11 @@ const path = require('path') const http = require('http') const https = require('https') const install = require('../lib/install') -const semver = require('semver') -const devDir = require('./common').devDir() +const { download, readCAFile } = require('../lib/download') +const { FULL_TEST, devDir } = require('./common') const gyp = require('../lib/node-gyp') -const log = require('../lib/log') const certs = require('./fixtures/certs') -log.logger.stream = null - describe('download', function () { it('download over http', async function () { const server = http.createServer((req, res) => { @@ -32,7 +29,7 @@ describe('download', function () { version: '42' } const url = `http://${host}:${port}` - const res = await install.test.download(gyp, url) + const res = await download(gyp, url) assert.strictEqual(await res.text(), 'ok') }) @@ -42,7 +39,7 @@ describe('download', function () { const cert = certs['server.crt'] const key = certs['server.key'] await fs.writeFile(cafile, cacontents, 'utf8') - const ca = await install.test.readCAFile(cafile) + const ca = await readCAFile(cafile) assert.strictEqual(ca.length, 1) @@ -67,7 +64,7 @@ describe('download', function () { version: '42' } const url = `https://${host}:${port}` - const res = await install.test.download(gyp, url) + const res = await download(gyp, url) assert.strictEqual(await res.text(), 'ok') }) @@ -98,7 +95,7 @@ describe('download', function () { version: '42' } const url = `http://${host}:${port}` - const res = await install.test.download(gyp, url) + const res = await download(gyp, url) assert.strictEqual(await res.text(), 'proxy ok') }) @@ -129,7 +126,7 @@ describe('download', function () { version: '42' } const url = `http://${host}:${port}` - const res = await install.test.download(gyp, url) + const res = await download(gyp, url) assert.strictEqual(await res.text(), 'ok') }) @@ -138,7 +135,7 @@ describe('download', function () { opts: { cafile: 'no.such.file' } } try { - await install.test.download(gyp, {}, 'http://bad/') + await download(gyp, {}, 'http://bad/') } catch (e) { assert.ok(/no.such.file/.test(e.message)) } @@ -151,7 +148,7 @@ describe('download', function () { after(async () => { await fs.unlink(cafile) }) - const cas = await install.test.readCAFile(path.join(__dirname, 'fixtures/ca-bundle.crt')) + const cas = await readCAFile(path.join(__dirname, 'fixtures/ca-bundle.crt')) assert.strictEqual(cas.length, 2) assert.notStrictEqual(cas[0], cas[1]) }) @@ -159,10 +156,7 @@ describe('download', function () { // only run this test if we are running a version of Node with predictable version path behavior it('download headers (actual)', async function () { - if (process.env.FAST_TEST || - process.release.name !== 'node' || - semver.prerelease(process.version) !== null || - semver.satisfies(process.version, '<10')) { + if (!FULL_TEST) { return this.skip('Skipping actual download of headers due to test environment configuration') } @@ -174,7 +168,6 @@ describe('download', function () { const prog = gyp() prog.parseArgv([]) prog.devDir = devDir - log.level = 'warn' await install(prog, []) const data = await fs.readFile(path.join(expectedDir, 'installVersion'), 'utf8') diff --git a/test/test-find-accessible-sync.js b/test/test-find-accessible-sync.js index 9ec12dbb33..0c8a5ddf07 100644 --- a/test/test-find-accessible-sync.js +++ b/test/test-find-accessible-sync.js @@ -4,7 +4,7 @@ const { describe, it } = require('mocha') const assert = require('assert') const path = require('path') const requireInject = require('require-inject') -const configure = requireInject('../lib/configure', { +const { findAccessibleSync } = requireInject('../lib/util', { 'graceful-fs': { closeSync: function () { return undefined }, openSync: function (path) { @@ -31,43 +31,43 @@ const readableFiles = [ describe('find-accessible-sync', function () { it('find accessible - empty array', function () { const candidates = [] - const found = configure.test.findAccessibleSync('test', dir, candidates) + const found = findAccessibleSync('test', dir, candidates) assert.strictEqual(found, undefined) }) it('find accessible - single item array, readable', function () { const candidates = [readableFile] - const found = configure.test.findAccessibleSync('test', dir, candidates) + const found = findAccessibleSync('test', dir, candidates) assert.strictEqual(found, path.resolve(dir, readableFile)) }) it('find accessible - single item array, readable in subdir', function () { const candidates = [readableFileInDir] - const found = configure.test.findAccessibleSync('test', dir, candidates) + const found = findAccessibleSync('test', dir, candidates) assert.strictEqual(found, path.resolve(dir, readableFileInDir)) }) it('find accessible - single item array, unreadable', function () { const candidates = ['unreadable_file'] - const found = configure.test.findAccessibleSync('test', dir, candidates) + const found = findAccessibleSync('test', dir, candidates) assert.strictEqual(found, undefined) }) it('find accessible - multi item array, no matches', function () { const candidates = ['non_existent_file', 'unreadable_file'] - const found = configure.test.findAccessibleSync('test', dir, candidates) + const found = findAccessibleSync('test', dir, candidates) assert.strictEqual(found, undefined) }) it('find accessible - multi item array, single match', function () { const candidates = ['non_existent_file', readableFile] - const found = configure.test.findAccessibleSync('test', dir, candidates) + const found = findAccessibleSync('test', dir, candidates) assert.strictEqual(found, path.resolve(dir, readableFile)) }) it('find accessible - multi item array, return first match', function () { const candidates = ['non_existent_file', anotherReadableFile, readableFile] - const found = configure.test.findAccessibleSync('test', dir, candidates) + const found = findAccessibleSync('test', dir, candidates) assert.strictEqual(found, path.resolve(dir, anotherReadableFile)) }) }) diff --git a/test/test-install.js b/test/test-install.js index 3fc39b7c2f..d6694270bb 100644 --- a/test/test-install.js +++ b/test/test-install.js @@ -1,23 +1,22 @@ 'use strict' -const { describe, it, after } = require('mocha') +const { describe, it, afterEach, beforeEach } = require('mocha') const { rm, mkdtemp } = require('fs/promises') const { createWriteStream } = require('fs') const assert = require('assert') const path = require('path') const os = require('os') -const semver = require('semver') const { pipeline: streamPipeline } = require('stream/promises') const requireInject = require('require-inject') +const { FULL_TEST } = require('./common') const gyp = require('../lib/node-gyp') - -const createInstall = (mocks = {}) => requireInject('../lib/install', mocks).test -const { download, install } = createInstall() +const install = require('../lib/install') +const { download } = require('../lib/download') describe('install', function () { it('EACCES retry once', async () => { let statCalled = 0 - const mockInstall = createInstall({ + const mockInstall = requireInject('../lib/install', { 'graceful-fs': { promises: { stat (_) { @@ -35,7 +34,7 @@ describe('install', function () { ensure: true }, commands: { - install: (...args) => mockInstall.install(Gyp, ...args), + install: (...args) => mockInstall(Gyp, ...args), remove: async () => {} } } @@ -54,79 +53,58 @@ describe('install', function () { } }) - // only run these tests if we are running a version of Node with predictable version path behavior - const skipParallelInstallTests = process.env.FAST_TEST || - process.release.name !== 'node' || - semver.prerelease(process.version) !== null || - semver.satisfies(process.version, '<10') - - async function parallelInstallsTest (test, devDir, prog) { - if (skipParallelInstallTests) { - return test.skip('Skipping parallel installs test due to test environment configuration') - } + describe('parallel', function () { + let prog - after(async () => { - await rm(devDir, { recursive: true, force: true }) + beforeEach(async () => { + prog = gyp() + prog.parseArgv([]) + prog.devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-')) }) - const expectedDir = path.join(devDir, process.version.replace(/^v/, '')) - await rm(expectedDir, { recursive: true, force: true }) - - await Promise.all([ - install(prog, []), - install(prog, []), - install(prog, []), - install(prog, []), - install(prog, []), - install(prog, []), - install(prog, []), - install(prog, []), - install(prog, []), - install(prog, []) - ]) - } - - it('parallel installs (ensure=true)', async function () { - this.timeout(600000) - - const devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-')) - - const prog = gyp() - prog.parseArgv([]) - prog.devDir = devDir - prog.opts.ensure = true - - await parallelInstallsTest(this, devDir, prog) - }) - - it('parallel installs (ensure=false)', async function () { - this.timeout(600000) - - const devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-')) - - const prog = gyp() - prog.parseArgv([]) - prog.devDir = devDir - prog.opts.ensure = false - - await parallelInstallsTest(this, devDir, prog) - }) + afterEach(async () => { + await rm(prog.devDir, { recursive: true, force: true }) + prog = null + }) - it('parallel installs (tarball)', async function () { - this.timeout(600000) + const runIt = (name, fn) => { + // only run these tests if we are running a version of Node with predictable version path behavior + if (!FULL_TEST) { + return it.skip('Skipping parallel installs test due to test environment configuration') + } - const devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-')) + return it(name, async function () { + this.timeout(600000) + await fn.call(this) + const expectedDir = path.join(prog.devDir, process.version.replace(/^v/, '')) + await rm(expectedDir, { recursive: true, force: true }) + await Promise.all([ + install(prog, []), + install(prog, []), + install(prog, []), + install(prog, []), + install(prog, []), + install(prog, []), + install(prog, []), + install(prog, []), + install(prog, []), + install(prog, []) + ]) + }) + } - const prog = gyp() - prog.parseArgv([]) - prog.devDir = devDir - prog.opts.tarball = path.join(devDir, 'node-headers.tar.gz') + runIt('ensure=true', async function () { + prog.opts.ensure = true + }) - await streamPipeline( - (await download(prog, `https://nodejs.org/dist/${process.version}/node-${process.version}.tar.gz`)).body, - createWriteStream(prog.opts.tarball) - ) + runIt('ensure=false', async function () { + prog.opts.ensure = false + }) - await parallelInstallsTest(this, devDir, prog) + runIt('tarball', async function () { + prog.opts.tarball = path.join(prog.devDir, 'node-headers.tar.gz') + const dl = await download(prog, `https://nodejs.org/dist/${process.version}/node-${process.version}.tar.gz`) + await streamPipeline(dl.body, createWriteStream(prog.opts.tarball)) + }) }) })