Skip to content

Commit 8975c33

Browse files
committed
expand test suite
1 parent bcb6423 commit 8975c33

File tree

2 files changed

+90
-12
lines changed

2 files changed

+90
-12
lines changed

packages/core/src/shared/utilities/processUtils.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,23 @@ interface Tracker<T> {
7373

7474
export const eof = Symbol('EOF')
7575

76+
export interface ProcessStats {
77+
memory: number
78+
cpu: number
79+
elapsed: number
80+
}
7681
export class ChildProcessTracker implements Tracker<ChildProcess> {
7782
static pollingInterval: number = 10000
78-
static thresholds: { memory: number; cpu: number; time: number } = {
83+
static thresholds: ProcessStats = {
7984
memory: 100 * 1024 * 1024, // 100 MB
8085
cpu: 50,
81-
time: 30 * 1000, // 30 seconds
86+
elapsed: 30 * 1000, // 30 seconds
8287
}
8388
#processByPid: Map<number, ChildProcess> = new Map<number, ChildProcess>()
8489
#pids: PollingSet<number>
8590

8691
public constructor() {
8792
this.#pids = new PollingSet(ChildProcessTracker.pollingInterval, () => this.monitor())
88-
getLogger().debug(`ChildProcessTracker created with polling interval: ${ChildProcessTracker.pollingInterval}`)
8993
}
9094

9195
public cleanUp() {
@@ -116,7 +120,7 @@ export class ChildProcessTracker implements Tracker<ChildProcess> {
116120
if (stats.cpu > ChildProcessTracker.thresholds.cpu) {
117121
getLogger().warn(`Process ${pid} exceeded cpu threshold: ${stats.cpu}`)
118122
}
119-
if (stats.elapsed > ChildProcessTracker.thresholds.time) {
123+
if (stats.elapsed > ChildProcessTracker.thresholds.elapsed) {
120124
getLogger().warn(`Process ${pid} exceeded time threshold: ${stats.elapsed}`)
121125
}
122126
} else {
@@ -144,7 +148,7 @@ export class ChildProcessTracker implements Tracker<ChildProcess> {
144148
return this.#pids.has(childProcess.pid())
145149
}
146150

147-
private async getUsage(pid: number): Promise<{ memory: number; cpu: number; elapsed: number }> {
151+
public async getUsage(pid: number): Promise<ProcessStats> {
148152
const stats = await pidusage(pid)
149153
return {
150154
memory: stats.memory,

packages/core/src/test/shared/utilities/processUtils.test.ts

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import assert from 'assert'
77
import * as os from 'os'
88
import * as path from 'path'
99
import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../../shared/filesystemUtilities'
10-
import { ChildProcess, ChildProcessTracker, eof } from '../../../shared/utilities/processUtils'
10+
import { ChildProcess, ChildProcessTracker, eof, ProcessStats } from '../../../shared/utilities/processUtils'
1111
import { sleep } from '../../../shared/utilities/timeoutUtils'
1212
import { Timeout, waitUntil } from '../../../shared/utilities/timeoutUtils'
1313
import { fs } from '../../../shared'
1414
import * as FakeTimers from '@sinonjs/fake-timers'
1515
import { installFakeClock } from '../../testUtil'
16+
import { isWin } from '../../../shared/vscode/env'
17+
import Sinon from 'sinon'
18+
import { assertLogsContain } from '../../globalSetup.test'
1619

1720
describe('ChildProcess', async function () {
1821
let tempFolder: string
@@ -353,6 +356,10 @@ describe('ChildProcess', async function () {
353356
}
354357
})
355358

359+
function getSleepCmd() {
360+
return isWin() ? 'timeout' : 'sleep'
361+
}
362+
356363
describe('ChildProcessTracker', function () {
357364
let tracker: ChildProcessTracker
358365
let clock: FakeTimers.InstalledClock
@@ -361,13 +368,80 @@ describe('ChildProcessTracker', function () {
361368
tracker = new ChildProcessTracker()
362369
})
363370

364-
it('removes stopped processes every X seconds', async function () {
365-
const childProcess = new ChildProcess('echo', ['hi'])
366-
await childProcess.run()
371+
it(`removes stopped processes every ${ChildProcessTracker.pollingInterval / 1000} seconds`, async function () {
372+
// Start a 'sleep' command, check it only removes after we stop it.
373+
const childProcess = new ChildProcess(getSleepCmd(), ['90'])
374+
childProcess.run().catch(() => assert.fail('sleep command threw an error'))
375+
tracker.add(childProcess)
376+
assert.strictEqual(tracker.has(childProcess), true, 'failed to add sleep command')
377+
378+
await clock.tickAsync(ChildProcessTracker.pollingInterval)
379+
assert.strictEqual(tracker.has(childProcess), true, 'process was mistakenly removed')
380+
childProcess.stop(true)
381+
382+
await clock.tickAsync(ChildProcessTracker.pollingInterval)
383+
assert.strictEqual(tracker.has(childProcess), false, 'process was not removed after stopping')
384+
})
385+
386+
it('multiple processes from same command are tracked seperately', async function () {
387+
const childProcess1 = new ChildProcess(getSleepCmd(), ['90'])
388+
const childProcess2 = new ChildProcess(getSleepCmd(), ['90'])
389+
childProcess1.run().catch(() => assert.fail('sleep command threw an error'))
390+
childProcess2.run().catch(() => assert.fail('sleep command threw an error'))
391+
392+
tracker.add(childProcess1)
393+
tracker.add(childProcess2)
394+
395+
assert.strictEqual(tracker.has(childProcess1), true, 'Missing first process')
396+
assert.strictEqual(tracker.has(childProcess2), true, 'Missing second process')
397+
assert.strictEqual(tracker.size(), 2)
398+
399+
childProcess1.stop()
400+
await clock.tickAsync(ChildProcessTracker.pollingInterval + 1)
401+
assert.strictEqual(tracker.has(childProcess2), true, 'first process was not removed after stopping it')
402+
assert.strictEqual(tracker.size(), 1)
403+
404+
childProcess2.stop()
405+
await clock.tickAsync(ChildProcessTracker.pollingInterval + 1)
406+
assert.strictEqual(tracker.size(), 0, 'second process was not removed after stopping it')
407+
})
408+
409+
it('logs a warning message when system usage exceeds threshold', async function () {
410+
const childProcess = new ChildProcess(getSleepCmd(), ['90'])
411+
childProcess.run().catch(() => assert.fail('sleep command threw an error'))
367412
tracker.add(childProcess)
368-
childProcess.stop()
369-
assert.strictEqual(tracker.has(childProcess), true)
413+
414+
const usageMock = Sinon.stub(ChildProcessTracker.prototype, 'getUsage')
415+
const highCpu: ProcessStats = {
416+
cpu: ChildProcessTracker.thresholds.cpu + 1,
417+
memory: 0,
418+
elapsed: 0,
419+
}
420+
const highMemory: ProcessStats = {
421+
cpu: 0,
422+
memory: ChildProcessTracker.thresholds.memory + 1,
423+
elapsed: 0,
424+
}
425+
426+
const highTime: ProcessStats = {
427+
cpu: 0,
428+
memory: 0,
429+
elapsed: ChildProcessTracker.thresholds.elapsed + 1,
430+
}
431+
432+
usageMock.resolves(highCpu)
433+
434+
await clock.tickAsync(ChildProcessTracker.pollingInterval)
435+
assertLogsContain('exceeded cpu threshold', false, 'warn')
436+
437+
usageMock.resolves(highMemory)
370438
await clock.tickAsync(ChildProcessTracker.pollingInterval)
371-
assert.strictEqual(tracker.has(childProcess), false)
439+
assertLogsContain('exceeded memory threshold', false, 'warn')
440+
441+
usageMock.resolves(highTime)
442+
await clock.tickAsync(ChildProcessTracker.pollingInterval)
443+
assertLogsContain('exceeded time threshold', false, 'warn')
444+
445+
usageMock.restore()
372446
})
373447
})

0 commit comments

Comments
 (0)