Skip to content
File

Blob: src/workerd/api/node/tests/perf-hooks-nodejs-test.js

javascript841 lines
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
4import {
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';
19import {
20 deepStrictEqual,
21 doesNotThrow,
22 ok,
23 throws,
24 strictEqual,
25 notStrictEqual,
26} from 'node:assert';
27 
28export 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 
50export 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 
89export 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 
109export 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 
119export 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 
143export 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 
154export 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 
275export 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 
286export 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 
301export 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 
310export 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 
345export 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 
434export 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 
464export 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 
514export 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 
576export 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 
593export 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 
780export 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 
796export 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 
818export 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 
828export const testSetResourceTimingBufferSize = {
829 test() {
830 doesNotThrow(() => perfHooksPerformance.setResourceTimingBufferSize(0));
831 },
832};
833 
834export 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};