Use electabul to instrument and report coverage
This commit is contained in:
parent
2c0de93f06
commit
ad07a20d9a
6 changed files with 30 additions and 183 deletions
|
@ -3,6 +3,7 @@
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"asar": "^0.11.0",
|
"asar": "^0.11.0",
|
||||||
|
"electabul": "~0.0.2",
|
||||||
"request": "*",
|
"request": "*",
|
||||||
"standard": "^7.1.2",
|
"standard": "^7.1.2",
|
||||||
"standard-markdown": "^1.1.1"
|
"standard-markdown": "^1.1.1"
|
||||||
|
@ -24,8 +25,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bootstrap": "python ./script/bootstrap.py",
|
"bootstrap": "python ./script/bootstrap.py",
|
||||||
"build": "python ./script/build.py -c D",
|
"build": "python ./script/build.py -c D",
|
||||||
"coverage": "npm run instrument-code-coverage && npm run test -- --use-instrumented-asar",
|
"coverage": "npm run instrument-code-coverage && npm test -- --use-instrumented-asar",
|
||||||
"instrument-code-coverage": "node ./spec/coverage/instrument.js",
|
"instrument-code-coverage": "electabul instrument --input-path ./lib --output-path ./out/coverage/electron.asar",
|
||||||
"lint": "npm run lint-js && npm run lint-cpp && npm run lint-docs",
|
"lint": "npm run lint-js && npm run lint-cpp && npm run lint-docs",
|
||||||
"lint-js": "standard && cd spec && standard",
|
"lint-js": "standard && cd spec && standard",
|
||||||
"lint-cpp": "python ./script/cpplint.py",
|
"lint-cpp": "python ./script/cpplint.py",
|
||||||
|
|
|
@ -997,6 +997,7 @@ describe('browser-window module', function () {
|
||||||
|
|
||||||
describe('dev tool extensions', function () {
|
describe('dev tool extensions', function () {
|
||||||
describe('BrowserWindow.addDevToolsExtension', function () {
|
describe('BrowserWindow.addDevToolsExtension', function () {
|
||||||
|
let showPanelIntevalId
|
||||||
this.timeout(10000)
|
this.timeout(10000)
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
@ -1008,7 +1009,7 @@ describe('browser-window module', function () {
|
||||||
assert.equal(BrowserWindow.getDevToolsExtensions().hasOwnProperty('foo'), true)
|
assert.equal(BrowserWindow.getDevToolsExtensions().hasOwnProperty('foo'), true)
|
||||||
|
|
||||||
w.webContents.on('devtools-opened', function () {
|
w.webContents.on('devtools-opened', function () {
|
||||||
var showPanelIntevalId = setInterval(function () {
|
showPanelIntevalId = setInterval(function () {
|
||||||
if (w && w.devToolsWebContents) {
|
if (w && w.devToolsWebContents) {
|
||||||
var showLastPanel = function () {
|
var showLastPanel = function () {
|
||||||
var lastPanelId = WebInspector.inspectorView._tabbedPane._tabs.peekLast().id
|
var lastPanelId = WebInspector.inspectorView._tabbedPane._tabs.peekLast().id
|
||||||
|
@ -1024,6 +1025,10 @@ describe('browser-window module', function () {
|
||||||
w.loadURL('about:blank')
|
w.loadURL('about:blank')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
clearInterval(showPanelIntevalId)
|
||||||
|
})
|
||||||
|
|
||||||
it('throws errors for missing manifest.json files', function () {
|
it('throws errors for missing manifest.json files', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
BrowserWindow.addDevToolsExtension(path.join(__dirname, 'does-not-exist'))
|
BrowserWindow.addDevToolsExtension(path.join(__dirname, 'does-not-exist'))
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
// Generate an instrumented .asar file for all the files in lib/ and save it
|
|
||||||
// to out/coverage/electron-instrumented.asar
|
|
||||||
|
|
||||||
var asar = require('asar')
|
|
||||||
var fs = require('fs')
|
|
||||||
var glob = require('glob')
|
|
||||||
var Instrumenter = require('istanbul').Instrumenter
|
|
||||||
var mkdirp = require('mkdirp')
|
|
||||||
var path = require('path')
|
|
||||||
var rimraf = require('rimraf')
|
|
||||||
|
|
||||||
var instrumenter = new Instrumenter()
|
|
||||||
var outputPath = path.join(__dirname, '..', '..', 'out', 'coverage')
|
|
||||||
var libPath = path.join(__dirname, '..', '..', 'lib')
|
|
||||||
|
|
||||||
rimraf.sync(path.join(outputPath, 'lib'))
|
|
||||||
|
|
||||||
glob.sync('**/*.js', {cwd: libPath}).forEach(function (relativePath) {
|
|
||||||
var rawPath = path.join(libPath, relativePath)
|
|
||||||
var raw = fs.readFileSync(rawPath, 'utf8')
|
|
||||||
|
|
||||||
var generatedPath = path.join(outputPath, 'lib', relativePath)
|
|
||||||
var generated = instrumenter.instrumentSync(raw, rawPath)
|
|
||||||
mkdirp.sync(path.dirname(generatedPath))
|
|
||||||
fs.writeFileSync(generatedPath, generated)
|
|
||||||
})
|
|
||||||
|
|
||||||
var asarPath = path.join(outputPath, 'electron.asar')
|
|
||||||
asar.createPackageWithOptions(path.join(outputPath, 'lib'), asarPath, {}, function (error) {
|
|
||||||
if (error) {
|
|
||||||
console.error(error.stack || error)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,140 +0,0 @@
|
||||||
const asar = require('asar')
|
|
||||||
const fs = require('fs')
|
|
||||||
const glob = require('glob')
|
|
||||||
const mkdirp = require('mkdirp')
|
|
||||||
const path = require('path')
|
|
||||||
const rimraf = require('rimraf')
|
|
||||||
const {Collector, Instrumenter, Reporter} = require('istanbul')
|
|
||||||
|
|
||||||
const outputPath = path.join(__dirname, '..', '..', 'out', 'coverage')
|
|
||||||
const libPath = path.join(__dirname, '..', '..', 'lib')
|
|
||||||
|
|
||||||
// Add unrequired files to the coverage report so all files are present there
|
|
||||||
const addUnrequiredFiles = (coverage) => {
|
|
||||||
const instrumenter = new Instrumenter()
|
|
||||||
const libPath = path.join(__dirname, '..', '..', 'lib')
|
|
||||||
|
|
||||||
glob.sync('**/*.js', {cwd: libPath}).map(function (relativePath) {
|
|
||||||
return path.join(libPath, relativePath)
|
|
||||||
}).filter(function (filePath) {
|
|
||||||
return coverage[filePath] == null
|
|
||||||
}).forEach(function (filePath) {
|
|
||||||
instrumenter.instrumentSync(fs.readFileSync(filePath, 'utf8'), filePath)
|
|
||||||
|
|
||||||
// When instrumenting the code, istanbul will give each FunctionDeclaration
|
|
||||||
// a value of 1 in coverState.s,presumably to compensate for function
|
|
||||||
// hoisting. We need to reset this, as the function was not hoisted, as it
|
|
||||||
// was never loaded.
|
|
||||||
Object.keys(instrumenter.coverState.s).forEach(function (key) {
|
|
||||||
instrumenter.coverState.s[key] = 0
|
|
||||||
});
|
|
||||||
|
|
||||||
coverage[filePath] = instrumenter.coverState
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add coverage data to collector for all opened browser windows
|
|
||||||
const addBrowserWindowsData = (collector) => {
|
|
||||||
const dataPath = path.join(outputPath, 'data')
|
|
||||||
glob.sync('*.json', {cwd: dataPath}).map(function (relativePath) {
|
|
||||||
return path.join(dataPath, relativePath)
|
|
||||||
}).forEach(function (filePath) {
|
|
||||||
collector.add(JSON.parse(fs.readFileSync(filePath)));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a code coverage report in out/coverage/lcov-report
|
|
||||||
exports.generateReport = () => {
|
|
||||||
const coverage = window.__coverage__
|
|
||||||
if (coverage == null) return
|
|
||||||
|
|
||||||
addUnrequiredFiles(coverage)
|
|
||||||
|
|
||||||
const collector = new Collector()
|
|
||||||
collector.add(coverage)
|
|
||||||
|
|
||||||
const {ipcRenderer} = require('electron')
|
|
||||||
collector.add(ipcRenderer.sendSync('get-coverage'))
|
|
||||||
|
|
||||||
addBrowserWindowsData(collector)
|
|
||||||
|
|
||||||
const reporter = new Reporter(null, outputPath)
|
|
||||||
reporter.addAll(['text', 'lcov'])
|
|
||||||
reporter.write(collector, true, function () {})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save coverage data from the browser window with the given pid
|
|
||||||
const saveCoverageData = (webContents, coverage, pid) => {
|
|
||||||
if (coverage && pid) {
|
|
||||||
const dataPath = path.join(outputPath, 'data', `${pid || webContents.getId()}-${webContents.getType()}-${Date.now()}.json`)
|
|
||||||
mkdirp.sync(path.dirname(dataPath))
|
|
||||||
fs.writeFileSync(dataPath, JSON.stringify(coverage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCoverageFromWebContents = (webContents, callback) => {
|
|
||||||
webContents.executeJavaScript('[window.__coverage__, window.process && window.process.pid]', (results) => {
|
|
||||||
const coverage = results[0]
|
|
||||||
const pid = results[1]
|
|
||||||
callback(coverage, pid)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveWebContentsCoverage = (webContents, callback) => {
|
|
||||||
getCoverageFromWebContents(webContents, (coverage, pid) => {
|
|
||||||
saveCoverageData(webContents, coverage, pid)
|
|
||||||
callback()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save coverage data when a BrowserWindow is closed manually
|
|
||||||
const patchBrowserWindow = () => {
|
|
||||||
const {BrowserWindow} = require('electron')
|
|
||||||
|
|
||||||
const {destroy} = BrowserWindow.prototype
|
|
||||||
BrowserWindow.prototype.destroy = function () {
|
|
||||||
if (this.isDestroyed() || !this.getURL()) {
|
|
||||||
return destroy.call(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveWebContentsCoverage(this.webContents, () => {
|
|
||||||
if (this.devToolsWebContents) {
|
|
||||||
saveWebContentsCoverage(this.devToolsWebContents, () => {
|
|
||||||
destroy.call(this)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
destroy.call(this)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save coverage data when beforeunload fires on the webContent's window object
|
|
||||||
const saveCoverageOnBeforeUnload = () => {
|
|
||||||
const {app, ipcMain} = require('electron')
|
|
||||||
|
|
||||||
ipcMain.on('save-coverage', function (event, coverage, pid) {
|
|
||||||
saveCoverageData(event.sender, coverage, pid)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('report-coverage', function (event, message) {
|
|
||||||
saveCoverageData(event.sender, message.coverage, `${message.pid}-extension`)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('web-contents-created', function (event, webContents) {
|
|
||||||
webContents.executeJavaScript(`
|
|
||||||
window.addEventListener('beforeunload', function () {
|
|
||||||
require('electron').ipcRenderer.send('save-coverage', window.__coverage__, window.process && window.process.pid)
|
|
||||||
})
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.setupCoverage = () => {
|
|
||||||
const coverage = global.__coverage__
|
|
||||||
if (coverage == null) return
|
|
||||||
|
|
||||||
rimraf.sync(path.join(outputPath, 'data'))
|
|
||||||
patchBrowserWindow()
|
|
||||||
saveCoverageOnBeforeUnload()
|
|
||||||
}
|
|
|
@ -13,11 +13,12 @@
|
||||||
// Disable use of deprecated functions.
|
// Disable use of deprecated functions.
|
||||||
process.throwDeprecation = true;
|
process.throwDeprecation = true;
|
||||||
|
|
||||||
// Check if we are running in CI.
|
var path = require('path');
|
||||||
var electron = require ('electron');
|
var electron = require ('electron');
|
||||||
var remote = electron.remote;
|
var remote = electron.remote;
|
||||||
var ipcRenderer = electron.ipcRenderer;
|
var ipcRenderer = electron.ipcRenderer;
|
||||||
|
|
||||||
|
// Check if we are running in CI.
|
||||||
var isCi = remote.getGlobal('isCi')
|
var isCi = remote.getGlobal('isCi')
|
||||||
|
|
||||||
if (!isCi) {
|
if (!isCi) {
|
||||||
|
@ -47,6 +48,8 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var Coverage = require('electabul').Coverage;
|
||||||
|
|
||||||
var Mocha = require('mocha');
|
var Mocha = require('mocha');
|
||||||
|
|
||||||
var mocha = new Mocha();
|
var mocha = new Mocha();
|
||||||
|
@ -81,9 +84,17 @@
|
||||||
walker.on('end', function() {
|
walker.on('end', function() {
|
||||||
var runner = mocha.run(function() {
|
var runner = mocha.run(function() {
|
||||||
Mocha.utils.highlightTags('code');
|
Mocha.utils.highlightTags('code');
|
||||||
|
|
||||||
|
var coverage = new Coverage({
|
||||||
|
libPath: path.join(__dirname, '..', '..', 'lib'),
|
||||||
|
outputPath: path.join(__dirname, '..', '..', 'out', 'coverage'),
|
||||||
|
formats: ['text', 'lcov']
|
||||||
|
});
|
||||||
|
coverage.addCoverage(ipcRenderer.sendSync('get-main-process-coverage'))
|
||||||
|
coverage.generateReport()
|
||||||
|
|
||||||
if (isCi)
|
if (isCi)
|
||||||
ipcRenderer.send('process.exit', runner.failures);
|
ipcRenderer.send('process.exit', runner.failures);
|
||||||
require('../coverage/reporter').generateReport()
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -8,6 +8,7 @@ const dialog = electron.dialog
|
||||||
const BrowserWindow = electron.BrowserWindow
|
const BrowserWindow = electron.BrowserWindow
|
||||||
const protocol = electron.protocol
|
const protocol = electron.protocol
|
||||||
|
|
||||||
|
const Coverage = require('electabul').Coverage
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const url = require('url')
|
const url = require('url')
|
||||||
|
@ -63,8 +64,13 @@ ipcMain.on('echo', function (event, msg) {
|
||||||
event.returnValue = msg
|
event.returnValue = msg
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('get-coverage', function(event) {
|
const coverage = new Coverage({
|
||||||
event.returnValue = global.__coverage__
|
outputPath: path.join(__dirname, '..', '..', 'out', 'coverage')
|
||||||
|
})
|
||||||
|
coverage.setup()
|
||||||
|
|
||||||
|
ipcMain.on('get-main-process-coverage', function (event) {
|
||||||
|
event.returnValue = global.__coverage__ || null
|
||||||
})
|
})
|
||||||
|
|
||||||
global.isCi = !!argv.ci
|
global.isCi = !!argv.ci
|
||||||
|
@ -84,8 +90,6 @@ app.on('window-all-closed', function () {
|
||||||
app.quit()
|
app.quit()
|
||||||
})
|
})
|
||||||
|
|
||||||
require('../coverage/reporter').setupCoverage()
|
|
||||||
|
|
||||||
app.on('ready', function () {
|
app.on('ready', function () {
|
||||||
// Test if using protocol module would crash.
|
// Test if using protocol module would crash.
|
||||||
electron.protocol.registerStringProtocol('test-if-crashes', function () {})
|
electron.protocol.registerStringProtocol('test-if-crashes', function () {})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue