chore: misc testing fixes (#2930)

* chore: misc test fixes

* Sort test runs by os first

* Use cross-env for test env var

* Try sorting matrix params

* Make FAST_TEST the default and rename to FULL_TEST

* Separate helper functions to not need to export test obj in files
This commit is contained in:
Luke Karrys 2023-10-28 14:13:10 -07:00 committed by GitHub
parent d52997e975
commit 4e493d4fb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 239 additions and 329 deletions

View file

@ -7,7 +7,7 @@ Contributor guide: https://github.com/nodejs/node/blob/main/CONTRIBUTING.md
##### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->
- [ ] `npm install && npm test` passes
- [ ] `npm install && npm run lint && npm test` passes
- [ ] tests are included <!-- Bug fixes and new features should include tests -->
- [ ] documentation is changed or added
- [ ] commit message follows [commit guidelines](https://github.com/googleapis/release-please#how-should-i-write-my-commits)

View file

@ -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' }}

View file

@ -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

View file

@ -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'

View file

@ -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
}

39
lib/download.js Normal file
View file

@ -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
}

View file

@ -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.'

View file

@ -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
}

View file

@ -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
}

View file

@ -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-*"
}
}

View file

@ -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

View file

@ -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

View file

@ -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')
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', function (done) {
// No `fs.copyFileSync` before node8.
if (process.version.substr(1).split('.')[0] < 8) {
return this.skip('skipping test for old node version')
}
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)
})
})

View file

@ -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() } })

View file

@ -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 () {

View file

@ -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')

View file

@ -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))
})
})

View file

@ -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')
describe('parallel', function () {
let prog
async function parallelInstallsTest (test, devDir, prog) {
if (skipParallelInstallTests) {
return test.skip('Skipping parallel installs test due to test environment configuration')
}
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 })
afterEach(async () => {
await rm(prog.devDir, { recursive: true, force: true })
prog = null
})
await Promise.all([
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, [])
])
}
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')
}
it('parallel installs (ensure=true)', async function () {
this.timeout(600000)
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 devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-'))
runIt('ensure=true', async function () {
prog.opts.ensure = true
})
const prog = gyp()
prog.parseArgv([])
prog.devDir = devDir
prog.opts.ensure = true
runIt('ensure=false', async function () {
prog.opts.ensure = false
})
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)
})
it('parallel installs (tarball)', 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.tarball = path.join(devDir, 'node-headers.tar.gz')
await streamPipeline(
(await download(prog, `https://nodejs.org/dist/${process.version}/node-${process.version}.tar.gz`)).body,
createWriteStream(prog.opts.tarball)
)
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))
})
})
})