Use electabul to instrument and report coverage
authorKevin Sawicki <kevinsawicki@gmail.com>
Wed, 3 Aug 2016 21:12:05 +0000 (14:12 -0700)
committerKevin Sawicki <kevinsawicki@gmail.com>
Wed, 3 Aug 2016 23:23:39 +0000 (16:23 -0700)
package.json
spec/api-browser-window-spec.js
spec/coverage/instrument.js [deleted file]
spec/coverage/reporter.js [deleted file]
spec/static/index.html
spec/static/main.js

index 0593301..c02190b 100644 (file)
@@ -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",
index 73a3ffa..91215d1 100644 (file)
@@ -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'))
diff --git a/spec/coverage/instrument.js b/spec/coverage/instrument.js
deleted file mode 100644 (file)
index aad98a8..0000000
+++ /dev/null
@@ -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)
-  }
-})
diff --git a/spec/coverage/reporter.js b/spec/coverage/reporter.js
deleted file mode 100644 (file)
index af28178..0000000
+++ /dev/null
@@ -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()
-}
index 6f77c79..49e3f10 100644 (file)
   // 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();
   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()
     });
   });
 })();
index e949267..26a2faf 100644 (file)
@@ -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 () {})