mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-26 00:50:41 -05:00
Merge branch 'develop' into v4.0-release
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
exports['lib/util/process_profiler ._aggregateGroups aggregates groups as expected 1'] = [
|
||||
{
|
||||
"group": "electron-shared",
|
||||
"processCount": 4,
|
||||
"pids": "99991, 99990, 99992, 99993",
|
||||
"cpuPercent": 80,
|
||||
"memRssMb": 40,
|
||||
"meanCpuPercent": 80,
|
||||
"meanMemRssMb": 40,
|
||||
"maxMemRssMb": 40
|
||||
},
|
||||
{
|
||||
"group": "other",
|
||||
"processCount": 2,
|
||||
"pids": "66666, 88888",
|
||||
"cpuPercent": 40,
|
||||
"memRssMb": 20,
|
||||
"meanCpuPercent": 40,
|
||||
"meanMemRssMb": 20,
|
||||
"maxMemRssMb": 20
|
||||
},
|
||||
{
|
||||
"group": "plugin",
|
||||
"processCount": 2,
|
||||
"pids": "22222, 22223",
|
||||
"cpuPercent": 40,
|
||||
"memRssMb": 20,
|
||||
"meanCpuPercent": 40,
|
||||
"meanMemRssMb": 20,
|
||||
"maxMemRssMb": 20
|
||||
},
|
||||
{
|
||||
"group": "browser",
|
||||
"processCount": 2,
|
||||
"pids": "11111, 11112",
|
||||
"cpuPercent": 40,
|
||||
"memRssMb": 20,
|
||||
"meanCpuPercent": 40,
|
||||
"meanMemRssMb": 20,
|
||||
"maxMemRssMb": 20
|
||||
},
|
||||
{
|
||||
"group": "ffmpeg",
|
||||
"processCount": 1,
|
||||
"pids": "33333",
|
||||
"cpuPercent": 20,
|
||||
"memRssMb": 10,
|
||||
"meanCpuPercent": 20,
|
||||
"meanMemRssMb": 10,
|
||||
"maxMemRssMb": 10
|
||||
},
|
||||
{
|
||||
"group": "desktop-gui",
|
||||
"processCount": 1,
|
||||
"pids": "77777",
|
||||
"cpuPercent": 20,
|
||||
"memRssMb": 10,
|
||||
"meanCpuPercent": 20,
|
||||
"meanMemRssMb": 10,
|
||||
"maxMemRssMb": 10
|
||||
},
|
||||
{
|
||||
"group": "cypress",
|
||||
"processCount": 1,
|
||||
"pids": "111111111",
|
||||
"cpuPercent": 20,
|
||||
"memRssMb": 10,
|
||||
"meanCpuPercent": 20,
|
||||
"meanMemRssMb": 10,
|
||||
"maxMemRssMb": 10
|
||||
},
|
||||
{
|
||||
"group": "TOTAL",
|
||||
"processCount": 13,
|
||||
"pids": "-",
|
||||
"cpuPercent": 260,
|
||||
"memRssMb": 130,
|
||||
"meanCpuPercent": 260,
|
||||
"meanMemRssMb": 130,
|
||||
"maxMemRssMb": 130
|
||||
}
|
||||
]
|
||||
@@ -1,13 +1,19 @@
|
||||
// override tty if we're being forced to
|
||||
require('./lib/util/tty').override()
|
||||
|
||||
if (process.env.CY_NET_PROFILE && process.env.CYPRESS_ENV) {
|
||||
const electronApp = require('./lib/util/electron-app')
|
||||
|
||||
// are we in the main node process or the electron process?
|
||||
const isRunningElectron = electronApp.isRunning()
|
||||
|
||||
if (process.env.CY_NET_PROFILE && isRunningElectron) {
|
||||
const netProfiler = require('./lib/util/net_profiler')()
|
||||
|
||||
process.stdout.write(`Network profiler writing to ${netProfiler.logPath}\n`)
|
||||
}
|
||||
|
||||
process.env.UV_THREADPOOL_SIZE = 128
|
||||
|
||||
require('graceful-fs').gracefulify(require('fs'))
|
||||
// if running in production mode (CYPRESS_ENV)
|
||||
// all transpile should have been done already
|
||||
@@ -15,6 +21,10 @@ require('graceful-fs').gracefulify(require('fs'))
|
||||
require('@packages/ts/register')
|
||||
require('@packages/coffee/register')
|
||||
|
||||
if (isRunningElectron) {
|
||||
require('./lib/util/process_profiler').start()
|
||||
}
|
||||
|
||||
require && require.extensions && delete require.extensions['.litcoffee']
|
||||
require && require.extensions && delete require.extensions['.coffee.md']
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ ELECTRON_DEBUG_EVENTS = [
|
||||
'unresponsive'
|
||||
]
|
||||
|
||||
instance = null
|
||||
|
||||
tryToCall = (win, method) ->
|
||||
try
|
||||
if not win.isDestroyed()
|
||||
@@ -72,6 +74,10 @@ module.exports = {
|
||||
_win.on "close", ->
|
||||
if not child.isDestroyed()
|
||||
child.destroy()
|
||||
|
||||
## add this pid to list of pids
|
||||
tryToCall child, ->
|
||||
instance?.pid?.push(child.webContents.getOSProcessId())
|
||||
}
|
||||
|
||||
_.defaultsDeep({}, options, defaults)
|
||||
@@ -253,9 +259,12 @@ module.exports = {
|
||||
|
||||
events.emit("exit")
|
||||
|
||||
return _.extend events, {
|
||||
instance = _.extend events, {
|
||||
pid: [tryToCall(win, -> win.webContents.getOSProcessId())]
|
||||
browserWindow: win
|
||||
kill: -> tryToCall(win, "destroy")
|
||||
removeAllListeners: -> tryToCall(win, "removeAllListeners")
|
||||
}
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
@@ -157,6 +157,17 @@ module.exports = {
|
||||
|
||||
close: kill,
|
||||
|
||||
_setInstance (_instance) {
|
||||
// for testing
|
||||
instance = _instance
|
||||
},
|
||||
|
||||
// note: does not guarantee that `browser` is still running
|
||||
// note: electron will return a list of pids for each webContent
|
||||
getBrowserInstance () {
|
||||
return instance
|
||||
},
|
||||
|
||||
getAllBrowsersWith (nameOrPath) {
|
||||
debug('getAllBrowsersWith %o', { nameOrPath })
|
||||
if (nameOrPath) {
|
||||
@@ -192,6 +203,8 @@ module.exports = {
|
||||
// TODO: bind to process.exit here
|
||||
// or move this functionality into cypress-core-launder
|
||||
|
||||
i.browser = browser
|
||||
|
||||
instance = i
|
||||
|
||||
// TODO: normalizing opening and closing / exiting
|
||||
|
||||
@@ -45,7 +45,7 @@ const exitErr = (err) => {
|
||||
|
||||
module.exports = {
|
||||
isCurrentlyRunningElectron () {
|
||||
return !!(process.versions && process.versions.electron)
|
||||
return require('./util/electron-app').isRunning()
|
||||
},
|
||||
|
||||
runElectron (mode, options) {
|
||||
@@ -128,7 +128,7 @@ module.exports = {
|
||||
// to force retina screens to not
|
||||
// upsample their images when offscreen
|
||||
// rendering
|
||||
require('./util/electron_app').scale()
|
||||
require('./util/electron-app').scale()
|
||||
}
|
||||
|
||||
// make sure we have the appData folder
|
||||
|
||||
@@ -26,7 +26,7 @@ const newlines = require('../util/newlines')
|
||||
const terminal = require('../util/terminal')
|
||||
const specsUtil = require('../util/specs')
|
||||
const humanTime = require('../util/human_time')
|
||||
const electronApp = require('../util/electron_app')
|
||||
const electronApp = require('../util/electron-app')
|
||||
const settings = require('../util/settings')
|
||||
const chromePolicyCheck = require('../util/chrome_policy_check')
|
||||
|
||||
@@ -660,6 +660,7 @@ const getVideoRecordingDelay = function (startedVideoCapture) {
|
||||
const maybeStartVideoRecording = Promise.method(function (options = {}) {
|
||||
const { spec, browser, video, videosFolder } = options
|
||||
|
||||
debug(`video recording has been ${video ? 'enabled' : 'disabled'}. video: %s`, video)
|
||||
// bail if we've been told not to capture
|
||||
// a video recording
|
||||
if (!video) {
|
||||
@@ -1403,7 +1404,7 @@ module.exports = {
|
||||
|
||||
run (options) {
|
||||
return electronApp
|
||||
.ready()
|
||||
.waitForReady()
|
||||
.then(() => {
|
||||
return this.ready(options)
|
||||
})
|
||||
|
||||
@@ -22,6 +22,14 @@ register = (event, callback) ->
|
||||
registeredEvents[event] = callback
|
||||
|
||||
module.exports = {
|
||||
## for testing
|
||||
_setPluginsProcess: (_pluginsProcess) ->
|
||||
pluginsProcess = _pluginsProcess
|
||||
|
||||
getPluginPid: () ->
|
||||
if pluginsProcess
|
||||
return pluginsProcess.pid
|
||||
|
||||
registerHandler: (handler) ->
|
||||
handlers.push(handler)
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ class Project extends EE {
|
||||
this.spec = null
|
||||
this.browser = null
|
||||
this.server = null
|
||||
this.memoryCheck = null
|
||||
this.automation = null
|
||||
this.getConfig = this.getConfig.bind(this)
|
||||
|
||||
@@ -78,15 +77,6 @@ class Project extends EE {
|
||||
debug('project options %o', options)
|
||||
this.options = options
|
||||
|
||||
if (process.env.CYPRESS_MEMORY) {
|
||||
const logMemory = () => {
|
||||
// eslint-disable-next-line no-console
|
||||
return console.log('memory info', process.memoryUsage())
|
||||
}
|
||||
|
||||
this.memoryCheck = setInterval(logMemory, 1000)
|
||||
}
|
||||
|
||||
this.onWarning = options.onWarning
|
||||
|
||||
return this.getConfig(options)
|
||||
@@ -205,10 +195,6 @@ class Project extends EE {
|
||||
close () {
|
||||
debug('closing project instance %s', this.projectRoot)
|
||||
|
||||
if (this.memoryCheck) {
|
||||
clearInterval(this.memoryCheck)
|
||||
}
|
||||
|
||||
this.cfg = null
|
||||
this.spec = null
|
||||
this.browser = null
|
||||
|
||||
+13
-6
@@ -1,5 +1,3 @@
|
||||
const debug = require('debug')('cypress:server:electron_app')
|
||||
|
||||
const scale = () => {
|
||||
try {
|
||||
const { app } = require('electron')
|
||||
@@ -10,7 +8,9 @@ const scale = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const ready = () => {
|
||||
const waitForReady = () => {
|
||||
const debug = require('debug')('cypress:server:electron-app')
|
||||
|
||||
const Promise = require('bluebird')
|
||||
const { app } = require('electron')
|
||||
|
||||
@@ -21,20 +21,27 @@ const ready = () => {
|
||||
debug('all BrowserWindows closed, not exiting')
|
||||
})
|
||||
|
||||
const waitForReady = () => {
|
||||
const onReadyEvent = () => {
|
||||
return new Promise((resolve) => {
|
||||
app.on('ready', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.any([
|
||||
waitForReady(),
|
||||
onReadyEvent(),
|
||||
Promise.delay(500),
|
||||
])
|
||||
}
|
||||
|
||||
const isRunning = () => {
|
||||
// are we in the electron or the node process?
|
||||
return Boolean(process.versions && process.versions.electron)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
scale,
|
||||
|
||||
ready,
|
||||
waitForReady,
|
||||
|
||||
isRunning,
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
import Debug from 'debug'
|
||||
import la from 'lazy-ass'
|
||||
import _ from 'lodash'
|
||||
import si from 'systeminformation'
|
||||
import { concatStream } from '@packages/network'
|
||||
|
||||
const browsers = require('../browsers')
|
||||
const plugins = require('../plugins')
|
||||
|
||||
type Group = 'browser' | 'cypress' | 'plugin' | 'desktop-gui' | 'ffmpeg' | 'electron-shared' | 'other'
|
||||
type Process = si.Systeminformation.ProcessesProcessData & {
|
||||
group?: Group
|
||||
}
|
||||
|
||||
const debug = Debug('cypress:server:util:process_profiler')
|
||||
const debugVerbose = Debug('cypress-verbose:server:util:process_profiler')
|
||||
|
||||
const interval = Number(process.env.CYPRESS_PROCESS_PROFILER_INTERVAL) || 10000
|
||||
let started = false
|
||||
|
||||
let groupsOverTime = {}
|
||||
|
||||
export const _reset = () => {
|
||||
groupsOverTime = {}
|
||||
}
|
||||
|
||||
const formatPidDisplay = (groupedProcesses) => {
|
||||
const pids = _.map(groupedProcesses, 'pid')
|
||||
const maxArrayLength = 6
|
||||
|
||||
let display = pids.slice(0, maxArrayLength).join(', ')
|
||||
|
||||
if (pids.length > maxArrayLength) {
|
||||
display += ` ... ${pids.length - maxArrayLength} more items`
|
||||
}
|
||||
|
||||
return display
|
||||
}
|
||||
|
||||
export const _groupCyProcesses = ({ list }: si.Systeminformation.ProcessesData) => {
|
||||
const cyProcesses: Process[] = []
|
||||
const thisProcess: Process = _.find(list, { pid: process.pid })!
|
||||
|
||||
la(thisProcess, 'expected to find current pid in process list', process.pid)
|
||||
|
||||
const isParentProcessInGroup = (proc: Process, group: Group) => {
|
||||
return _.chain(cyProcesses).filter({ group }).map('pid').includes(proc.parentPid).value()
|
||||
}
|
||||
|
||||
// is this a browser process launched to run Cypress tests?
|
||||
const isBrowserProcess = (proc: Process): boolean => {
|
||||
const instance = browsers.getBrowserInstance()
|
||||
// electron will return a list of pids, since it's not a hierarchy
|
||||
const pid: number | number[] = instance && instance.pid
|
||||
|
||||
return (Array.isArray(pid) ? (pid as number[]).includes(proc.pid) : proc.pid === pid)
|
||||
|| isParentProcessInGroup(proc, 'browser')
|
||||
}
|
||||
|
||||
const isPluginProcess = (proc: Process): boolean => {
|
||||
return proc.pid === plugins.getPluginPid()
|
||||
|| isParentProcessInGroup(proc, 'plugin')
|
||||
}
|
||||
|
||||
// is this the renderer for the desktop-gui?
|
||||
const isDesktopGuiProcess = (proc: Process): boolean => {
|
||||
return proc.params.includes('--type=renderer')
|
||||
&& !isBrowserProcess(proc)
|
||||
}
|
||||
|
||||
// these processes may be shared between the AUT and desktop-gui
|
||||
// rather than treat them as part of the `browser` in `run` mode and have
|
||||
// their usage in `open` mode be ambiguous, just put them in their own group
|
||||
const isElectronSharedProcess = (proc: Process): boolean => {
|
||||
const isType = (type) => {
|
||||
return proc.params.includes(`--type=${type}`)
|
||||
}
|
||||
|
||||
return isType('broker')
|
||||
|| isType('gpu-process')
|
||||
|| isType('utility')
|
||||
|| isType('zygote')
|
||||
}
|
||||
|
||||
const isFfmpegProcess = (proc: Process): boolean => {
|
||||
return proc.parentPid === thisProcess.pid
|
||||
&& /ffmpeg/i.test(proc.name)
|
||||
}
|
||||
|
||||
const getProcessGroup = (proc: Process): Group => {
|
||||
if (proc === thisProcess) {
|
||||
return 'cypress'
|
||||
}
|
||||
|
||||
if (isBrowserProcess(proc)) {
|
||||
return 'browser'
|
||||
}
|
||||
|
||||
if (isPluginProcess(proc)) {
|
||||
return 'plugin'
|
||||
}
|
||||
|
||||
if (isDesktopGuiProcess(proc)) {
|
||||
return 'desktop-gui'
|
||||
}
|
||||
|
||||
if (isFfmpegProcess(proc)) {
|
||||
return 'ffmpeg'
|
||||
}
|
||||
|
||||
if (isElectronSharedProcess(proc)) {
|
||||
return 'electron-shared'
|
||||
}
|
||||
|
||||
return 'other'
|
||||
}
|
||||
|
||||
const classifyProcess = (proc: Process) => {
|
||||
const classify = (group: Group) => {
|
||||
proc.group = group
|
||||
cyProcesses.push(proc)
|
||||
|
||||
// queue all children
|
||||
_.chain(list)
|
||||
.filter({ parentPid: proc.pid })
|
||||
.map(classifyProcess)
|
||||
.value()
|
||||
}
|
||||
|
||||
classify(getProcessGroup(proc))
|
||||
}
|
||||
|
||||
classifyProcess(thisProcess)
|
||||
|
||||
return cyProcesses
|
||||
}
|
||||
|
||||
export const _renameBrowserGroup = (processes: Process[]) => {
|
||||
const instance = browsers.getBrowserInstance()
|
||||
const displayName = _.get(instance, 'browser.displayName')
|
||||
|
||||
processes.forEach((proc) => {
|
||||
if (!displayName) {
|
||||
return
|
||||
}
|
||||
|
||||
if (proc.group === 'browser') {
|
||||
proc.group = displayName
|
||||
}
|
||||
})
|
||||
|
||||
return processes
|
||||
}
|
||||
|
||||
export const _aggregateGroups = (processes: Process[]) => {
|
||||
debugVerbose('all Cypress-launched processes: %s', require('util').inspect(processes))
|
||||
|
||||
const groupTotals = _.chain(processes)
|
||||
.groupBy('group')
|
||||
.mapValues((groupedProcesses, group) => {
|
||||
return {
|
||||
group,
|
||||
processCount: groupedProcesses.length,
|
||||
pids: formatPidDisplay(groupedProcesses),
|
||||
cpuPercent: _.sumBy(groupedProcesses, 'pcpu'),
|
||||
memRssMb: _.sumBy(groupedProcesses, 'mem_rss') / 1024,
|
||||
}
|
||||
})
|
||||
.values()
|
||||
.sortBy('memRssMb')
|
||||
.reverse()
|
||||
.value()
|
||||
|
||||
groupTotals.push(_.reduce(groupTotals, (acc, val) => {
|
||||
acc.processCount += val.processCount
|
||||
acc.cpuPercent += val.cpuPercent
|
||||
acc.memRssMb += val.memRssMb
|
||||
|
||||
return acc
|
||||
}, { group: 'TOTAL', processCount: 0, pids: '-', cpuPercent: 0, memRssMb: 0 }))
|
||||
|
||||
groupTotals.forEach((total) => {
|
||||
if (!groupsOverTime[total.group]) {
|
||||
groupsOverTime[total.group] = []
|
||||
}
|
||||
|
||||
const measurements = groupsOverTime[total.group]
|
||||
|
||||
measurements.push(total)
|
||||
|
||||
_.merge(total, {
|
||||
meanCpuPercent: _.meanBy(measurements, 'cpuPercent'),
|
||||
meanMemRssMb: _.meanBy(measurements, 'memRssMb'),
|
||||
maxMemRssMb: _.max(_.map(measurements, _.property('memRssMb'))),
|
||||
})
|
||||
|
||||
_.forEach(total, (v, k) => {
|
||||
// round all numbers to 100ths precision
|
||||
if (_.isNumber(v)) {
|
||||
total[k] = _.round(v, 2)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return groupTotals
|
||||
}
|
||||
|
||||
export const _printGroupedProcesses = (groupTotals) => {
|
||||
const consoleBuffer = concatStream((buf) => {
|
||||
// get rid of trailing newline
|
||||
debug(String(buf).trim())
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
const buffedConsole = new console.Console(consoleBuffer)
|
||||
|
||||
buffedConsole.log('current & mean memory and CPU usage by process group:')
|
||||
buffedConsole.table(groupTotals, [
|
||||
'group',
|
||||
'processCount',
|
||||
'pids',
|
||||
'cpuPercent',
|
||||
'meanCpuPercent',
|
||||
'memRssMb',
|
||||
'meanMemRssMb',
|
||||
'maxMemRssMb',
|
||||
])
|
||||
|
||||
consoleBuffer.end()
|
||||
}
|
||||
|
||||
function _checkProcesses () {
|
||||
return si.processes()
|
||||
.then(_groupCyProcesses)
|
||||
.then(_renameBrowserGroup)
|
||||
.then(_aggregateGroups)
|
||||
.then(_printGroupedProcesses)
|
||||
.then(_scheduleProcessCheck)
|
||||
.catch((err) => {
|
||||
debug('error running process profiler: %o', err)
|
||||
})
|
||||
}
|
||||
|
||||
function _scheduleProcessCheck () {
|
||||
// not setinterval, since checkProcesses is asynchronous
|
||||
setTimeout(_checkProcesses, interval)
|
||||
}
|
||||
|
||||
export function start () {
|
||||
if (!debug.enabled && !debugVerbose.enabled) {
|
||||
debug('process profiler not enabled')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (started) {
|
||||
return
|
||||
}
|
||||
|
||||
_checkProcesses()
|
||||
|
||||
started = true
|
||||
}
|
||||
@@ -120,6 +120,7 @@
|
||||
"squirrelly": "7.7.0",
|
||||
"strip-ansi": "3.0.1",
|
||||
"syntax-error": "1.4.0",
|
||||
"systeminformation": "4.19.1",
|
||||
"term-size": "2.1.0",
|
||||
"tough-cookie": "3.0.1",
|
||||
"trash": "5.2.0",
|
||||
|
||||
@@ -44,6 +44,7 @@ const env = require(`${root}lib/util/env`)
|
||||
const v = require(`${root}lib/util/validation`)
|
||||
const system = require(`${root}lib/util/system`)
|
||||
const appData = require(`${root}lib/util/app_data`)
|
||||
const electronApp = require('../../lib/util/electron-app')
|
||||
const { formStatePath } = require(`${root}lib/util/saved_state`)
|
||||
|
||||
const TYPICAL_BROWSERS = [
|
||||
@@ -121,7 +122,7 @@ describe('lib/cypress', () => {
|
||||
// spawning a separate process
|
||||
sinon.stub(videoCapture, 'start').resolves({})
|
||||
sinon.stub(plugins, 'init').resolves(undefined)
|
||||
sinon.stub(cypress, 'isCurrentlyRunningElectron').returns(true)
|
||||
sinon.stub(electronApp, 'isRunning').returns(true)
|
||||
sinon.stub(extension, 'setHostAndPath').resolves()
|
||||
sinon.stub(launcher, 'detect').resolves(TYPICAL_BROWSERS)
|
||||
sinon.stub(process, 'exit')
|
||||
|
||||
@@ -6,6 +6,16 @@ browsers = require("#{root}../lib/browsers")
|
||||
utils = require("#{root}../lib/browsers/utils")
|
||||
|
||||
describe "lib/browsers/index", ->
|
||||
context ".getBrowserInstance", ->
|
||||
it "returns instance", ->
|
||||
instance = { pid: 1234 }
|
||||
browsers._setInstance(instance)
|
||||
expect(browsers.getBrowserInstance()).to.eq(instance)
|
||||
|
||||
it "returns undefined if no instance", ->
|
||||
browsers._setInstance()
|
||||
expect(browsers.getBrowserInstance()).to.be.undefined
|
||||
|
||||
context ".isBrowserFamily", ->
|
||||
it "allows only known browsers", ->
|
||||
expect(browsers.isBrowserFamily("chromium")).to.be.true
|
||||
|
||||
@@ -12,6 +12,8 @@ electron = require("#{root}../lib/browsers/electron")
|
||||
savedState = require("#{root}../lib/saved_state")
|
||||
Automation = require("#{root}../lib/automation")
|
||||
|
||||
ELECTRON_PID = 10001
|
||||
|
||||
describe "lib/browsers/electron", ->
|
||||
beforeEach ->
|
||||
@url = "https://foo.com"
|
||||
@@ -34,6 +36,7 @@ describe "lib/browsers/electron", ->
|
||||
remove: sinon.stub()
|
||||
}
|
||||
}
|
||||
getOSProcessId: sinon.stub().returns(ELECTRON_PID)
|
||||
"debugger": {
|
||||
attach: sinon.stub().returns()
|
||||
sendCommand: sinon.stub().resolves()
|
||||
@@ -42,8 +45,7 @@ describe "lib/browsers/electron", ->
|
||||
}
|
||||
})
|
||||
|
||||
context ".open", ->
|
||||
beforeEach ->
|
||||
@stubForOpen = ->
|
||||
sinon.stub(electron, "_render").resolves(@win)
|
||||
sinon.stub(plugins, "has")
|
||||
sinon.stub(plugins, "execute")
|
||||
@@ -53,6 +55,10 @@ describe "lib/browsers/electron", ->
|
||||
la(check.fn(state.get), "state is missing .get to stub", state)
|
||||
sinon.stub(state, "get").resolves(@state)
|
||||
|
||||
context ".open", ->
|
||||
beforeEach ->
|
||||
@stubForOpen()
|
||||
|
||||
it "calls render with url, state, and options", ->
|
||||
electron.open("electron", @url, @options, @automation)
|
||||
.then =>
|
||||
@@ -76,6 +82,9 @@ describe "lib/browsers/electron", ->
|
||||
expect(obj.kill).to.be.a("function")
|
||||
expect(obj.removeAllListeners).to.be.a("function")
|
||||
|
||||
expect(@win.webContents.getOSProcessId).to.be.calledOnce
|
||||
expect(obj.pid).to.deep.eq([ELECTRON_PID])
|
||||
|
||||
it "is noop when before:browser:launch yields null", ->
|
||||
plugins.has.returns(true)
|
||||
plugins.execute.resolves(null)
|
||||
@@ -239,6 +248,24 @@ describe "lib/browsers/electron", ->
|
||||
event, @url, parentWindow, @options.projectRoot, @state, @options
|
||||
)
|
||||
|
||||
it "adds pid of new BrowserWindow to pid list", ->
|
||||
opts = electron._defaultOptions(@options.projectRoot, @state, @options)
|
||||
|
||||
NEW_WINDOW_PID = ELECTRON_PID * 2
|
||||
|
||||
child = _.cloneDeep(@win)
|
||||
child.webContents.getOSProcessId = sinon.stub().returns(NEW_WINDOW_PID)
|
||||
|
||||
electron._launchChild.resolves(child)
|
||||
|
||||
@stubForOpen()
|
||||
.then =>
|
||||
electron.open("electron", @url, opts, @automation)
|
||||
.then (instance) =>
|
||||
opts.onNewWindow.call(@win, {}, @url)
|
||||
.then ->
|
||||
expect(instance.pid).to.deep.eq([ELECTRON_PID, NEW_WINDOW_PID])
|
||||
|
||||
## TODO: these all need to be updated
|
||||
context.skip "._launchChild", ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -5,6 +5,8 @@ cp = require("child_process")
|
||||
util = require("#{root}../lib/plugins/util")
|
||||
plugins = require("#{root}../lib/plugins")
|
||||
|
||||
PLUGIN_PID = 77777
|
||||
|
||||
describe "lib/plugins/index", ->
|
||||
beforeEach ->
|
||||
plugins._reset()
|
||||
@@ -13,6 +15,7 @@ describe "lib/plugins/index", ->
|
||||
send: sinon.spy()
|
||||
on: sinon.stub()
|
||||
kill: sinon.spy()
|
||||
pid: PLUGIN_PID
|
||||
}
|
||||
sinon.stub(cp, "fork").returns(@pluginsProcess)
|
||||
|
||||
@@ -200,3 +203,16 @@ describe "lib/plugins/index", ->
|
||||
plugins.register("foo", foo)
|
||||
plugins.execute("foo", "arg1", "arg2")
|
||||
expect(foo).to.be.calledWith("arg1", "arg2")
|
||||
|
||||
context "#getPluginPid", ->
|
||||
beforeEach ->
|
||||
plugins._setPluginsProcess(null)
|
||||
|
||||
it "returns the pid if there is a plugins process", ->
|
||||
@ipc.on.withArgs("loaded").yields([])
|
||||
plugins.init({ pluginsFile: "cypress-plugin" })
|
||||
.then ->
|
||||
expect(plugins.getPluginPid()).to.eq(PLUGIN_PID)
|
||||
|
||||
it "returns undefined if there is no plugins process", ->
|
||||
expect(plugins.getPluginPid()).to.be.undefined
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import '../../spec_helper'
|
||||
|
||||
import _ from 'lodash'
|
||||
import si from 'systeminformation'
|
||||
import { expect } from 'chai'
|
||||
import {
|
||||
_groupCyProcesses,
|
||||
_renameBrowserGroup,
|
||||
_aggregateGroups,
|
||||
_reset,
|
||||
} from '../../../lib/util/process_profiler'
|
||||
import sinon from 'sinon'
|
||||
import snapshot from 'snap-shot-it'
|
||||
|
||||
const browsers = require('../../../lib/browsers')
|
||||
const plugins = require('../../../lib/plugins')
|
||||
|
||||
const BROWSER_PID = 11111
|
||||
const SUB_BROWSER_PID = 11112
|
||||
const GUI_PID = 77777
|
||||
const PLUGIN_PID = 22222
|
||||
const SUB_PLUGIN_PID = 22223
|
||||
const FFMPEG_PID = 33333
|
||||
const MAIN_PID = process.pid
|
||||
const OTHER_PID = 66666
|
||||
const ANOTHER_PID = 88888
|
||||
const LAUNCHER_PID = 55555
|
||||
const SHARED_BROKER_PID = 99990
|
||||
const SHARED_GPU_PID = 99991
|
||||
const SHARED_UTILITY_PID = 99992
|
||||
const SHARED_ZYGOTE_PID = 99993
|
||||
|
||||
const PROCESSES: Partial<si.Systeminformation.ProcessesProcessData>[] = [
|
||||
{
|
||||
pid: MAIN_PID,
|
||||
parentPid: LAUNCHER_PID,
|
||||
params: '',
|
||||
name: 'Cypress',
|
||||
},
|
||||
{
|
||||
pid: BROWSER_PID,
|
||||
parentPid: MAIN_PID,
|
||||
params: '',
|
||||
name: 'firefox',
|
||||
},
|
||||
{
|
||||
pid: SUB_BROWSER_PID,
|
||||
parentPid: BROWSER_PID,
|
||||
params: '',
|
||||
name: 'firefox-bin',
|
||||
},
|
||||
{
|
||||
pid: GUI_PID,
|
||||
parentPid: MAIN_PID,
|
||||
params: '--type=renderer',
|
||||
name: 'Cypress',
|
||||
},
|
||||
{
|
||||
pid: PLUGIN_PID,
|
||||
parentPid: MAIN_PID,
|
||||
params: 'plugin.js',
|
||||
name: 'node',
|
||||
},
|
||||
{
|
||||
pid: SUB_PLUGIN_PID,
|
||||
parentPid: PLUGIN_PID,
|
||||
params: '',
|
||||
name: 'msword.exe',
|
||||
},
|
||||
{
|
||||
pid: FFMPEG_PID,
|
||||
parentPid: MAIN_PID,
|
||||
params: '',
|
||||
name: 'ffmpeg',
|
||||
},
|
||||
{
|
||||
pid: OTHER_PID,
|
||||
parentPid: MAIN_PID,
|
||||
params: '',
|
||||
name: 'foo',
|
||||
},
|
||||
{
|
||||
pid: ANOTHER_PID,
|
||||
parentPid: MAIN_PID,
|
||||
params: '',
|
||||
name: 'bar',
|
||||
},
|
||||
{
|
||||
pid: SHARED_GPU_PID,
|
||||
parentPid: MAIN_PID,
|
||||
params: '--type=gpu-process',
|
||||
name: 'Cypress',
|
||||
},
|
||||
{
|
||||
pid: SHARED_BROKER_PID,
|
||||
parentPid: MAIN_PID,
|
||||
params: '--type=broker',
|
||||
name: 'Cypress',
|
||||
},
|
||||
{
|
||||
pid: SHARED_UTILITY_PID,
|
||||
parentPid: MAIN_PID,
|
||||
params: '--type=utility',
|
||||
name: 'Cypress',
|
||||
},
|
||||
{
|
||||
pid: SHARED_ZYGOTE_PID,
|
||||
parentPid: MAIN_PID,
|
||||
params: '--type=zygote',
|
||||
name: 'Cypress',
|
||||
},
|
||||
]
|
||||
|
||||
describe('lib/util/process_profiler', function () {
|
||||
beforeEach(() => {
|
||||
_reset()
|
||||
})
|
||||
|
||||
context('._groupCyProcesses', () => {
|
||||
it('groups correctly', () => {
|
||||
sinon.stub(browsers, 'getBrowserInstance').returns({ pid: BROWSER_PID })
|
||||
sinon.stub(plugins, 'getPluginPid').returns(PLUGIN_PID)
|
||||
|
||||
// @ts-ignore
|
||||
const groupedProcesses = _groupCyProcesses({ list: PROCESSES })
|
||||
|
||||
const checkGroup = (pid, group) => {
|
||||
expect(_.find(groupedProcesses, { pid }))
|
||||
.to.have.property('group')
|
||||
.eq(group)
|
||||
}
|
||||
|
||||
checkGroup(BROWSER_PID, 'browser')
|
||||
checkGroup(SUB_BROWSER_PID, 'browser')
|
||||
checkGroup(GUI_PID, 'desktop-gui')
|
||||
checkGroup(PLUGIN_PID, 'plugin')
|
||||
checkGroup(SUB_PLUGIN_PID, 'plugin')
|
||||
checkGroup(FFMPEG_PID, 'ffmpeg')
|
||||
checkGroup(MAIN_PID, 'cypress')
|
||||
checkGroup(OTHER_PID, 'other')
|
||||
checkGroup(ANOTHER_PID, 'other')
|
||||
checkGroup(SHARED_GPU_PID, 'electron-shared')
|
||||
checkGroup(SHARED_BROKER_PID, 'electron-shared')
|
||||
checkGroup(SHARED_UTILITY_PID, 'electron-shared')
|
||||
checkGroup(SHARED_ZYGOTE_PID, 'electron-shared')
|
||||
})
|
||||
})
|
||||
|
||||
context('._renameBrowserGroup', () => {
|
||||
it('renames browser-grouped processes to correct name', () => {
|
||||
sinon.stub(browsers, 'getBrowserInstance').returns({ browser: { displayName: 'FooBrowser' } })
|
||||
|
||||
const processes = [
|
||||
{ group: 'foo' },
|
||||
{ group: 'bar' },
|
||||
{ group: 'browser', pid: 1 },
|
||||
{ group: 'browser', pid: 2 },
|
||||
]
|
||||
|
||||
const expected = [
|
||||
{ group: 'foo' },
|
||||
{ group: 'bar' },
|
||||
{ group: 'FooBrowser', pid: 1 },
|
||||
{ group: 'FooBrowser', pid: 2 },
|
||||
]
|
||||
|
||||
// @ts-ignore
|
||||
expect(_renameBrowserGroup(processes)).to.deep.eq(expected)
|
||||
})
|
||||
})
|
||||
|
||||
context('._aggregateGroups', () => {
|
||||
it('aggregates groups as expected', () => {
|
||||
sinon.stub(browsers, 'getBrowserInstance').returns({ pid: BROWSER_PID })
|
||||
sinon.stub(plugins, 'getPluginPid').returns(PLUGIN_PID)
|
||||
|
||||
const processes = _.cloneDeep(PROCESSES)
|
||||
.map((proc) => {
|
||||
// add some dummy measurements so there is data to aggregate
|
||||
proc.mem_rss = 10 * 1024 // 10mb
|
||||
proc.pcpu = 20
|
||||
|
||||
return proc
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
const result = _aggregateGroups(_groupCyProcesses({ list: processes }))
|
||||
|
||||
// main process will have variable pid, replace it w constant for snapshotting
|
||||
_.find(result, { pids: String(MAIN_PID) }).pids = '111111111'
|
||||
|
||||
// @ts-ignore
|
||||
snapshot(result)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user