Skip to content

Commit

Permalink
Migration from tap to mocha (#2851)
Browse files Browse the repository at this point in the history
* migrate from tap to mocha

After make-fetch-happen update GitHub Actions started failing. Migrating
from tap to mocha testing framework for GitHub Action stability.

* write custom test reporter for more verbose output

Implemented a simple custom mocha test reporter to replace the default
one. Made test report more developer friendly.
  • Loading branch information
StefanStojanovic authored Jun 5, 2023
1 parent aaa117c commit 5df2b72
Show file tree
Hide file tree
Showing 13 changed files with 1,773 additions and 1,835 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@
},
"devDependencies": {
"bindings": "^1.5.0",
"mocha": "^10.2.0",
"nan": "^2.14.2",
"require-inject": "^1.4.4",
"standard": "^14.3.4",
"tap": "^12.7.0"
"standard": "^14.3.4"
},
"scripts": {
"lint": "standard */*.js test/**/*.js",
"test": "npm run lint && tap --timeout=1200 test/test-*"
"test": "npm run lint && mocha --reporter=test/reporter.js test/test-download.js test/test-*"
}
}
75 changes: 75 additions & 0 deletions test/reporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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
200 changes: 101 additions & 99 deletions test/test-addon.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const test = require('tap').test
const { describe, it } = require('mocha')
const assert = require('assert')
const path = require('path')
const fs = require('graceful-fs')
const childProcess = require('child_process')
Expand Down Expand Up @@ -35,116 +36,117 @@ function checkCharmapValid () {
return lines.pop() === 'True'
}

test('build simple addon', function (t) {
t.plan(3)

// Set the loglevel otherwise the output disappears when run via 'npm test'
var cmd = [nodeGyp, 'rebuild', '-C', addonPath, '--loglevel=verbose']
var proc = execFile(process.execPath, cmd, function (err, stdout, stderr) {
var logLines = stderr.toString().trim().split(/\r?\n/)
var lastLine = logLines[logLines.length - 1]
t.strictEqual(err, null)
t.strictEqual(lastLine, 'gyp info ok', 'should end in ok')
t.strictEqual(runHello().trim(), 'world')
describe('addon', function () {
this.timeout(300000)

it('build simple addon', function (done) {
// Set the loglevel otherwise the output disappears when run via 'npm test'
var cmd = [nodeGyp, 'rebuild', '-C', addonPath, '--loglevel=verbose']
var proc = execFile(process.execPath, cmd, function (err, stdout, stderr) {
var logLines = stderr.toString().trim().split(/\r?\n/)
var 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')
})
proc.stdout.setEncoding('utf-8')
proc.stderr.setEncoding('utf-8')
})

test('build simple addon in path with non-ascii characters', function (t) {
t.plan(1)

if (!checkCharmapValid()) {
return t.skip('python console app can\'t encode non-ascii character.')
}
it('build simple addon in path with non-ascii characters', function (done) {
if (!checkCharmapValid()) {
return this.skip('python console app can\'t encode non-ascii character.')
}

var testDirNames = {
cp936: '文件夹',
cp1252: 'Latīna',
cp932: 'フォルダ'
}
// Select non-ascii characters by current encoding
var testDirName = testDirNames[getEncoding()]
// If encoding is UTF-8 or other then no need to test
if (!testDirName) {
return t.skip('no need to test')
}
var testDirNames = {
cp936: '文件夹',
cp1252: 'Latīna',
cp932: 'フォルダ'
}
// Select non-ascii characters by current encoding
var testDirName = testDirNames[getEncoding()]
// If encoding is UTF-8 or other then no need to test
if (!testDirName) {
return this.skip('no need to test')
}

t.plan(3)
this.timeout(300000)

var data
var configPath = path.join(addonPath, 'build', 'config.gypi')
try {
data = fs.readFileSync(configPath, 'utf8')
} catch (err) {
t.error(err)
return
}
var config = JSON.parse(data.replace(/#.+\n/, ''))
var nodeDir = config.variables.nodedir
var testNodeDir = path.join(addonPath, testDirName)
// Create symbol link to path with non-ascii characters
try {
fs.symlinkSync(nodeDir, testNodeDir, 'dir')
} catch (err) {
switch (err.code) {
case 'EEXIST': break
case 'EPERM':
t.error(err, 'Please try to running console as an administrator')
return
default:
t.error(err)
return
var data
var configPath = path.join(addonPath, 'build', 'config.gypi')
try {
data = fs.readFileSync(configPath, 'utf8')
} catch (err) {
assert.fail(err)
return
}
}

var cmd = [
nodeGyp,
'rebuild',
'-C',
addonPath,
'--loglevel=verbose',
'-nodedir=' + testNodeDir
]
var proc = execFile(process.execPath, cmd, function (err, stdout, stderr) {
var config = JSON.parse(data.replace(/#.+\n/, ''))
var nodeDir = config.variables.nodedir
var testNodeDir = path.join(addonPath, testDirName)
// Create symbol link to path with non-ascii characters
try {
fs.unlink(testNodeDir)
fs.symlinkSync(nodeDir, testNodeDir, 'dir')
} catch (err) {
t.error(err)
switch (err.code) {
case 'EEXIST': break
case 'EPERM':
assert.fail(err, null, 'Please try to running console as an administrator')
return
default:
assert.fail(err)
return
}
}

var logLines = stderr.toString().trim().split(/\r?\n/)
var lastLine = logLines[logLines.length - 1]
t.strictEqual(err, null)
t.strictEqual(lastLine, 'gyp info ok', 'should end in ok')
t.strictEqual(runHello().trim(), 'world')
var cmd = [
nodeGyp,
'rebuild',
'-C',
addonPath,
'--loglevel=verbose',
'-nodedir=' + testNodeDir
]
var proc = execFile(process.execPath, cmd, function (err, stdout, stderr) {
try {
fs.unlink(testNodeDir)
} catch (err) {
assert.fail(err)
}

var logLines = stderr.toString().trim().split(/\r?\n/)
var 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')
})
proc.stdout.setEncoding('utf-8')
proc.stderr.setEncoding('utf-8')
})

test('addon works with renamed host executable', function (t) {
// No `fs.copyFileSync` before node8.
if (process.version.substr(1).split('.')[0] < 8) {
t.skip('skipping test for old node version')
t.end()
return
}

t.plan(3)

var notNodePath = path.join(os.tmpdir(), 'notnode' + path.extname(process.execPath))
fs.copyFileSync(process.execPath, notNodePath)
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')
}

var cmd = [nodeGyp, 'rebuild', '-C', addonPath, '--loglevel=verbose']
var proc = execFile(process.execPath, cmd, function (err, stdout, stderr) {
var logLines = stderr.toString().trim().split(/\r?\n/)
var lastLine = logLines[logLines.length - 1]
t.strictEqual(err, null)
t.strictEqual(lastLine, 'gyp info ok', 'should end in ok')
t.strictEqual(runHello(notNodePath).trim(), 'world')
fs.unlinkSync(notNodePath)
this.timeout(300000)

var notNodePath = path.join(os.tmpdir(), 'notnode' + path.extname(process.execPath))
fs.copyFileSync(process.execPath, notNodePath)

var cmd = [nodeGyp, 'rebuild', '-C', addonPath, '--loglevel=verbose']
var proc = execFile(process.execPath, cmd, function (err, stdout, stderr) {
var logLines = stderr.toString().trim().split(/\r?\n/)
var 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')
})
proc.stdout.setEncoding('utf-8')
proc.stderr.setEncoding('utf-8')
})
Loading

0 comments on commit 5df2b72

Please sign in to comment.