Skip to content

Commit 5bc78e5

Browse files
committed
test: add test to increase code coverage
1 parent c3affc0 commit 5bc78e5

File tree

11 files changed

+923
-5
lines changed

11 files changed

+923
-5
lines changed

.github/workflows/ci-node.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,11 @@ jobs:
8484
tail -n 50 /tmp/iii-engine.log || true
8585
exit 1
8686
87-
- name: Run tests
87+
- name: Run tests with coverage
8888
env:
8989
III_BRIDGE_URL: ws://localhost:49199
9090
III_HTTP_URL: http://localhost:3199
91-
run: pnpm --filter iii-sdk test
91+
run: pnpm --filter iii-sdk test:coverage
9292

9393
- name: Stop III Engine
9494
if: always()

.github/workflows/ci-python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
tail -n 50 /tmp/iii-engine.log || true
7070
exit 1
7171
72-
- name: Run tests
72+
- name: Run tests with coverage
7373
run: uv run pytest -q
7474

7575
- name: Stop III Engine

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ worker_py/__pycache__/
77
.vscode/
88
.bin/
99
dist/
10+
.coverage
11+
coverage/
12+
htmlcov/
1013
.DS_Store
1114
.idea/
1215
.cursor/

packages/node/iii/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"scripts": {
1515
"build": "tsdown",
1616
"test": "vitest run",
17+
"test:coverage": "vitest run --coverage",
1718
"test:watch": "vitest"
1819
},
1920
"exports": {
@@ -53,9 +54,10 @@
5354
"ws": "^8.18.3"
5455
},
5556
"devDependencies": {
57+
"@vitest/coverage-v8": "^2.1.0",
5658
"@types/ws": "^8.18.1",
5759
"tsdown": "^0.17.0",
5860
"typescript": "^5.9.3",
5961
"vitest": "^2.1.0"
6062
}
61-
}
63+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
import { WorkerMetricsCollector } from '../src/worker-metrics'
3+
import { registerWorkerGauges, stopWorkerGauges } from '../src/otel-worker-gauges'
4+
5+
type FakeGauge = { name: string }
6+
7+
describe('registerWorkerGauges', () => {
8+
beforeEach(() => {
9+
vi.clearAllMocks()
10+
stopWorkerGauges()
11+
})
12+
13+
it('registers gauges once, records metrics, and unregisters the callback on stop', () => {
14+
const gauges: FakeGauge[] = []
15+
let batchCallback: ((result: { observe: (...args: unknown[]) => void }) => void) | undefined
16+
17+
vi.spyOn(WorkerMetricsCollector.prototype, 'collect').mockReturnValue({
18+
memory_heap_used: 10,
19+
memory_heap_total: 20,
20+
memory_rss: 30,
21+
memory_external: 40,
22+
cpu_percent: 50,
23+
cpu_user_micros: 60,
24+
cpu_system_micros: 70,
25+
event_loop_lag_ms: 80,
26+
uptime_seconds: 90,
27+
timestamp_ms: 123,
28+
runtime: 'node',
29+
})
30+
const stopMonitoringSpy = vi
31+
.spyOn(WorkerMetricsCollector.prototype, 'stopMonitoring')
32+
.mockImplementation(() => {})
33+
34+
const meter = {
35+
createObservableGauge: vi.fn((name: string) => {
36+
const gauge = { name }
37+
gauges.push(gauge)
38+
return gauge
39+
}),
40+
addBatchObservableCallback: vi.fn((callback: typeof batchCallback) => {
41+
batchCallback = callback
42+
}),
43+
removeBatchObservableCallback: vi.fn(),
44+
}
45+
const batchResult = {
46+
observe: vi.fn(),
47+
}
48+
49+
registerWorkerGauges(meter as never, {
50+
workerId: 'worker-123',
51+
workerName: 'coverage-worker',
52+
})
53+
registerWorkerGauges(meter as never, {
54+
workerId: 'worker-ignored',
55+
})
56+
57+
expect(meter.createObservableGauge).toHaveBeenCalledTimes(9)
58+
expect(batchCallback).toBeTypeOf('function')
59+
60+
batchCallback?.(batchResult)
61+
62+
expect(batchResult.observe).toHaveBeenCalledTimes(9)
63+
expect(batchResult.observe).toHaveBeenCalledWith(
64+
gauges[0],
65+
10,
66+
expect.objectContaining({
67+
'worker.id': 'worker-123',
68+
'worker.name': 'coverage-worker',
69+
}),
70+
)
71+
72+
stopWorkerGauges()
73+
74+
expect(meter.removeBatchObservableCallback).toHaveBeenCalledOnce()
75+
expect(stopMonitoringSpy).toHaveBeenCalledOnce()
76+
})
77+
78+
it('skips undefined metrics and handles stop without prior registration', () => {
79+
const gauges: FakeGauge[] = []
80+
let batchCallback: ((result: { observe: (...args: unknown[]) => void }) => void) | undefined
81+
82+
vi.spyOn(WorkerMetricsCollector.prototype, 'collect').mockReturnValue({
83+
memory_heap_used: undefined,
84+
memory_heap_total: 20,
85+
memory_rss: undefined,
86+
memory_external: 40,
87+
cpu_percent: undefined,
88+
cpu_user_micros: 60,
89+
cpu_system_micros: undefined,
90+
event_loop_lag_ms: 80,
91+
uptime_seconds: undefined,
92+
timestamp_ms: 123,
93+
runtime: 'node',
94+
})
95+
96+
const meter = {
97+
createObservableGauge: vi.fn((name: string) => {
98+
const gauge = { name }
99+
gauges.push(gauge)
100+
return gauge
101+
}),
102+
addBatchObservableCallback: vi.fn((callback: typeof batchCallback) => {
103+
batchCallback = callback
104+
}),
105+
removeBatchObservableCallback: vi.fn(),
106+
}
107+
const batchResult = {
108+
observe: vi.fn(),
109+
}
110+
111+
stopWorkerGauges()
112+
registerWorkerGauges(meter as never, { workerId: 'worker-456' })
113+
114+
batchCallback?.(batchResult)
115+
116+
expect(batchResult.observe).toHaveBeenCalledTimes(4)
117+
expect(batchResult.observe).toHaveBeenCalledWith(gauges[1], 20, { 'worker.id': 'worker-456' })
118+
})
119+
})
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { performance } from 'node:perf_hooks'
2+
import { beforeEach, describe, expect, it, vi } from 'vitest'
3+
import { WorkerMetricsCollector } from '../src/worker-metrics'
4+
5+
describe('WorkerMetricsCollector', () => {
6+
beforeEach(() => {
7+
vi.restoreAllMocks()
8+
vi.clearAllMocks()
9+
})
10+
11+
it('collects memory and cpu metrics and caps cpu percent at 100', () => {
12+
const histogram = {
13+
mean: 8_000_000,
14+
reset: vi.fn(),
15+
disable: vi.fn(),
16+
}
17+
18+
vi.spyOn(Date, 'now').mockReturnValue(10_000)
19+
vi.spyOn(process, 'cpuUsage').mockReturnValue({ user: 401_000, system: 101_000 })
20+
vi.spyOn(process, 'memoryUsage').mockReturnValue({
21+
rss: 1_024,
22+
heapTotal: 2_048,
23+
heapUsed: 1_536,
24+
external: 256,
25+
arrayBuffers: 128,
26+
})
27+
vi.spyOn(performance, 'now').mockReturnValue(1_500)
28+
29+
const collector = new WorkerMetricsCollector({ eventLoopResolutionMs: 0 })
30+
;(collector as { lastCpuUsage: NodeJS.CpuUsage }).lastCpuUsage = { user: 1_000, system: 500 }
31+
;(collector as { lastCpuTime: number }).lastCpuTime = 1_000
32+
;(collector as { eventLoopHistogram: unknown }).eventLoopHistogram = histogram
33+
const metrics = collector.collect()
34+
35+
expect(histogram.reset).toHaveBeenCalledOnce()
36+
expect(metrics).toMatchObject({
37+
memory_rss: 1_024,
38+
memory_heap_total: 2_048,
39+
memory_heap_used: 1_536,
40+
memory_external: 256,
41+
cpu_user_micros: 401_000,
42+
cpu_system_micros: 101_000,
43+
cpu_percent: 100,
44+
event_loop_lag_ms: 8,
45+
uptime_seconds: 0,
46+
timestamp_ms: 10_000,
47+
runtime: 'node',
48+
})
49+
})
50+
51+
it('stops monitoring and clears the histogram reference', () => {
52+
const histogram = {
53+
mean: 0,
54+
reset: vi.fn(),
55+
disable: vi.fn(),
56+
}
57+
58+
vi.spyOn(Date, 'now').mockReturnValue(20_500)
59+
vi.spyOn(process, 'cpuUsage').mockReturnValue({ user: 1_000, system: 500 })
60+
vi.spyOn(process, 'memoryUsage').mockReturnValue({
61+
rss: 2_048,
62+
heapTotal: 4_096,
63+
heapUsed: 3_072,
64+
external: 512,
65+
arrayBuffers: 128,
66+
})
67+
vi.spyOn(performance, 'now').mockReturnValue(600)
68+
69+
const collector = new WorkerMetricsCollector({ eventLoopResolutionMs: 5.8 })
70+
;(collector as { lastCpuUsage: NodeJS.CpuUsage }).lastCpuUsage = { user: 500, system: 250 }
71+
;(collector as { lastCpuTime: number }).lastCpuTime = 100
72+
;(collector as { eventLoopHistogram: unknown }).eventLoopHistogram = histogram
73+
const metrics = collector.collect()
74+
collector.stopMonitoring()
75+
76+
expect(metrics.cpu_percent).toBeCloseTo(0.15)
77+
expect(metrics.event_loop_lag_ms).toBe(0)
78+
expect(histogram.disable).toHaveBeenCalledOnce()
79+
expect((collector as { eventLoopHistogram: unknown }).eventLoopHistogram).toBeNull()
80+
})
81+
})

packages/node/iii/vitest.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,18 @@ export default defineConfig({
66
testTimeout: 30000,
77
hookTimeout: 30000,
88
setupFiles: ['./tests/setup.ts'],
9+
coverage: {
10+
provider: 'v8',
11+
include: ['src/**/*.ts'],
12+
reporter: ['text', 'lcov'],
13+
reportsDirectory: './coverage',
14+
exclude: ['src/stream.ts', 'src/triggers.ts', 'src/types.ts'],
15+
thresholds: {
16+
lines: 70,
17+
functions: 70,
18+
branches: 70,
19+
statements: 70,
20+
},
21+
},
922
},
1023
})

0 commit comments

Comments
 (0)