File
Blob: src/workerd/api/node/tests/perf-hooks-nodejs-test.js
| 1 | // Copyright (c) 2025 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | import { |
| 5 | performance as perfHooksPerformance, |
| 6 | Performance, |
| 7 | PerformanceEntry, |
| 8 | PerformanceMark, |
| 9 | PerformanceMeasure, |
| 10 | PerformanceObserver, |
| 11 | PerformanceObserverEntryList, |
| 12 | PerformanceResourceTiming, |
| 13 | createHistogram, |
| 14 | monitorEventLoopDelay, |
| 15 | eventLoopUtilization, |
| 16 | timerify, |
| 17 | constants, |
| 18 | } from 'node:perf_hooks'; |
| 19 | import { |
| 20 | deepStrictEqual, |
| 21 | doesNotThrow, |
| 22 | ok, |
| 23 | throws, |
| 24 | strictEqual, |
| 25 | notStrictEqual, |
| 26 | } from 'node:assert'; |
| 27 | |
| 28 | export const testPerformanceExports = { |
| 29 | test() { |
| 30 | // Test that all expected exports exist |
| 31 | ok(performance); |
| 32 | ok(Performance); |
| 33 | ok(PerformanceEntry); |
| 34 | ok(PerformanceMark); |
| 35 | ok(PerformanceMeasure); |
| 36 | ok(PerformanceObserver); |
| 37 | ok(PerformanceObserverEntryList); |
| 38 | ok(PerformanceResourceTiming); |
| 39 | ok(createHistogram); |
| 40 | ok(monitorEventLoopDelay); |
| 41 | ok(eventLoopUtilization); |
| 42 | ok(timerify); |
| 43 | ok(constants); |
| 44 | |
| 45 | // Test that performance is an instance of Performance |
| 46 | ok(perfHooksPerformance instanceof Performance); |
| 47 | }, |
| 48 | }; |
| 49 | |
| 50 | export const testPerformanceConstants = { |
| 51 | test() { |
| 52 | deepStrictEqual(constants, { |
| 53 | // GC type constants |
| 54 | NODE_PERFORMANCE_GC_MAJOR: 4, |
| 55 | NODE_PERFORMANCE_GC_MINOR: 1, |
| 56 | NODE_PERFORMANCE_GC_INCREMENTAL: 8, |
| 57 | NODE_PERFORMANCE_GC_WEAKCB: 16, |
| 58 | |
| 59 | // GC flags constants |
| 60 | NODE_PERFORMANCE_GC_FLAGS_NO: 0, |
| 61 | NODE_PERFORMANCE_GC_FLAGS_CONSTRUCT_RETAINED: 2, |
| 62 | NODE_PERFORMANCE_GC_FLAGS_FORCED: 4, |
| 63 | NODE_PERFORMANCE_GC_FLAGS_SYNCHRONOUS_PHANTOM_PROCESSING: 8, |
| 64 | NODE_PERFORMANCE_GC_FLAGS_ALL_AVAILABLE_GARBAGE: 16, |
| 65 | NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY: 32, |
| 66 | NODE_PERFORMANCE_GC_FLAGS_SCHEDULE_IDLE: 64, |
| 67 | |
| 68 | // Entry type constants |
| 69 | NODE_PERFORMANCE_ENTRY_TYPE_GC: 0, |
| 70 | NODE_PERFORMANCE_ENTRY_TYPE_HTTP: 1, |
| 71 | NODE_PERFORMANCE_ENTRY_TYPE_HTTP2: 2, |
| 72 | NODE_PERFORMANCE_ENTRY_TYPE_NET: 3, |
| 73 | NODE_PERFORMANCE_ENTRY_TYPE_DNS: 4, |
| 74 | |
| 75 | // Milestone constants |
| 76 | NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN_TIMESTAMP: 0, |
| 77 | NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN: 1, |
| 78 | NODE_PERFORMANCE_MILESTONE_ENVIRONMENT: 2, |
| 79 | NODE_PERFORMANCE_MILESTONE_NODE_START: 3, |
| 80 | NODE_PERFORMANCE_MILESTONE_V8_START: 4, |
| 81 | NODE_PERFORMANCE_MILESTONE_LOOP_START: 5, |
| 82 | NODE_PERFORMANCE_MILESTONE_LOOP_EXIT: 6, |
| 83 | NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE: 7, |
| 84 | }); |
| 85 | ok(Object.isFrozen(constants)); |
| 86 | }, |
| 87 | }; |
| 88 | |
| 89 | export const testPerformanceBasicFunctionality = { |
| 90 | test() { |
| 91 | // Test that performance.now() works |
| 92 | const start = perfHooksPerformance.now(); |
| 93 | ok(typeof start === 'number'); |
| 94 | ok(start >= 0, 'start should be bigger than or equal to 0'); |
| 95 | |
| 96 | // Test that consecutive calls return increasing values |
| 97 | const end = perfHooksPerformance.now(); |
| 98 | ok(end >= start, 'end should be bigger than or equal to start'); |
| 99 | |
| 100 | // Test performance.timeOrigin |
| 101 | ok(typeof perfHooksPerformance.timeOrigin === 'number'); |
| 102 | ok( |
| 103 | perfHooksPerformance.timeOrigin >= 0, |
| 104 | 'timeOrigin should be bigger than or equal to 0' |
| 105 | ); |
| 106 | }, |
| 107 | }; |
| 108 | |
| 109 | export const testUnimplementedMethods = { |
| 110 | test() { |
| 111 | // These methods throw because they cannot be meaningfully stubbed |
| 112 | throws(() => createHistogram(), { message: /is not implemented/ }); |
| 113 | throws(() => monitorEventLoopDelay(), { |
| 114 | message: /is not implemented/, |
| 115 | }); |
| 116 | }, |
| 117 | }; |
| 118 | |
| 119 | export const testStandaloneEventLoopUtilization = { |
| 120 | test() { |
| 121 | // eventLoopUtilization returns stub values for compatibility |
| 122 | const result = eventLoopUtilization(); |
| 123 | ok(typeof result === 'object', 'should return an object'); |
| 124 | strictEqual(result.idle, 0, 'idle should be 0'); |
| 125 | strictEqual(result.active, 0, 'active should be 0'); |
| 126 | strictEqual(result.utilization, 0, 'utilization should be 0'); |
| 127 | |
| 128 | // Should accept optional parameters without error |
| 129 | const result2 = eventLoopUtilization(result); |
| 130 | ok( |
| 131 | typeof result2 === 'object', |
| 132 | 'should return an object with one argument' |
| 133 | ); |
| 134 | |
| 135 | const result3 = eventLoopUtilization(result, result2); |
| 136 | ok( |
| 137 | typeof result3 === 'object', |
| 138 | 'should return an object with two arguments' |
| 139 | ); |
| 140 | }, |
| 141 | }; |
| 142 | |
| 143 | export const testStandaloneTimerify = { |
| 144 | test() { |
| 145 | // timerify returns the function as-is (stub behavior) |
| 146 | const testFn = () => 42; |
| 147 | const timerified = timerify(testFn); |
| 148 | |
| 149 | strictEqual(timerified, testFn, 'timerify should return the same function'); |
| 150 | strictEqual(timerified(), 42, 'timerified function should work correctly'); |
| 151 | }, |
| 152 | }; |
| 153 | |
| 154 | export const testPerformanceNodeTiming = { |
| 155 | test() { |
| 156 | // nodeTiming is a Node.js-specific property available on both |
| 157 | // the perf_hooks performance export and globalThis.performance |
| 158 | ok( |
| 159 | perfHooksPerformance.nodeTiming, |
| 160 | 'nodeTiming should exist on perf_hooks' |
| 161 | ); |
| 162 | ok( |
| 163 | globalThis.performance.nodeTiming, |
| 164 | 'nodeTiming should exist on globalThis' |
| 165 | ); |
| 166 | |
| 167 | const nodeTiming = perfHooksPerformance.nodeTiming; |
| 168 | strictEqual(nodeTiming.name, 'node', 'name should be "node"'); |
| 169 | strictEqual(nodeTiming.entryType, 'node', 'entryType should be "node"'); |
| 170 | strictEqual( |
| 171 | typeof nodeTiming.startTime, |
| 172 | 'number', |
| 173 | 'startTime should be a number' |
| 174 | ); |
| 175 | strictEqual( |
| 176 | typeof nodeTiming.duration, |
| 177 | 'number', |
| 178 | 'duration should be a number' |
| 179 | ); |
| 180 | strictEqual( |
| 181 | typeof nodeTiming.nodeStart, |
| 182 | 'number', |
| 183 | 'nodeStart should be a number' |
| 184 | ); |
| 185 | strictEqual( |
| 186 | typeof nodeTiming.v8Start, |
| 187 | 'number', |
| 188 | 'v8Start should be a number' |
| 189 | ); |
| 190 | strictEqual( |
| 191 | typeof nodeTiming.bootstrapComplete, |
| 192 | 'number', |
| 193 | 'bootstrapComplete should be a number' |
| 194 | ); |
| 195 | strictEqual( |
| 196 | typeof nodeTiming.environment, |
| 197 | 'number', |
| 198 | 'environment should be a number' |
| 199 | ); |
| 200 | strictEqual( |
| 201 | typeof nodeTiming.loopStart, |
| 202 | 'number', |
| 203 | 'loopStart should be a number' |
| 204 | ); |
| 205 | strictEqual( |
| 206 | typeof nodeTiming.loopExit, |
| 207 | 'number', |
| 208 | 'loopExit should be a number' |
| 209 | ); |
| 210 | strictEqual( |
| 211 | typeof nodeTiming.idleTime, |
| 212 | 'number', |
| 213 | 'idleTime should be a number' |
| 214 | ); |
| 215 | |
| 216 | // uvMetricsInfo should return an object with libuv metrics (stub values) |
| 217 | ok( |
| 218 | typeof nodeTiming.uvMetricsInfo === 'object', |
| 219 | 'uvMetricsInfo should be an object' |
| 220 | ); |
| 221 | strictEqual( |
| 222 | nodeTiming.uvMetricsInfo.loopCount, |
| 223 | 0, |
| 224 | 'uvMetricsInfo.loopCount should be 0' |
| 225 | ); |
| 226 | strictEqual( |
| 227 | nodeTiming.uvMetricsInfo.events, |
| 228 | 0, |
| 229 | 'uvMetricsInfo.events should be 0' |
| 230 | ); |
| 231 | strictEqual( |
| 232 | nodeTiming.uvMetricsInfo.eventsWaiting, |
| 233 | 0, |
| 234 | 'uvMetricsInfo.eventsWaiting should be 0' |
| 235 | ); |
| 236 | |
| 237 | // Verify that nodeTiming properties are instance (own) properties, not prototype properties |
| 238 | // This matches Node.js behavior where Reflect.ownKeys(performance.nodeTiming) includes |
| 239 | // all properties like nodeStart, v8Start, etc. |
| 240 | const ownKeys = Reflect.ownKeys(nodeTiming); |
| 241 | ok(ownKeys.includes('nodeStart'), 'nodeStart should be an own property'); |
| 242 | ok(ownKeys.includes('v8Start'), 'v8Start should be an own property'); |
| 243 | ok( |
| 244 | ownKeys.includes('bootstrapComplete'), |
| 245 | 'bootstrapComplete should be an own property' |
| 246 | ); |
| 247 | ok( |
| 248 | ownKeys.includes('environment'), |
| 249 | 'environment should be an own property' |
| 250 | ); |
| 251 | ok(ownKeys.includes('loopStart'), 'loopStart should be an own property'); |
| 252 | ok(ownKeys.includes('loopExit'), 'loopExit should be an own property'); |
| 253 | ok(ownKeys.includes('idleTime'), 'idleTime should be an own property'); |
| 254 | ok( |
| 255 | ownKeys.includes('uvMetricsInfo'), |
| 256 | 'uvMetricsInfo should be an own property' |
| 257 | ); |
| 258 | |
| 259 | // Verify prototype only has constructor and toJSON (matching Node.js behavior) |
| 260 | const protoKeys = Reflect.ownKeys(Object.getPrototypeOf(nodeTiming)); |
| 261 | ok(protoKeys.includes('constructor'), 'prototype should have constructor'); |
| 262 | ok(protoKeys.includes('toJSON'), 'prototype should have toJSON'); |
| 263 | |
| 264 | // toJSON should work and include uvMetricsInfo |
| 265 | ok(typeof nodeTiming.toJSON === 'function', 'toJSON should be a function'); |
| 266 | const json = nodeTiming.toJSON(); |
| 267 | ok(typeof json === 'object', 'toJSON should return an object'); |
| 268 | ok( |
| 269 | typeof json.uvMetricsInfo === 'object', |
| 270 | 'toJSON should include uvMetricsInfo' |
| 271 | ); |
| 272 | }, |
| 273 | }; |
| 274 | |
| 275 | export const testPerformanceEventLoopUtilization = { |
| 276 | test() { |
| 277 | // performance.eventLoopUtilization() should return stub values (not throw) |
| 278 | const result = perfHooksPerformance.eventLoopUtilization(); |
| 279 | ok(typeof result === 'object', 'should return an object'); |
| 280 | strictEqual(result.idle, 0, 'idle should be 0'); |
| 281 | strictEqual(result.active, 0, 'active should be 0'); |
| 282 | strictEqual(result.utilization, 0, 'utilization should be 0'); |
| 283 | }, |
| 284 | }; |
| 285 | |
| 286 | export const testPerformanceTimerify = { |
| 287 | test() { |
| 288 | // performance.timerify() should return the function as-is (stub behavior) |
| 289 | const testFn = () => 'test'; |
| 290 | const timerified = perfHooksPerformance.timerify(testFn); |
| 291 | |
| 292 | strictEqual(timerified, testFn, 'timerify should return the same function'); |
| 293 | strictEqual( |
| 294 | timerified(), |
| 295 | 'test', |
| 296 | 'timerified function should work correctly' |
| 297 | ); |
| 298 | }, |
| 299 | }; |
| 300 | |
| 301 | export const testPerformanceMarkResourceTiming = { |
| 302 | test() { |
| 303 | // markResourceTiming should not throw (no-op stub) |
| 304 | perfHooksPerformance.markResourceTiming(); |
| 305 | // If we get here without throwing, the test passes |
| 306 | ok(true, 'markResourceTiming should not throw'); |
| 307 | }, |
| 308 | }; |
| 309 | |
| 310 | export const testPerformanceMark = { |
| 311 | test() { |
| 312 | perfHooksPerformance.clearMarks(); |
| 313 | |
| 314 | const mark1 = perfHooksPerformance.mark('test-mark-1'); |
| 315 | ok( |
| 316 | mark1 instanceof PerformanceMark, |
| 317 | 'mark should return a PerformanceMark instance' |
| 318 | ); |
| 319 | strictEqual(mark1.name, 'test-mark-1', 'mark name should match'); |
| 320 | strictEqual(mark1.entryType, 'mark', 'entryType should be "mark"'); |
| 321 | ok(typeof mark1.startTime === 'number', 'startTime should be a number'); |
| 322 | strictEqual(mark1.duration, 0, 'duration should be 0 for marks'); |
| 323 | |
| 324 | const customTime = 100; |
| 325 | const mark2 = perfHooksPerformance.mark('test-mark-2', { |
| 326 | startTime: customTime, |
| 327 | }); |
| 328 | strictEqual(mark2.startTime, customTime, 'custom startTime should be set'); |
| 329 | |
| 330 | const detail = { key: 'value' }; |
| 331 | const mark3 = perfHooksPerformance.mark('test-mark-3', { detail }); |
| 332 | ok(mark3.detail, 'detail should be set'); |
| 333 | deepStrictEqual(mark3.detail, detail, 'detail should match'); |
| 334 | |
| 335 | const entries = perfHooksPerformance.getEntries(); |
| 336 | ok(entries.length >= 3, 'should have at least 3 entries'); |
| 337 | |
| 338 | const marks = perfHooksPerformance.getEntriesByType('mark'); |
| 339 | ok(marks.length >= 3, 'should have at least 3 marks'); |
| 340 | |
| 341 | perfHooksPerformance.clearMarks(); |
| 342 | }, |
| 343 | }; |
| 344 | |
| 345 | export const testPerformanceMeasure = { |
| 346 | test() { |
| 347 | perfHooksPerformance.clearMarks(); |
| 348 | perfHooksPerformance.clearMeasures(); |
| 349 | |
| 350 | const mark1 = perfHooksPerformance.mark('start-mark'); |
| 351 | const _mark2 = perfHooksPerformance.mark('end-mark'); |
| 352 | |
| 353 | const measure1 = perfHooksPerformance.measure( |
| 354 | 'test-measure-1', |
| 355 | 'start-mark', |
| 356 | 'end-mark' |
| 357 | ); |
| 358 | ok( |
| 359 | measure1 instanceof PerformanceMeasure, |
| 360 | 'measure should return a PerformanceMeasure instance' |
| 361 | ); |
| 362 | strictEqual(measure1.name, 'test-measure-1', 'measure name should match'); |
| 363 | strictEqual(measure1.entryType, 'measure', 'entryType should be "measure"'); |
| 364 | ok(typeof measure1.startTime === 'number', 'startTime should be a number'); |
| 365 | ok(typeof measure1.duration === 'number', 'duration should be a number'); |
| 366 | strictEqual( |
| 367 | measure1.startTime, |
| 368 | mark1.startTime, |
| 369 | 'measure startTime should match start mark' |
| 370 | ); |
| 371 | strictEqual( |
| 372 | measure1.detail, |
| 373 | null, |
| 374 | 'detail should be null when using string marks' |
| 375 | ); |
| 376 | |
| 377 | const measure2 = perfHooksPerformance.measure('test-measure-2', { |
| 378 | start: 50, |
| 379 | end: 150, |
| 380 | }); |
| 381 | strictEqual( |
| 382 | measure2.startTime, |
| 383 | 50, |
| 384 | 'measure with options should have correct startTime' |
| 385 | ); |
| 386 | strictEqual( |
| 387 | measure2.duration, |
| 388 | 100, |
| 389 | 'measure with options should have correct duration' |
| 390 | ); |
| 391 | strictEqual( |
| 392 | measure2.detail, |
| 393 | null, |
| 394 | 'detail should be null when not provided in options' |
| 395 | ); |
| 396 | |
| 397 | const measure3 = perfHooksPerformance.measure('test-measure-3', { |
| 398 | start: 100, |
| 399 | duration: 50, |
| 400 | }); |
| 401 | strictEqual( |
| 402 | measure3.startTime, |
| 403 | 100, |
| 404 | 'measure with duration should have correct startTime' |
| 405 | ); |
| 406 | strictEqual( |
| 407 | measure3.duration, |
| 408 | 50, |
| 409 | 'measure with duration should have correct duration' |
| 410 | ); |
| 411 | strictEqual( |
| 412 | measure3.detail, |
| 413 | null, |
| 414 | 'detail should be null when not provided in options' |
| 415 | ); |
| 416 | |
| 417 | const customDetail = { customKey: 'customValue' }; |
| 418 | const measure4 = perfHooksPerformance.measure('test-measure-4', { |
| 419 | start: 200, |
| 420 | end: 300, |
| 421 | detail: customDetail, |
| 422 | }); |
| 423 | ok(measure4.detail, 'measure should have detail'); |
| 424 | deepStrictEqual(measure4.detail, customDetail, 'detail should match'); |
| 425 | |
| 426 | const measures = perfHooksPerformance.getEntriesByType('measure'); |
| 427 | ok(measures.length >= 4, 'should have at least 4 measures'); |
| 428 | |
| 429 | perfHooksPerformance.clearMarks(); |
| 430 | perfHooksPerformance.clearMeasures(); |
| 431 | }, |
| 432 | }; |
| 433 | |
| 434 | export const testPerformanceMeasureNameOnly = { |
| 435 | test() { |
| 436 | perfHooksPerformance.clearMarks(); |
| 437 | perfHooksPerformance.clearMeasures(); |
| 438 | |
| 439 | const before = perfHooksPerformance.now(); |
| 440 | const measure = perfHooksPerformance.measure('name-only-measure'); |
| 441 | const after = perfHooksPerformance.now(); |
| 442 | |
| 443 | ok( |
| 444 | measure instanceof PerformanceMeasure, |
| 445 | 'measure should return a PerformanceMeasure instance' |
| 446 | ); |
| 447 | strictEqual(measure.name, 'name-only-measure', 'measure name should match'); |
| 448 | strictEqual(measure.entryType, 'measure', 'entryType should be "measure"'); |
| 449 | strictEqual(measure.startTime, 0, 'startTime should be 0 (timeOrigin)'); |
| 450 | ok( |
| 451 | measure.duration >= before && measure.duration <= after, |
| 452 | 'duration should be approximately the current time' |
| 453 | ); |
| 454 | strictEqual( |
| 455 | measure.detail, |
| 456 | null, |
| 457 | 'detail should be null when not provided' |
| 458 | ); |
| 459 | |
| 460 | perfHooksPerformance.clearMeasures(); |
| 461 | }, |
| 462 | }; |
| 463 | |
| 464 | export const testGetEntriesByName = { |
| 465 | test() { |
| 466 | perfHooksPerformance.clearMarks(); |
| 467 | perfHooksPerformance.clearMeasures(); |
| 468 | |
| 469 | perfHooksPerformance.mark('test-name'); |
| 470 | perfHooksPerformance.mark('test-name'); |
| 471 | perfHooksPerformance.mark('other-name'); |
| 472 | perfHooksPerformance.measure('test-name', { start: 0, end: 100 }); |
| 473 | |
| 474 | const entriesByName = perfHooksPerformance.getEntriesByName('test-name'); |
| 475 | ok(Array.isArray(entriesByName), 'getEntriesByName should return an array'); |
| 476 | strictEqual( |
| 477 | entriesByName.length, |
| 478 | 3, |
| 479 | 'should have 3 entries with name "test-name"' |
| 480 | ); |
| 481 | |
| 482 | const marksByName = perfHooksPerformance.getEntriesByName( |
| 483 | 'test-name', |
| 484 | 'mark' |
| 485 | ); |
| 486 | strictEqual( |
| 487 | marksByName.length, |
| 488 | 2, |
| 489 | 'should have 2 marks with name "test-name"' |
| 490 | ); |
| 491 | |
| 492 | const measuresByName = perfHooksPerformance.getEntriesByName( |
| 493 | 'test-name', |
| 494 | 'measure' |
| 495 | ); |
| 496 | strictEqual( |
| 497 | measuresByName.length, |
| 498 | 1, |
| 499 | 'should have 1 measure with name "test-name"' |
| 500 | ); |
| 501 | |
| 502 | const noEntries = perfHooksPerformance.getEntriesByName('non-existent'); |
| 503 | ok( |
| 504 | Array.isArray(noEntries), |
| 505 | 'should return empty array for non-existent name' |
| 506 | ); |
| 507 | strictEqual(noEntries.length, 0, 'should have 0 entries'); |
| 508 | |
| 509 | perfHooksPerformance.clearMarks(); |
| 510 | perfHooksPerformance.clearMeasures(); |
| 511 | }, |
| 512 | }; |
| 513 | |
| 514 | export const testClearMethods = { |
| 515 | test() { |
| 516 | perfHooksPerformance.mark('mark-to-keep'); |
| 517 | perfHooksPerformance.mark('mark-to-remove'); |
| 518 | perfHooksPerformance.mark('mark-to-remove'); |
| 519 | perfHooksPerformance.mark('another-mark'); |
| 520 | |
| 521 | perfHooksPerformance.clearMarks('mark-to-remove'); |
| 522 | const marksAfterClear = perfHooksPerformance.getEntriesByType('mark'); |
| 523 | strictEqual( |
| 524 | marksAfterClear.length, |
| 525 | 2, |
| 526 | 'should have 2 marks after clearing specific name' |
| 527 | ); |
| 528 | ok( |
| 529 | marksAfterClear.every((m) => m.name !== 'mark-to-remove'), |
| 530 | 'should not have removed marks' |
| 531 | ); |
| 532 | ok( |
| 533 | marksAfterClear.some((m) => m.name === 'mark-to-keep'), |
| 534 | 'should have kept other marks' |
| 535 | ); |
| 536 | |
| 537 | perfHooksPerformance.clearMarks(); |
| 538 | const allMarksCleared = perfHooksPerformance.getEntriesByType('mark'); |
| 539 | strictEqual( |
| 540 | allMarksCleared.length, |
| 541 | 0, |
| 542 | 'should have 0 marks after clearing all' |
| 543 | ); |
| 544 | |
| 545 | perfHooksPerformance.measure('measure-to-keep', { start: 0, end: 100 }); |
| 546 | perfHooksPerformance.measure('measure-to-remove', { start: 100, end: 200 }); |
| 547 | perfHooksPerformance.measure('measure-to-remove', { start: 200, end: 300 }); |
| 548 | |
| 549 | perfHooksPerformance.clearMeasures('measure-to-remove'); |
| 550 | const measuresAfterClear = perfHooksPerformance.getEntriesByType('measure'); |
| 551 | strictEqual( |
| 552 | measuresAfterClear.length, |
| 553 | 1, |
| 554 | 'should have 1 measure after clearing specific name' |
| 555 | ); |
| 556 | strictEqual( |
| 557 | measuresAfterClear[0].name, |
| 558 | 'measure-to-keep', |
| 559 | 'should have kept the right measure' |
| 560 | ); |
| 561 | |
| 562 | perfHooksPerformance.clearMeasures(); |
| 563 | const allMeasuresCleared = perfHooksPerformance.getEntriesByType('measure'); |
| 564 | strictEqual( |
| 565 | allMeasuresCleared.length, |
| 566 | 0, |
| 567 | 'should have 0 measures after clearing all' |
| 568 | ); |
| 569 | |
| 570 | perfHooksPerformance.clearResourceTimings(); |
| 571 | const resourceTimings = perfHooksPerformance.getEntriesByType('resource'); |
| 572 | strictEqual(resourceTimings.length, 0, 'should have 0 resource timings'); |
| 573 | }, |
| 574 | }; |
| 575 | |
| 576 | export const testPerformanceEntryTypes = { |
| 577 | test() { |
| 578 | const allEntries = perfHooksPerformance.getEntries(); |
| 579 | ok(Array.isArray(allEntries)); |
| 580 | |
| 581 | const markEntries = perfHooksPerformance.getEntriesByType('mark'); |
| 582 | ok(Array.isArray(markEntries)); |
| 583 | |
| 584 | const measureEntries = perfHooksPerformance.getEntriesByType('measure'); |
| 585 | ok(Array.isArray(measureEntries)); |
| 586 | |
| 587 | perfHooksPerformance.clearMarks(); |
| 588 | perfHooksPerformance.clearMeasures(); |
| 589 | perfHooksPerformance.clearResourceTimings(); |
| 590 | }, |
| 591 | }; |
| 592 | |
| 593 | export const testEventCounts = { |
| 594 | test() { |
| 595 | const eventCounts = perfHooksPerformance.eventCounts; |
| 596 | ok(eventCounts, 'eventCounts should exist'); |
| 597 | ok(typeof eventCounts === 'object', 'eventCounts should be an object'); |
| 598 | |
| 599 | ok( |
| 600 | typeof eventCounts.get === 'function', |
| 601 | 'eventCounts should have get method' |
| 602 | ); |
| 603 | ok( |
| 604 | typeof eventCounts.has === 'function', |
| 605 | 'eventCounts should have has method' |
| 606 | ); |
| 607 | ok( |
| 608 | typeof eventCounts.entries === 'function', |
| 609 | 'eventCounts should have entries method' |
| 610 | ); |
| 611 | ok( |
| 612 | typeof eventCounts.keys === 'function', |
| 613 | 'eventCounts should have keys method' |
| 614 | ); |
| 615 | ok( |
| 616 | typeof eventCounts.values === 'function', |
| 617 | 'eventCounts should have values method' |
| 618 | ); |
| 619 | ok( |
| 620 | typeof eventCounts.forEach === 'function', |
| 621 | 'eventCounts should have forEach method' |
| 622 | ); |
| 623 | |
| 624 | ok('size' in eventCounts, 'eventCounts should have size property'); |
| 625 | ok(typeof eventCounts.size === 'number', 'size should be a number'); |
| 626 | ok(eventCounts.size >= 0, 'size should be non-negative'); |
| 627 | strictEqual(eventCounts.size, 0, 'size should be 0 for empty EventCounts'); |
| 628 | |
| 629 | ok(!('set' in eventCounts), 'eventCounts should not have set method'); |
| 630 | ok(!('delete' in eventCounts), 'eventCounts should not have delete method'); |
| 631 | ok(!('clear' in eventCounts), 'eventCounts should not have clear method'); |
| 632 | |
| 633 | const testKeys = [ |
| 634 | 'click', |
| 635 | 'keydown', |
| 636 | '', |
| 637 | 'special-chars!@#', |
| 638 | '123', |
| 639 | 'undefined', |
| 640 | 'null', |
| 641 | ]; |
| 642 | for (const key of testKeys) { |
| 643 | const result = eventCounts.get(key); |
| 644 | strictEqual( |
| 645 | result, |
| 646 | undefined, |
| 647 | `get('${key}') should return undefined for non-existent keys` |
| 648 | ); |
| 649 | |
| 650 | const hasResult = eventCounts.has(key); |
| 651 | strictEqual( |
| 652 | hasResult, |
| 653 | false, |
| 654 | `has('${key}') should return false for non-existent keys` |
| 655 | ); |
| 656 | } |
| 657 | |
| 658 | ok( |
| 659 | typeof eventCounts[Symbol.iterator] === 'function', |
| 660 | 'eventCounts should be iterable' |
| 661 | ); |
| 662 | |
| 663 | const entriesIter = eventCounts.entries(); |
| 664 | ok( |
| 665 | entriesIter && typeof entriesIter.next === 'function', |
| 666 | 'entries() should return an iterator' |
| 667 | ); |
| 668 | ok( |
| 669 | typeof entriesIter[Symbol.iterator] === 'function', |
| 670 | 'entries iterator should be iterable' |
| 671 | ); |
| 672 | const entriesResult = entriesIter.next(); |
| 673 | strictEqual( |
| 674 | entriesResult.done, |
| 675 | true, |
| 676 | 'entries iterator should be done immediately (empty map)' |
| 677 | ); |
| 678 | strictEqual( |
| 679 | entriesResult.value, |
| 680 | undefined, |
| 681 | 'entries iterator value should be undefined when done' |
| 682 | ); |
| 683 | |
| 684 | const keysIter = eventCounts.keys(); |
| 685 | ok( |
| 686 | keysIter && typeof keysIter.next === 'function', |
| 687 | 'keys() should return an iterator' |
| 688 | ); |
| 689 | ok( |
| 690 | typeof keysIter[Symbol.iterator] === 'function', |
| 691 | 'keys iterator should be iterable' |
| 692 | ); |
| 693 | const keysResult = keysIter.next(); |
| 694 | strictEqual( |
| 695 | keysResult.done, |
| 696 | true, |
| 697 | 'keys iterator should be done immediately (empty map)' |
| 698 | ); |
| 699 | strictEqual( |
| 700 | keysResult.value, |
| 701 | undefined, |
| 702 | 'keys iterator value should be undefined when done' |
| 703 | ); |
| 704 | |
| 705 | const valuesIter = eventCounts.values(); |
| 706 | ok( |
| 707 | valuesIter && typeof valuesIter.next === 'function', |
| 708 | 'values() should return an iterator' |
| 709 | ); |
| 710 | ok( |
| 711 | typeof valuesIter[Symbol.iterator] === 'function', |
| 712 | 'values iterator should be iterable' |
| 713 | ); |
| 714 | const valuesResult = valuesIter.next(); |
| 715 | strictEqual( |
| 716 | valuesResult.done, |
| 717 | true, |
| 718 | 'values iterator should be done immediately (empty map)' |
| 719 | ); |
| 720 | strictEqual( |
| 721 | valuesResult.value, |
| 722 | undefined, |
| 723 | 'values iterator value should be undefined when done' |
| 724 | ); |
| 725 | |
| 726 | let forEachCalled = false; |
| 727 | let forEachContext = null; |
| 728 | const customThis = { custom: true }; |
| 729 | |
| 730 | eventCounts.forEach(function () { |
| 731 | forEachCalled = true; |
| 732 | }); |
| 733 | ok(!forEachCalled, 'forEach should not call callback when map is empty'); |
| 734 | |
| 735 | eventCounts.forEach(function () { |
| 736 | // eslint-disable-next-line @typescript-eslint/no-this-alias |
| 737 | forEachContext = this; |
| 738 | }, customThis); |
| 739 | strictEqual( |
| 740 | forEachContext, |
| 741 | null, |
| 742 | 'forEach with thisArg should not be called for empty map' |
| 743 | ); |
| 744 | |
| 745 | const entries = []; |
| 746 | for (const entry of eventCounts) { |
| 747 | entries.push(entry); |
| 748 | } |
| 749 | ok(Array.isArray(entries), 'Should be able to iterate with for...of'); |
| 750 | strictEqual(entries.length, 0, 'Should have no entries currently'); |
| 751 | |
| 752 | const spreadEntries = [...eventCounts]; |
| 753 | ok(Array.isArray(spreadEntries), 'Should be able to use spread operator'); |
| 754 | strictEqual(spreadEntries.length, 0, 'Spread should produce empty array'); |
| 755 | |
| 756 | const spreadKeys = [...eventCounts.keys()]; |
| 757 | ok(Array.isArray(spreadKeys), 'Should be able to spread keys'); |
| 758 | strictEqual(spreadKeys.length, 0, 'Keys spread should produce empty array'); |
| 759 | |
| 760 | const spreadValues = [...eventCounts.values()]; |
| 761 | ok(Array.isArray(spreadValues), 'Should be able to spread values'); |
| 762 | strictEqual( |
| 763 | spreadValues.length, |
| 764 | 0, |
| 765 | 'Values spread should produce empty array' |
| 766 | ); |
| 767 | |
| 768 | const fromEntries = Array.from(eventCounts); |
| 769 | ok(Array.isArray(fromEntries), 'Array.from should work with eventCounts'); |
| 770 | strictEqual(fromEntries.length, 0, 'Array.from should produce empty array'); |
| 771 | |
| 772 | const eventCounts2 = perfHooksPerformance.eventCounts; |
| 773 | ok( |
| 774 | eventCounts2, |
| 775 | 'Second call to performance.eventCounts should also return an object' |
| 776 | ); |
| 777 | }, |
| 778 | }; |
| 779 | |
| 780 | export const testPerformanceMarkToJSON = { |
| 781 | test() { |
| 782 | const mark1 = perfHooksPerformance.mark('test-mark-json'); |
| 783 | const json1 = mark1.toJSON(); |
| 784 | strictEqual(json1.name, 'test-mark-json'); |
| 785 | strictEqual(json1.entryType, 'mark'); |
| 786 | strictEqual(json1.duration, 0); |
| 787 | ok(!('detail' in json1)); |
| 788 | |
| 789 | const detail = { key: 'value' }; |
| 790 | const mark2 = perfHooksPerformance.mark('test-mark-json-2', { detail }); |
| 791 | const json2 = mark2.toJSON(); |
| 792 | deepStrictEqual(json2.detail, detail); |
| 793 | }, |
| 794 | }; |
| 795 | |
| 796 | export const testPerformanceMeasureToJSON = { |
| 797 | test() { |
| 798 | perfHooksPerformance.mark('start'); |
| 799 | perfHooksPerformance.mark('end'); |
| 800 | const measure1 = perfHooksPerformance.measure('test-json', 'start', 'end'); |
| 801 | const json1 = measure1.toJSON(); |
| 802 | strictEqual(json1.name, 'test-json'); |
| 803 | strictEqual(json1.entryType, 'measure'); |
| 804 | // detail should not be in JSON when not provided (it's null) |
| 805 | ok(!('detail' in json1), 'detail should not be in JSON when not provided'); |
| 806 | |
| 807 | const customDetail = { value: 42 }; |
| 808 | const measure2 = perfHooksPerformance.measure('test-json-2', { |
| 809 | start: 50, |
| 810 | end: 150, |
| 811 | detail: customDetail, |
| 812 | }); |
| 813 | const json2 = measure2.toJSON(); |
| 814 | deepStrictEqual(json2.detail, customDetail); |
| 815 | }, |
| 816 | }; |
| 817 | |
| 818 | export const testPerformanceToJSON = { |
| 819 | test() { |
| 820 | const json = perfHooksPerformance.toJSON(); |
| 821 | strictEqual(typeof json, 'object'); |
| 822 | strictEqual(json.timeOrigin, 0); |
| 823 | // toJSON should not include other properties beyond timeOrigin |
| 824 | deepStrictEqual(Object.keys(json), ['timeOrigin']); |
| 825 | }, |
| 826 | }; |
| 827 | |
| 828 | export const testSetResourceTimingBufferSize = { |
| 829 | test() { |
| 830 | doesNotThrow(() => perfHooksPerformance.setResourceTimingBufferSize(0)); |
| 831 | }, |
| 832 | }; |
| 833 | |
| 834 | export const testGlobalThis = { |
| 835 | async test() { |
| 836 | notStrictEqual(typeof globalThis.performance, 'undefined'); |
| 837 | ok('addEventListener' in globalThis.performance); |
| 838 | notStrictEqual(typeof globalThis.Performance, 'undefined'); |
| 839 | }, |
| 840 | }; |