Welcome to mirror list, hosted at ThFree Co, Russian Federation.

signal-handling.js « test « arborist « workspaces - github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 344f60cf89958be76e5a59119274a494b3a25ea2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
const t = require('tap')

const onExit = require('../lib/signal-handling.js')
t.equal(onExit.process, process, 'uses the real process by default')

// ok, from here on out, use our mock
const EE = require('events')
const proc = onExit.process = new class MockProcess extends EE {
  constructor () {
    super()
    this.pid = process.pid
  }

  // yes, yes, the actual process.kill doesn't return a promise, but
  // we need to know when this async action is done to know when to
  // continue on to other parts of the test.
  kill (pid, signal) {
    if (pid !== this.pid) {
      throw Object.assign(new Error('wrong pid sent to kill() method'), {
        expect: this.pid,
        actual: pid,
      })
    }
    return new Promise(res => process.nextTick(() => {
      this.emit(signal)
      res()
    }))
  }
}()

t.test('load and unload', async t => {
  t.plan(2)

  const unload = onExit(({ signal }) => t.equal(signal, 'SIGINT'))
  unload()

  // should not trigger for these
  await Promise.all([
    proc.kill(process.pid, 'SIGINT'),
    proc.kill(process.pid, 'SIGINT'),
    proc.kill(process.pid, 'SIGINT'),
  ])

  t.equal(proc.listeners('beforeExit').length, 0, 'did not leave beforeExit')
  t.equal(proc.listeners('SIGINT').length, 0, 'did not leave SIGINT')

  // calling multiple times is safe
  unload()
  unload()
})

t.test('load only listens to one event', async t => {
  t.plan(2)
  onExit(({ signal }) => t.equal(signal, 'SIGINT'))
  await proc.kill(process.pid, 'SIGINT')
  // should not trigger our onExit handler, but WILL call this one,
  // since the beforeExit handler was assigned
  proc.once('SIGINT', () => t.pass('got follow-up signal'))
  proc.emit('beforeExit')
  // give it a moment for the synthetic kill timeout to fire
  await new Promise(res => setTimeout(res))
})

t.test('only respond to first signal', async t => {
  t.plan(5)
  onExit(({ signal }) => t.equal(signal, 'SIGINT'))
  await proc.kill(process.pid, 'SIGINT')
  await proc.kill(process.pid, 'SIGHUP')
  await proc.kill(process.pid, 'SIGTERM')
  proc.emit('beforeExit')
  t.equal(proc.listeners('beforeExit').length, 0, 'did not leave beforeExit')
  t.equal(proc.listeners('SIGINT').length, 0, 'did not leave SIGINT')
  t.equal(proc.listeners('SIGHUP').length, 0, 'did not leave SIGHUP')
  t.equal(proc.listeners('SIGTERM').length, 0, 'did not leave SIGTERM')
})

t.test('can do multiple loads in parallel', t => {
  t.plan(2)

  t.test('running two handlers', async t => {
    t.plan(3)
    onExit(({ signal }) => t.equal(signal, 'SIGINT', '1'))
    onExit(({ signal }) => t.equal(signal, 'SIGINT', '2'))
    await proc.kill(process.pid, 'SIGINT')
    // should not trigger our onExit handler, but WILL call this one,
    // since the beforeExit handler was assigned
    proc.once('SIGINT', () => t.pass('got follow-up signal'))
    proc.emit('beforeExit')
    // give it a moment for the synthetic kill timeout to fire
    await new Promise(res => setTimeout(res))
  })

  t.test('verify cleanup after both are finished', t => {
    t.plan(4)
    t.equal(proc.listeners('beforeExit').length, 0, 'did not leave beforeExit')
    t.equal(proc.listeners('SIGINT').length, 0, 'did not leave SIGINT')
    t.equal(proc.listeners('SIGHUP').length, 0, 'did not leave SIGHUP')
    t.equal(proc.listeners('SIGTERM').length, 0, 'did not leave SIGTERM')
  })
})

t.test('no max listener warning', t => {
  t.plan(1)
  const { emitWarning } = process
  Object.defineProperty(process, 'emitWarning', {
    value: (...args) => {
      emitWarning.call(process, ...args)
      throw new Error('no warnings should be emitted')
    },
    configurable: true,
  })
  t.teardown(() => Object.defineProperty(process, 'emitWarning', {
    value: emitWarning,
    configurable: true,
  }))

  const unloads = []
  for (let i = 0; i < 1000; i++) {
    unloads.push(onExit(() => {}))
  }
  for (const unload of unloads) {
    unload()
  }

  t.pass('if no throw, then it worked')
})