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",
|
||||
"devDependencies": {
|
||||
"asar": "^0.11.0",
|
||||
"electabul": "~0.0.2",
|
||||
"request": "*",
|
||||
"standard": "^7.1.2",
|
||||
"standard-markdown": "^1.1.1"
|
||||
|
@ -24,8 +25,8 @@
|
|||
"scripts": {
|
||||
"bootstrap": "python ./script/bootstrap.py",
|
||||
"build": "python ./script/build.py -c D",
|
||||
"coverage": "npm run instrument-code-coverage && npm run test -- --use-instrumented-asar",
|
||||
"instrument-code-coverage": "node ./spec/coverage/instrument.js",
|
||||
"coverage": "npm run instrument-code-coverage && npm test -- --use-instrumented-asar",
|
||||
"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-js": "standard && cd spec && standard",
|
||||
"lint-cpp": "python ./script/cpplint.py",
|
||||
|
|
|
@ -997,6 +997,7 @@ describe('browser-window module', function () {
|
|||
|
||||
describe('dev tool extensions', function () {
|
||||
describe('BrowserWindow.addDevToolsExtension', function () {
|
||||
let showPanelIntevalId
|
||||
this.timeout(10000)
|
||||
|
||||
beforeEach(function () {
|
||||
|
@ -1008,7 +1009,7 @@ describe('browser-window module', function () {
|
|||
assert.equal(BrowserWindow.getDevToolsExtensions().hasOwnProperty('foo'), true)
|
||||
|
||||
w.webContents.on('devtools-opened', function () {
|
||||
var showPanelIntevalId = setInterval(function () {
|
||||
showPanelIntevalId = setInterval(function () {
|
||||
if (w && w.devToolsWebContents) {
|
||||
var showLastPanel = function () {
|
||||
var lastPanelId = WebInspector.inspectorView._tabbedPane._tabs.peekLast().id
|
||||
|
@ -1024,6 +1025,10 @@ describe('browser-window module', function () {
|
|||
w.loadURL('about:blank')
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
clearInterval(showPanelIntevalId)
|
||||
})
|
||||
|
||||
it('throws errors for missing manifest.json files', function () {
|
||||
assert.throws(function () {
|
||||
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.
|
||||
process.throwDeprecation = true;
|
||||
|
||||
// Check if we are running in CI.
|
||||
var path = require('path');
|
||||
var electron = require ('electron');
|
||||
var remote = electron.remote;
|
||||
var ipcRenderer = electron.ipcRenderer;
|
||||
|
||||
// Check if we are running in CI.
|
||||
var isCi = remote.getGlobal('isCi')
|
||||
|
||||
if (!isCi) {
|
||||
|
@ -47,6 +48,8 @@
|
|||
});
|
||||
}
|
||||
|
||||
var Coverage = require('electabul').Coverage;
|
||||
|
||||
var Mocha = require('mocha');
|
||||
|
||||
var mocha = new Mocha();
|
||||
|
@ -81,9 +84,17 @@
|
|||
walker.on('end', function() {
|
||||
var runner = mocha.run(function() {
|
||||
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)
|
||||
ipcRenderer.send('process.exit', runner.failures);
|
||||
require('../coverage/reporter').generateReport()
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -8,6 +8,7 @@ const dialog = electron.dialog
|
|||
const BrowserWindow = electron.BrowserWindow
|
||||
const protocol = electron.protocol
|
||||
|
||||
const Coverage = require('electabul').Coverage
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
|
@ -63,8 +64,13 @@ ipcMain.on('echo', function (event, msg) {
|
|||
event.returnValue = msg
|
||||
})
|
||||
|
||||
ipcMain.on('get-coverage', function(event) {
|
||||
event.returnValue = global.__coverage__
|
||||
const coverage = new 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
|
||||
|
@ -84,8 +90,6 @@ app.on('window-all-closed', function () {
|
|||
app.quit()
|
||||
})
|
||||
|
||||
require('../coverage/reporter').setupCoverage()
|
||||
|
||||
app.on('ready', function () {
|
||||
// Test if using protocol module would crash.
|
||||
electron.protocol.registerStringProtocol('test-if-crashes', function () {})
|
||||
|
|
Loading…
Reference in a new issue