@mzzsfy/dsh-bug-perf 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -3
- package/cordis.patch.yml +9 -0
- package/package.json +52 -4
- package/src/client.js +833 -0
- package/src/config.mjs +79 -0
- package/src/index.js +126 -0
- package/test/client-jobs.test.mjs +220 -0
- package/test/client-keepalive.test.mjs +227 -0
- package/test/client-restart.test.mjs +175 -0
- package/test/client-stuck.test.mjs +350 -0
- package/test/client-watchdog.test.mjs +186 -0
- package/test/config.test.mjs +118 -0
- package/test/host.test.mjs +141 -0
- package//345/205/274/345/256/271/346/200/247/346/265/213/350/257/225.md +199 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
const source = readFileSync(new URL('../src/client.js', import.meta.url), 'utf8')
|
|
6
|
+
|
|
7
|
+
function loadPlugin() {
|
|
8
|
+
let registered
|
|
9
|
+
const window = { __ModuleLoader__: { load: (entry) => { registered = entry } } }
|
|
10
|
+
new Function('window', source)(window)
|
|
11
|
+
return registered.factory(() => { throw new Error('no modules') })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function fakeTimers() {
|
|
15
|
+
let now = 0
|
|
16
|
+
let nextId = 0
|
|
17
|
+
const intervals = new Map()
|
|
18
|
+
return {
|
|
19
|
+
now: () => now,
|
|
20
|
+
setInterval: (fn, ms) => { const id = ++nextId; intervals.set(id, { fn, every: ms, at: now + ms }); return id },
|
|
21
|
+
clearInterval: (id) => { intervals.delete(id) },
|
|
22
|
+
advance(ms) {
|
|
23
|
+
now += ms
|
|
24
|
+
for (const [id, timer] of [...intervals]) {
|
|
25
|
+
if (timer.at <= now) {
|
|
26
|
+
timer.at = now + timer.every
|
|
27
|
+
timer.fn()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
intervalCount: () => intervals.size,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function fakeView({ writable = false, phase = 'connected', revision = 1, pendingRender, hasStream = true } = {}) {
|
|
36
|
+
const snapshot = { phase, writable }
|
|
37
|
+
return {
|
|
38
|
+
sessionId: 'session-a',
|
|
39
|
+
id: 'term-a',
|
|
40
|
+
revision,
|
|
41
|
+
pendingRender,
|
|
42
|
+
stream: hasStream ? {} : undefined,
|
|
43
|
+
connects: 0,
|
|
44
|
+
lifetime: { signal: { addEventListener() {} } },
|
|
45
|
+
state: { getSnapshot: () => snapshot },
|
|
46
|
+
snapshot,
|
|
47
|
+
connect() { this.connects += 1 },
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const OPTIONS = { checkMs: 2000, stallMs: 8000, cooldownMs: 60000 }
|
|
52
|
+
|
|
53
|
+
function stateFor(view, now = 0) {
|
|
54
|
+
return { revision: view.revision, changedAt: now, armed: false, healedAt: -Infinity }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Given 视图从未可写(旁观只读), When 长时间无输出, Then 不触发重连
|
|
58
|
+
test('看门狗_未武装_不触发', () => {
|
|
59
|
+
const plugin = loadPlugin()
|
|
60
|
+
const view = fakeView({ writable: false })
|
|
61
|
+
const state = stateFor(view)
|
|
62
|
+
const result = plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 30000, visible: true })
|
|
63
|
+
assert.equal(result, 'idle')
|
|
64
|
+
assert.equal(state.armed, false)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Given 视图曾接管输入随后卡住, When 停滞超过阈值, Then 触发一次重连并释放武装
|
|
68
|
+
test('看门狗_已武装停滞_触发重连', () => {
|
|
69
|
+
const plugin = loadPlugin()
|
|
70
|
+
const view = fakeView({ writable: true })
|
|
71
|
+
const state = stateFor(view)
|
|
72
|
+
assert.equal(plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 1000, visible: true }), 'progress')
|
|
73
|
+
assert.equal(state.armed, true)
|
|
74
|
+
view.snapshot.writable = false
|
|
75
|
+
assert.equal(plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 5000, visible: true }), 'idle')
|
|
76
|
+
assert.equal(plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 10500, visible: true }), 'heal')
|
|
77
|
+
assert.equal(state.armed, false)
|
|
78
|
+
assert.equal(state.healedAt, 10500)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Given 视图持续有新帧, When 每轮检查, Then 只报进度不触发
|
|
82
|
+
test('看门狗_持续有新帧_不触发', () => {
|
|
83
|
+
const plugin = loadPlugin()
|
|
84
|
+
const view = fakeView({ writable: true })
|
|
85
|
+
const state = stateFor(view)
|
|
86
|
+
for (let i = 1; i <= 6; i++) {
|
|
87
|
+
view.revision = i
|
|
88
|
+
const result = plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: i * 10000, visible: true })
|
|
89
|
+
assert.equal(result, 'progress')
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Given 刚自愈过, When 再次停滞进入冷却期, Then 不重复重连
|
|
94
|
+
test('看门狗_冷却期_不重复', () => {
|
|
95
|
+
const plugin = loadPlugin()
|
|
96
|
+
const view = fakeView({ writable: true })
|
|
97
|
+
const state = stateFor(view)
|
|
98
|
+
plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 1000, visible: true })
|
|
99
|
+
view.snapshot.writable = false
|
|
100
|
+
assert.equal(plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 9500, visible: true }), 'heal')
|
|
101
|
+
state.armed = true
|
|
102
|
+
assert.equal(plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 20000, visible: true }), 'cooldown')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Given 页面隐藏, When 停滞判定到期, Then 不触发(隐藏期的停顿不是故障)
|
|
106
|
+
test('看门狗_页面隐藏_不触发', () => {
|
|
107
|
+
const plugin = loadPlugin()
|
|
108
|
+
const view = fakeView({ writable: true })
|
|
109
|
+
const state = stateFor(view)
|
|
110
|
+
assert.equal(plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 1000, visible: true }), 'progress')
|
|
111
|
+
view.snapshot.writable = false
|
|
112
|
+
assert.equal(plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 20000, visible: false }), 'hidden')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Given 视图正在等渲染或没有 attach 流, When 检查, Then 不当作冻结处理
|
|
116
|
+
test('看门狗_待渲染或无流_不触发', () => {
|
|
117
|
+
const plugin = loadPlugin()
|
|
118
|
+
const pending = fakeView({ writable: true, pendingRender: {} })
|
|
119
|
+
const pendingState = stateFor(pending)
|
|
120
|
+
plugin.__test.watchdogTick({ view: pending, state: pendingState, options: OPTIONS, now: 1000, visible: true })
|
|
121
|
+
pending.snapshot.writable = false
|
|
122
|
+
assert.equal(plugin.__test.watchdogTick({ view: pending, state: pendingState, options: OPTIONS, now: 20000, visible: true }), 'pending-render')
|
|
123
|
+
|
|
124
|
+
const detached = fakeView({ writable: true, hasStream: false })
|
|
125
|
+
const detachedState = stateFor(detached)
|
|
126
|
+
plugin.__test.watchdogTick({ view: detached, state: detachedState, options: OPTIONS, now: 1000, visible: true })
|
|
127
|
+
detached.snapshot.writable = false
|
|
128
|
+
assert.equal(plugin.__test.watchdogTick({ view: detached, state: detachedState, options: OPTIONS, now: 20000, visible: true }), 'no-stream')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// Given 视图已断开重连中, When 检查, Then 只报未连接, 不叠加重连
|
|
132
|
+
test('看门狗_未连接_不触发', () => {
|
|
133
|
+
const plugin = loadPlugin()
|
|
134
|
+
const view = fakeView({ writable: true, phase: 'connecting' })
|
|
135
|
+
const state = stateFor(view)
|
|
136
|
+
plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 1000, visible: true })
|
|
137
|
+
view.snapshot.writable = false
|
|
138
|
+
assert.equal(plugin.__test.watchdogTick({ view, state, options: OPTIONS, now: 20000, visible: true }), 'not-connected')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Given 新视图由官方服务创建, When 看门狗安装后推进到停滞阈值, Then 自动调用 connect 恢复输出
|
|
142
|
+
test('看门狗_新建视图_自动重连', () => {
|
|
143
|
+
const plugin = loadPlugin()
|
|
144
|
+
const timers = fakeTimers()
|
|
145
|
+
const holder = { view() { return this.created }, created: fakeView({ writable: true }), views: new Map() }
|
|
146
|
+
const result = plugin.__test.installTerminalWatchdog({ holder, options: OPTIONS, timers, isVisible: () => true })
|
|
147
|
+
assert.equal(result.installed, true)
|
|
148
|
+
const view = holder.view('session-a', 'tab-1', 'content-1')
|
|
149
|
+
timers.advance(2000)
|
|
150
|
+
view.snapshot.writable = false
|
|
151
|
+
timers.advance(8000)
|
|
152
|
+
assert.equal(view.connects, 1)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// Given 已存在的视图, When 安装时遍历 views, Then 同样进入监控
|
|
156
|
+
test('看门狗_既有视图_进入监控', () => {
|
|
157
|
+
const plugin = loadPlugin()
|
|
158
|
+
const timers = fakeTimers()
|
|
159
|
+
const existing = fakeView({ writable: true })
|
|
160
|
+
const holder = { view: () => existing, views: new Map([['session-a', new Map([['tab-1', existing]])]]) }
|
|
161
|
+
plugin.__test.installTerminalWatchdog({ holder, options: OPTIONS, timers, isVisible: () => true })
|
|
162
|
+
timers.advance(2000)
|
|
163
|
+
existing.snapshot.writable = false
|
|
164
|
+
timers.advance(8000)
|
|
165
|
+
assert.equal(existing.connects, 1)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// Given 宿主版本没有 webTerminals.view, When 安装, Then 跳过并不抛错
|
|
169
|
+
test('看门狗_服务结构缺失_跳过不抛', () => {
|
|
170
|
+
const plugin = loadPlugin()
|
|
171
|
+
const missing = plugin.__test.installTerminalWatchdog({ holder: {}, options: OPTIONS, timers: fakeTimers(), isVisible: () => true })
|
|
172
|
+
assert.equal(missing.installed, false)
|
|
173
|
+
const alsoMissing = plugin.__test.installTerminalWatchdog({ holder: undefined, options: OPTIONS, timers: fakeTimers(), isVisible: () => true })
|
|
174
|
+
assert.equal(alsoMissing.installed, false)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Given 已安装, When 再次安装, Then 幂等不重复挂定时器
|
|
178
|
+
test('看门狗_重复安装_幂等', () => {
|
|
179
|
+
const plugin = loadPlugin()
|
|
180
|
+
const timers = fakeTimers()
|
|
181
|
+
const holder = { view: () => fakeView({ writable: true }), views: new Map() }
|
|
182
|
+
plugin.__test.installTerminalWatchdog({ holder, options: OPTIONS, timers, isVisible: () => true })
|
|
183
|
+
const again = plugin.__test.installTerminalWatchdog({ holder, options: OPTIONS, timers, isVisible: () => true })
|
|
184
|
+
assert.equal(again.installed, false)
|
|
185
|
+
assert.equal(timers.intervalCount() <= 1, true)
|
|
186
|
+
})
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { DEFAULT_CONFIG, normalizeConfig } from '../src/config.mjs'
|
|
4
|
+
|
|
5
|
+
// Given 无配置, When 归一化, Then 两个功能默认开启且阈值取默认
|
|
6
|
+
test('配置_缺省_默认全开', () => {
|
|
7
|
+
const config = normalizeConfig(undefined)
|
|
8
|
+
assert.deepEqual(config, DEFAULT_CONFIG)
|
|
9
|
+
assert.equal(config.restartCoalesce.enabled, true)
|
|
10
|
+
assert.equal(config.restartCoalesce.windowMs, 100)
|
|
11
|
+
assert.equal(config.terminalWatchdog.enabled, true)
|
|
12
|
+
assert.equal(config.terminalWatchdog.checkMs, 2000)
|
|
13
|
+
assert.equal(config.terminalWatchdog.stallMs, 8000)
|
|
14
|
+
assert.equal(config.terminalWatchdog.cooldownMs, 60000)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// Given 只覆盖一个字段, When 归一化, Then 其余字段保持默认
|
|
18
|
+
test('配置_部分覆盖_其余保持默认', () => {
|
|
19
|
+
const config = normalizeConfig({ restartCoalesce: { windowMs: 250 } })
|
|
20
|
+
assert.equal(config.restartCoalesce.windowMs, 250)
|
|
21
|
+
assert.equal(config.restartCoalesce.enabled, true)
|
|
22
|
+
assert.equal(config.terminalWatchdog.stallMs, 8000)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// Given 功能关闭, When 归一化, Then 关闭语义原样保留而阈值仍可取
|
|
26
|
+
test('配置_功能关闭_保留阈值', () => {
|
|
27
|
+
const config = normalizeConfig({ terminalWatchdog: { enabled: false, stallMs: 4000 } })
|
|
28
|
+
assert.equal(config.terminalWatchdog.enabled, false)
|
|
29
|
+
assert.equal(config.terminalWatchdog.stallMs, 4000)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Given 非法类型与越界值, When 归一化, Then 回退默认而不是把坏值带进运行时
|
|
33
|
+
test('配置_非法值_回退默认', () => {
|
|
34
|
+
const config = normalizeConfig({
|
|
35
|
+
restartCoalesce: { enabled: 'yes', windowMs: -1 },
|
|
36
|
+
terminalWatchdog: { checkMs: 0, stallMs: 'soon', cooldownMs: Number.NaN },
|
|
37
|
+
})
|
|
38
|
+
assert.equal(config.restartCoalesce.enabled, true)
|
|
39
|
+
assert.equal(config.restartCoalesce.windowMs, 100)
|
|
40
|
+
assert.equal(config.terminalWatchdog.checkMs, 2000)
|
|
41
|
+
assert.equal(config.terminalWatchdog.stallMs, 8000)
|
|
42
|
+
assert.equal(config.terminalWatchdog.cooldownMs, 60000)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Given 超上限的阈值, When 归一化, Then 收敛到上限(防止错误配置把功能变成滥用)
|
|
46
|
+
test('配置_越界上限_收敛', () => {
|
|
47
|
+
const config = normalizeConfig({
|
|
48
|
+
restartCoalesce: { windowMs: 60000 },
|
|
49
|
+
terminalWatchdog: { checkMs: 600000, stallMs: 999999999, cooldownMs: 999999999 },
|
|
50
|
+
})
|
|
51
|
+
assert.equal(config.restartCoalesce.windowMs, 2000)
|
|
52
|
+
assert.equal(config.terminalWatchdog.checkMs, 60000)
|
|
53
|
+
assert.equal(config.terminalWatchdog.stallMs, 600000)
|
|
54
|
+
assert.equal(config.terminalWatchdog.cooldownMs, 3600000)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Given 未知字段, When 归一化, Then 被丢弃(配置面只认已声明字段)
|
|
58
|
+
test('配置_未知字段_丢弃', () => {
|
|
59
|
+
const config = normalizeConfig({ unknown: 1, restartCoalesce: { windowMs: 150 } })
|
|
60
|
+
assert.equal(config.unknown, undefined)
|
|
61
|
+
assert.equal(config.restartCoalesce.windowMs, 150)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Given 无配置, When 归一化, Then 保活与停滞兜底默认开启且阈值取默认
|
|
65
|
+
test('配置_保活与停滞_默认值', () => {
|
|
66
|
+
const config = normalizeConfig(undefined)
|
|
67
|
+
assert.deepEqual(config.socketKeepAlive, { enabled: true, intervalMs: 1500 })
|
|
68
|
+
assert.deepEqual(config.stuckSession, {
|
|
69
|
+
enabled: true,
|
|
70
|
+
checkMs: 2000,
|
|
71
|
+
stallMs: 8000,
|
|
72
|
+
reconnect: true,
|
|
73
|
+
maxReconnectAttempts: 5,
|
|
74
|
+
reconnectIntervalMs: 15000,
|
|
75
|
+
})
|
|
76
|
+
assert.deepEqual(config.jobStreamLimit, { enabled: true, base: 32, max: 48 })
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Given 新增补丁的非法值, When 归一化, Then 回退默认而不是把坏值带进运行时
|
|
80
|
+
test('配置_保活与停滞_非法值回退', () => {
|
|
81
|
+
const config = normalizeConfig({
|
|
82
|
+
socketKeepAlive: { enabled: 'on', intervalMs: 0 },
|
|
83
|
+
stuckSession: { checkMs: -5, stallMs: 'slow', reconnect: 1, maxReconnectAttempts: Number.NaN, reconnectIntervalMs: 0 },
|
|
84
|
+
})
|
|
85
|
+
assert.equal(config.socketKeepAlive.enabled, true)
|
|
86
|
+
assert.equal(config.socketKeepAlive.intervalMs, 1500)
|
|
87
|
+
assert.equal(config.stuckSession.checkMs, 2000)
|
|
88
|
+
assert.equal(config.stuckSession.stallMs, 8000)
|
|
89
|
+
assert.equal(config.stuckSession.reconnect, true)
|
|
90
|
+
assert.equal(config.stuckSession.maxReconnectAttempts, 5)
|
|
91
|
+
assert.equal(config.stuckSession.reconnectIntervalMs, 15000)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// Given 超上限的阈值, When 归一化, Then 收敛到上限
|
|
95
|
+
test('配置_保活与停滞_越界收敛', () => {
|
|
96
|
+
const config = normalizeConfig({
|
|
97
|
+
socketKeepAlive: { intervalMs: 999999 },
|
|
98
|
+
stuckSession: { checkMs: 999999, stallMs: 999999999, maxReconnectAttempts: 999999, reconnectIntervalMs: 999999999 },
|
|
99
|
+
})
|
|
100
|
+
assert.equal(config.socketKeepAlive.intervalMs, 60000)
|
|
101
|
+
assert.equal(config.stuckSession.checkMs, 60000)
|
|
102
|
+
assert.equal(config.stuckSession.stallMs, 600000)
|
|
103
|
+
assert.equal(config.stuckSession.maxReconnectAttempts, 20)
|
|
104
|
+
assert.equal(config.stuckSession.reconnectIntervalMs, 600000)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Given 任务流上限被关掉或给了非法值, When 归一化, Then 关闭生效、非法值回退默认
|
|
108
|
+
test('配置_任务流上限_开关与回退', () => {
|
|
109
|
+
const off = normalizeConfig({ jobStreamLimit: { enabled: false } })
|
|
110
|
+
assert.equal(off.jobStreamLimit.enabled, false)
|
|
111
|
+
const bad = normalizeConfig({ jobStreamLimit: { base: 0, max: 0 } })
|
|
112
|
+
assert.equal(bad.jobStreamLimit.base, 32)
|
|
113
|
+
assert.equal(bad.jobStreamLimit.max, 48)
|
|
114
|
+
const capped = normalizeConfig({ jobStreamLimit: { max: 99999 } })
|
|
115
|
+
assert.equal(capped.jobStreamLimit.max, 512)
|
|
116
|
+
const inverted = normalizeConfig({ jobStreamLimit: { base: 200, max: 10 } })
|
|
117
|
+
assert.equal(inverted.jobStreamLimit.base, 10)
|
|
118
|
+
})
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { DEFAULT_CONFIG } from '../src/config.mjs'
|
|
4
|
+
import { apply, Config, name } from '../src/index.js'
|
|
5
|
+
|
|
6
|
+
function makeRes() {
|
|
7
|
+
return {
|
|
8
|
+
status: undefined,
|
|
9
|
+
headers: undefined,
|
|
10
|
+
body: undefined,
|
|
11
|
+
writeHead(status, headers) { this.status = status; this.headers = headers },
|
|
12
|
+
end(body) { this.body = body },
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeCtx({ server, registerError } = {}) {
|
|
17
|
+
const registrations = []
|
|
18
|
+
const disposed = []
|
|
19
|
+
const ctx = {
|
|
20
|
+
get: (service) => (service === 'webServer' ? server : undefined),
|
|
21
|
+
effect: (factory) => {
|
|
22
|
+
const dispose = factory()
|
|
23
|
+
ctx.effects.push(dispose)
|
|
24
|
+
return dispose
|
|
25
|
+
},
|
|
26
|
+
effects: [],
|
|
27
|
+
registrations,
|
|
28
|
+
disposed,
|
|
29
|
+
}
|
|
30
|
+
if (server !== undefined) {
|
|
31
|
+
server.register = (route) => {
|
|
32
|
+
if (registerError !== undefined) throw registerError
|
|
33
|
+
registrations.push(route)
|
|
34
|
+
return () => disposed.push(route.path)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return ctx
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function request(route, config) {
|
|
41
|
+
const res = makeRes()
|
|
42
|
+
await route.handler({ url: '/api/dsh-bug-perf/config', method: 'GET' }, res)
|
|
43
|
+
return res
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function requestDiag(route, query) {
|
|
47
|
+
const res = makeRes()
|
|
48
|
+
await route.handler({ url: `/api/dsh-bug-perf/diag${query}`, method: 'GET' }, res)
|
|
49
|
+
return res
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Given 插件声明注入, When 装载器解析, Then 等待 webServer 就绪后再激活(不是同步探测)
|
|
53
|
+
test('宿主_注入声明_等待webServer', async () => {
|
|
54
|
+
const index = await import('../src/index.js')
|
|
55
|
+
assert.deepEqual(index.inject, ['webServer'])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Given 宿主提供 webServer, When 插件激活, Then 注册只读配置路由且路径固定
|
|
59
|
+
test('宿主_激活_注册配置路由', () => {
|
|
60
|
+
const ctx = makeCtx({ server: {} })
|
|
61
|
+
apply(ctx, undefined)
|
|
62
|
+
assert.equal(name, 'dsh-bug-perf')
|
|
63
|
+
assert.equal(ctx.registrations.length, 2)
|
|
64
|
+
assert.equal(ctx.registrations[0].kind, 'exact')
|
|
65
|
+
assert.equal(ctx.registrations[0].path, '/api/dsh-bug-perf/config')
|
|
66
|
+
assert.equal(ctx.registrations[1].path, '/api/dsh-bug-perf/diag')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// Given 行配置为缺省, When 客户端读取配置, Then 返回默认配置 JSON
|
|
70
|
+
test('宿主_缺省配置_返回默认值', async () => {
|
|
71
|
+
const ctx = makeCtx({ server: {} })
|
|
72
|
+
apply(ctx, undefined)
|
|
73
|
+
const res = await request(ctx.registrations[0])
|
|
74
|
+
assert.equal(res.status, 200)
|
|
75
|
+
const json = JSON.parse(res.body)
|
|
76
|
+
assert.deepEqual(json.config, DEFAULT_CONFIG)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Given 行配置部分覆盖, When 客户端读取, Then 覆盖项生效且其余为默认
|
|
80
|
+
test('宿主_部分覆盖_返回归一化结果', async () => {
|
|
81
|
+
const ctx = makeCtx({ server: {} })
|
|
82
|
+
apply(ctx, { restartCoalesce: { enabled: false }, terminalWatchdog: { stallMs: 12000 } })
|
|
83
|
+
const json = JSON.parse((await request(ctx.registrations[0])).body)
|
|
84
|
+
assert.equal(json.config.restartCoalesce.enabled, false)
|
|
85
|
+
assert.equal(json.config.restartCoalesce.windowMs, 100)
|
|
86
|
+
assert.equal(json.config.terminalWatchdog.stallMs, 12000)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// Given 0.1.7 宿主把整节配置包成 volatile ref, When 读取, Then 解箱后返回
|
|
90
|
+
test('宿主_volatile配置_解箱', async () => {
|
|
91
|
+
const ctx = makeCtx({ server: {} })
|
|
92
|
+
apply(ctx, { get: () => ({ terminalWatchdog: { enabled: false } }) })
|
|
93
|
+
const json = JSON.parse((await request(ctx.registrations[0])).body)
|
|
94
|
+
assert.equal(json.config.terminalWatchdog.enabled, false)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Given 宿主没有 webServer 服务, When 插件激活, Then 不注册不抛错(旧宿主/无 web 场景)
|
|
98
|
+
test('宿主_无webServer_干净跳过', () => {
|
|
99
|
+
const ctx = makeCtx()
|
|
100
|
+
assert.doesNotThrow(() => apply(ctx, undefined))
|
|
101
|
+
assert.equal(ctx.registrations.length, 0)
|
|
102
|
+
assert.equal(ctx.effects.length, 0)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Given 路由注册抛错, When 插件激活, Then 错误被记录而不是拖垮页面
|
|
106
|
+
test('宿主_注册失败_不抛', () => {
|
|
107
|
+
const ctx = makeCtx({ server: {}, registerError: new Error('route table frozen') })
|
|
108
|
+
assert.doesNotThrow(() => apply(ctx, undefined))
|
|
109
|
+
assert.equal(ctx.registrations.length, 0)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Given 插件导出 Config, When 取各节默认值, Then 与共享默认值一致(设置页与运行时同源)
|
|
113
|
+
test('宿主_Config默认值_与共享默认一致', () => {
|
|
114
|
+
const restart = Config.dict.restartCoalesce({})
|
|
115
|
+
const watchdog = Config.dict.terminalWatchdog({})
|
|
116
|
+
assert.equal(restart.enabled, DEFAULT_CONFIG.restartCoalesce.enabled)
|
|
117
|
+
assert.equal(restart.windowMs, DEFAULT_CONFIG.restartCoalesce.windowMs)
|
|
118
|
+
assert.equal(watchdog.enabled, DEFAULT_CONFIG.terminalWatchdog.enabled)
|
|
119
|
+
assert.equal(watchdog.checkMs, DEFAULT_CONFIG.terminalWatchdog.checkMs)
|
|
120
|
+
assert.equal(watchdog.stallMs, DEFAULT_CONFIG.terminalWatchdog.stallMs)
|
|
121
|
+
assert.equal(watchdog.cooldownMs, DEFAULT_CONFIG.terminalWatchdog.cooldownMs)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// Given 浏览器上报采样, When 宿主收到合法载荷, Then 返回 ok 并落盘
|
|
125
|
+
test('宿主_采样上报_接受合法载荷', async () => {
|
|
126
|
+
const ctx = makeCtx({ server: {} })
|
|
127
|
+
apply(ctx, undefined)
|
|
128
|
+
const res = await requestDiag(ctx.registrations[1], `?d=${encodeURIComponent(JSON.stringify({ at: 1, totals: { opened: 1 } }))}`)
|
|
129
|
+
assert.equal(res.status, 200)
|
|
130
|
+
assert.deepEqual(JSON.parse(res.body), { ok: true })
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Given 缺少载荷或载荷不是 JSON, When 上报, Then 返回 400 且不抛出
|
|
134
|
+
test('宿主_采样上报_拒绝坏载荷', async () => {
|
|
135
|
+
const ctx = makeCtx({ server: {} })
|
|
136
|
+
apply(ctx, undefined)
|
|
137
|
+
const missing = await requestDiag(ctx.registrations[1], '')
|
|
138
|
+
assert.equal(missing.status, 400)
|
|
139
|
+
const broken = await requestDiag(ctx.registrations[1], '?d=not-json')
|
|
140
|
+
assert.equal(broken.status, 400)
|
|
141
|
+
})
|