-
Notifications
You must be signed in to change notification settings - Fork 6
/
errors.spec.ts
208 lines (176 loc) · 7.46 KB
/
errors.spec.ts
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import {
threadedClass,
ThreadedClassManager,
ThreadedClass
} from '../index'
import { TestClassErrors } from '../../test-lib/testClassErrors'
import { RegisterExitHandlers } from '../parent-process/manager'
import { cbError, cbReject, cbReturnBadValue } from './lib/errorCallbacks'
const TESTCLASS_PATH = '../../test-lib/testClassErrors.js'
const getTests = (disableMultithreading: boolean) => {
return () => {
let threaded: ThreadedClass<TestClassErrors>
let onClosed = jest.fn()
let onError = jest.fn()
let onClosedListener: any
let onErrorListener: any
beforeAll(async () => {
ThreadedClassManager.handleExit = RegisterExitHandlers.NO
ThreadedClassManager.debug = false
threaded = await threadedClass<TestClassErrors, typeof TestClassErrors>(TESTCLASS_PATH, 'TestClassErrors', [], { disableMultithreading })
onClosedListener = ThreadedClassManager.onEvent(threaded, 'thread_closed', onClosed)
onErrorListener = ThreadedClassManager.onEvent(threaded, 'error', onError)
})
beforeEach(() => {
expect(threaded).toBeTruthy()
})
afterEach(() => {
expect((threaded as any).__uncaughtError).toBeFalsy()
})
afterAll(async () => {
await ThreadedClassManager.destroy(threaded)
expect(ThreadedClassManager.getThreadCount()).toEqual(0)
onClosedListener.stop()
onErrorListener.stop()
expect(onClosed).toHaveBeenCalledTimes(1)
})
test('Error in called method', async () => {
await expect(threaded.doError()).rejects.toMatch(/TestError/)
// ensure that the original path is included in the stack-trace:
await expect(threaded.doError()).rejects.toMatch(/testClassErrors.js/)
await expect(threaded.doError()).rejects.toMatch(/errors.spec/)
})
test('SyntaxError in called method', async () => {
await expect(threaded.doSyntaxError()).rejects.toMatch(/SyntaxError/)
// ensure that the original path is included in the stack-trace:
await expect(threaded.doSyntaxError()).rejects.toMatch(/testClassErrors.js/)
await expect(threaded.doSyntaxError()).rejects.toMatch(/errors.spec/)
})
test('Error in callback', async () => {
// Pre-test: check that cbError throws an error:
expect(returnError(cbError)).toMatch(/TestError in callback 123/)
// ensure that the original path is included in the stack-trace:
expect(returnError(cbError)).toMatch(/lib[\/\\]errorCallbacks/)
expect(returnError(cbError)).toMatch(/errors.spec/)
await expect(threaded.callCallback(cbError)).rejects.toMatch(/TestError in callback 123/)
// ensure that the original path is included in the stack-trace:
await expect(threaded.callCallback(cbError)).rejects.toMatch(/lib[\/\\]errorCallbacks/)
await expect(threaded.callCallback(cbError)).rejects.toMatch(/errors.spec/)
await expect(threaded.callCallback(cbReject)).rejects.toMatch(/Rejected promise 123/)
// ensure that the original path is included in the stack-trace:
await expect(threaded.callCallback(cbReject)).rejects.toMatch(/lib[\/\\]errorCallbacks/)
await expect(threaded.callCallback(cbReject)).rejects.toMatch(/errors.spec/)
})
if (!disableMultithreading) {
test('Error in unhandled promise', async () => {
expect(await threaded.getUnhandledPromiseRejections()).toHaveLength(0)
// This sets up an unhandled promise on in the child thread, and causes it to reject:
await threaded.rejectUnhandledPromise()
await sleep(10) // Ensure that the promise has been rejected
const unhandled = await threaded.getUnhandledPromiseRejections()
expect(unhandled).toHaveLength(1)
expect(unhandled[0]).toMatch(/Rejecting promise/)
expect(unhandled[0]).toMatch(/testClassErrors.js/)
await threaded.clearUnhandledPromiseRejections()
})
if (process.version.startsWith('v10.')) {
// For some unknown reason, this test fails on node 10.x in CI.
// Since Node 10 is on it's way out, we'll just skip it for now.
test('Error in event listener', async () => {
expect(await threaded.getUnhandledPromiseRejections()).toHaveLength(0)
// Set up an event listener that throws an error, on the parent thread:
await threaded.on('testEvent', () => {
throw new Error('TestError in event listener')
})
console.log(process.version)
// await expect(threaded.emitEvent('testEvent')).rejects.toMatch(/TestError in event listener/)
await threaded.emitEvent('testEvent')
// Because event emit/listeners don't handle promises, there should be an unhandled Promise rejection in the client:
const unhandled = await threaded.getUnhandledPromiseRejections()
expect(unhandled).toHaveLength(1)
/*
Error: TestError in event listener
at threadedClass\src\__tests__\errors.spec.ts:84:11
at Object.onMessageFromInstance [as onMessageCallback] (threadedClass\src\parent-process\threadedClass.ts:131:23)
at TestClassErrors.emit (events.js:400:28)
at TestClassErrors.emitEvent (threadedClass\test-lib\testClassErrors.js:18:14)
at ThreadedWorker.handleInstanceMessageFromParent (threadedClass\dist\child-process\worker.js:311:34)
...
*/
const errorLines = unhandled[0].split('\n')
expect(errorLines[0]).toMatch(/TestError in event listener/)
expect(errorLines[1]).toMatch(/errors.spec/)
expect(errorLines[2]).toMatch(/threadedClass/)
expect(errorLines[3]).toMatch(/emit/)
expect(errorLines[4]).toMatch(/testClassErrors/)
await threaded.clearUnhandledPromiseRejections()
})
}
const m = (process.version + '').match(/(\d+)\.(\d+)\.(\d+)/)
if (
m &&
m[1] &&
parseInt(m[1], 10) >= 11
) {
// DataCloneError is introduced in Node.js 11+
test('Internal error when sending invalid value', async () => {
let err: string | undefined
try {
await threaded.receiveValue({
a: () => {
// This is a function.
// ThreadedClass doesn't support functions inside of objects.
// Sending this will throw a DataCloneError.
}
})
} catch (e) {
if (typeof e === 'object') err = e.stack
else err = `${e}`
}
expect(err).toMatch(/DataCloneError/)
// ensure that the original path is included in the stack-trace:
expect(err).toMatch(/errors.spec/)
})
test('Internal error when returning invalid value in callback', async () => {
let err: string | undefined
try {
await threaded.callCallback(cbReturnBadValue)
} catch (e) {
if (typeof e === 'object') err = e.stack
else err = `${e}`
}
expect(err).toMatch(/DataCloneError/)
// ensure that the original path is included in the stack-trace:
expect(err).toMatch(/errors.spec/)
})
}
test('Error thrown in an setTimeout function', async () => {
expect(onClosed).toHaveBeenCalledTimes(0)
await expect(threaded.doAsyncError()).resolves.toBeTruthy()
await sleep(10)
expect(onClosed).toHaveBeenCalledTimes(1)
if (!process.version.startsWith('v10.')) {
// In Node 10, errors in setTimeout are only logged.
expect(onError).toHaveBeenCalledTimes(1)
expect(onError.mock.calls[0][0].message).toMatch(/DaleATuCuerpoAlegría/)
}
})
// }
}
}
}
describe('threadedclass', getTests(false))
describe('threadedclass single thread', getTests(true))
function returnError (cb: () => any): string | null {
try {
cb()
} catch (error) {
let str = error.toString()
if (typeof error === 'object') str += '\n' + error.stack
return str
}
return null
}
function sleep (ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}