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

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