mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 15:38:46 +00:00
Compare commits
2 Commits
main
...
claude/imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9336cb9ea | ||
|
|
bbf539be04 |
@@ -384,6 +384,7 @@ src/bun.js/bindings/webcore/MessagePortChannelRegistry.cpp
|
||||
src/bun.js/bindings/webcore/NetworkLoadMetrics.cpp
|
||||
src/bun.js/bindings/webcore/Performance.cpp
|
||||
src/bun.js/bindings/webcore/PerformanceEntry.cpp
|
||||
src/bun.js/bindings/webcore/PerformanceFunctionTiming.cpp
|
||||
src/bun.js/bindings/webcore/PerformanceMark.cpp
|
||||
src/bun.js/bindings/webcore/PerformanceMeasure.cpp
|
||||
src/bun.js/bindings/webcore/PerformanceObserver.cpp
|
||||
|
||||
@@ -66,6 +66,10 @@ size_t PerformanceEntry::memoryCost() const
|
||||
const PerformanceResourceTiming* resource = static_cast<const PerformanceResourceTiming*>(this);
|
||||
return resource->memoryCost() + baseCost;
|
||||
}
|
||||
case Type::Function: {
|
||||
// Function timing entries are created from JavaScript
|
||||
return sizeof(PerformanceEntry) + baseCost;
|
||||
}
|
||||
default: {
|
||||
return sizeof(PerformanceEntry) + baseCost;
|
||||
}
|
||||
@@ -85,6 +89,9 @@ std::optional<PerformanceEntry::Type> PerformanceEntry::parseEntryTypeString(con
|
||||
if (entryType == "resource"_s)
|
||||
return std::optional<Type>(Type::Resource);
|
||||
|
||||
if (entryType == "function"_s)
|
||||
return std::optional<Type>(Type::Function);
|
||||
|
||||
// if (DeprecatedGlobalSettings::paintTimingEnabled()) {
|
||||
// if (entryType == "paint"_s)
|
||||
// return std::optional<Type>(Type::Paint);
|
||||
|
||||
@@ -53,7 +53,8 @@ public:
|
||||
Mark = 1 << 1,
|
||||
Measure = 1 << 2,
|
||||
Resource = 1 << 3,
|
||||
Paint = 1 << 4
|
||||
Paint = 1 << 4,
|
||||
Function = 1 << 5
|
||||
};
|
||||
|
||||
virtual Type performanceEntryType() const = 0;
|
||||
|
||||
63
src/bun.js/bindings/webcore/PerformanceFunctionTiming.cpp
Normal file
63
src/bun.js/bindings/webcore/PerformanceFunctionTiming.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Jarred Sumner. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "PerformanceFunctionTiming.h"
|
||||
|
||||
#include "SerializedScriptValue.h"
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
PerformanceFunctionTiming::PerformanceFunctionTiming(const String& name, double startTime, double endTime, RefPtr<SerializedScriptValue>&& detail)
|
||||
: PerformanceEntry(name, startTime, endTime)
|
||||
, m_serializedDetail(WTFMove(detail))
|
||||
{
|
||||
}
|
||||
|
||||
PerformanceFunctionTiming::~PerformanceFunctionTiming() = default;
|
||||
|
||||
Ref<PerformanceFunctionTiming> PerformanceFunctionTiming::create(const String& name, double startTime, double endTime, RefPtr<SerializedScriptValue>&& detail)
|
||||
{
|
||||
return adoptRef(*new PerformanceFunctionTiming(name, startTime, endTime, WTFMove(detail)));
|
||||
}
|
||||
|
||||
JSC::JSValue PerformanceFunctionTiming::detail(JSC::JSGlobalObject& globalObject)
|
||||
{
|
||||
if (!m_serializedDetail)
|
||||
return JSC::jsNull();
|
||||
|
||||
return m_serializedDetail->deserialize(globalObject, &globalObject);
|
||||
}
|
||||
|
||||
size_t PerformanceFunctionTiming::memoryCost() const
|
||||
{
|
||||
size_t cost = sizeof(PerformanceFunctionTiming);
|
||||
if (m_serializedDetail)
|
||||
cost += m_serializedDetail->memoryCost();
|
||||
return cost;
|
||||
}
|
||||
|
||||
} // namespace WebCore
|
||||
59
src/bun.js/bindings/webcore/PerformanceFunctionTiming.h
Normal file
59
src/bun.js/bindings/webcore/PerformanceFunctionTiming.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Jarred Sumner. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PerformanceEntry.h"
|
||||
#include <wtf/Vector.h>
|
||||
#include <wtf/text/WTFString.h>
|
||||
|
||||
namespace JSC {
|
||||
class JSGlobalObject;
|
||||
class JSValue;
|
||||
}
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
class SerializedScriptValue;
|
||||
|
||||
class PerformanceFunctionTiming final : public PerformanceEntry {
|
||||
public:
|
||||
static Ref<PerformanceFunctionTiming> create(const String& name, double startTime, double endTime, RefPtr<SerializedScriptValue>&& detail);
|
||||
|
||||
JSC::JSValue detail(JSC::JSGlobalObject&);
|
||||
|
||||
size_t memoryCost() const;
|
||||
|
||||
private:
|
||||
PerformanceFunctionTiming(const String& name, double startTime, double endTime, RefPtr<SerializedScriptValue>&& detail);
|
||||
~PerformanceFunctionTiming();
|
||||
|
||||
Type performanceEntryType() const final { return Type::Function; }
|
||||
ASCIILiteral entryType() const final { return "function"_s; }
|
||||
|
||||
RefPtr<SerializedScriptValue> m_serializedDetail;
|
||||
};
|
||||
|
||||
} // namespace WebCore
|
||||
@@ -150,7 +150,8 @@ Vector<String> PerformanceObserver::supportedEntryTypes(ScriptExecutionContext&
|
||||
Vector<String> entryTypes = {
|
||||
"mark"_s,
|
||||
"measure"_s,
|
||||
"resource"_s
|
||||
"resource"_s,
|
||||
"function"_s
|
||||
};
|
||||
|
||||
// if (context.settingsValues().performanceNavigationTimingAPIEnabled)
|
||||
|
||||
@@ -119,6 +119,159 @@ class PerformanceResourceTiming {
|
||||
}
|
||||
$toClass(PerformanceResourceTiming, "PerformanceResourceTiming", PerformanceEntry);
|
||||
|
||||
// Store active function observers
|
||||
const functionObservers = new Set();
|
||||
|
||||
function processComplete(name: string, start: number, args: any[], histogram?: any) {
|
||||
const duration = performance.now() - start;
|
||||
|
||||
// Create performance entry matching Node.js structure
|
||||
const entry = {
|
||||
name,
|
||||
entryType: "function",
|
||||
startTime: start,
|
||||
duration,
|
||||
detail: args,
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.name,
|
||||
entryType: this.entryType,
|
||||
startTime: this.startTime,
|
||||
duration: this.duration,
|
||||
detail: this.detail,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// Add function arguments as indexed properties
|
||||
for (let n = 0; n < args.length; n++) {
|
||||
entry[n] = args[n];
|
||||
}
|
||||
|
||||
// Notify observers manually since we're creating entries from JS
|
||||
if (functionObservers.size > 0) {
|
||||
queueMicrotask(() => {
|
||||
for (const observer of functionObservers) {
|
||||
if (observer && observer._callback) {
|
||||
try {
|
||||
const list = {
|
||||
getEntries() {
|
||||
return [entry];
|
||||
},
|
||||
getEntriesByType(type: string) {
|
||||
return type === "function" ? [entry] : [];
|
||||
},
|
||||
getEntriesByName(name: string) {
|
||||
return entry.name === name ? [entry] : [];
|
||||
},
|
||||
};
|
||||
observer._callback(list, observer);
|
||||
} catch (err) {
|
||||
// Ignore errors in observer callbacks
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function timerify(fn: Function, options: { histogram?: any } = {}) {
|
||||
// Validate that fn is a function
|
||||
if (typeof fn !== "function") {
|
||||
throw $ERR_INVALID_ARG_TYPE("fn", "Function", fn);
|
||||
}
|
||||
|
||||
// Validate options
|
||||
if (options !== null && typeof options !== "object") {
|
||||
throw $ERR_INVALID_ARG_TYPE("options", "Object", options);
|
||||
}
|
||||
|
||||
const { histogram } = options;
|
||||
|
||||
// We're skipping histogram validation since we're not implementing that part
|
||||
// But keep the structure for compatibility
|
||||
if (histogram !== undefined) {
|
||||
// Just validate it exists and has a record method for now
|
||||
if (typeof histogram?.record !== "function") {
|
||||
throw $ERR_INVALID_ARG_TYPE("options.histogram", "RecordableHistogram", histogram);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the timerified function
|
||||
function timerified(this: any, ...args: any[]) {
|
||||
const isConstructorCall = new.target !== undefined;
|
||||
const start = performance.now();
|
||||
|
||||
let result;
|
||||
if (isConstructorCall) {
|
||||
// Use Reflect.construct for constructor calls
|
||||
// Pass the timerified function as new.target to maintain instanceof
|
||||
result = Reflect.construct(fn, args, timerified);
|
||||
} else {
|
||||
// Use $apply for regular function calls (Bun's internal apply)
|
||||
result = fn.$apply(this, args);
|
||||
}
|
||||
|
||||
// Handle async functions (promises)
|
||||
if (!isConstructorCall && result && typeof result.finally === "function") {
|
||||
// For promises, attach the processComplete to finally
|
||||
return result.finally(() => {
|
||||
processComplete(fn.name || "anonymous", start, args, histogram);
|
||||
});
|
||||
}
|
||||
|
||||
// For sync functions, process immediately
|
||||
processComplete(fn.name || "anonymous", start, args, histogram);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Define properties on the timerified function to match the original
|
||||
Object.defineProperties(timerified, {
|
||||
length: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
value: fn.length,
|
||||
},
|
||||
name: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
value: `timerified ${fn.name || "anonymous"}`,
|
||||
},
|
||||
});
|
||||
|
||||
// Copy prototype for constructor functions
|
||||
if (fn.prototype) {
|
||||
timerified.prototype = fn.prototype;
|
||||
}
|
||||
|
||||
return timerified;
|
||||
}
|
||||
|
||||
// Minimal wrapper to track function observers
|
||||
const OriginalPerformanceObserver = PerformanceObserver;
|
||||
class WrappedPerformanceObserver extends OriginalPerformanceObserver {
|
||||
_callback: Function;
|
||||
|
||||
constructor(callback: Function) {
|
||||
super(callback);
|
||||
this._callback = callback;
|
||||
}
|
||||
|
||||
observe(options: any) {
|
||||
if ((options.entryTypes && options.entryTypes.includes("function")) || options.type === "function") {
|
||||
functionObservers.add(this);
|
||||
}
|
||||
super.observe(options);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
functionObservers.delete(this);
|
||||
super.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceObserver = WrappedPerformanceObserver as any;
|
||||
|
||||
export default {
|
||||
performance: {
|
||||
mark(_) {
|
||||
@@ -154,6 +307,7 @@ export default {
|
||||
now: () => performance.now(),
|
||||
eventLoopUtilization: eventLoopUtilization,
|
||||
clearResourceTimings: function () {},
|
||||
timerify,
|
||||
},
|
||||
// performance: {
|
||||
// clearMarks: [Function: clearMarks],
|
||||
|
||||
288
test/js/node/perf_hooks/test-performance-timerify.test.ts
Normal file
288
test/js/node/perf_hooks/test-performance-timerify.test.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { performance, PerformanceObserver } from "perf_hooks";
|
||||
|
||||
describe("performance.timerify", () => {
|
||||
test("should wrap a function and measure its performance", done => {
|
||||
const obs = new PerformanceObserver(list => {
|
||||
const entries = list.getEntries();
|
||||
expect(entries.length).toBe(1);
|
||||
const entry = entries[0];
|
||||
expect(entry.name).toBe("noop");
|
||||
expect(entry.entryType).toBe("function");
|
||||
expect(typeof entry.duration).toBe("number");
|
||||
expect(typeof entry.startTime).toBe("number");
|
||||
obs.disconnect();
|
||||
done();
|
||||
});
|
||||
obs.observe({ entryTypes: ["function"] });
|
||||
|
||||
function noop() {}
|
||||
const timerified = performance.timerify(noop);
|
||||
timerified();
|
||||
});
|
||||
|
||||
test("should preserve function return value", () => {
|
||||
function returnsOne() {
|
||||
return 1;
|
||||
}
|
||||
const timerified = performance.timerify(returnsOne);
|
||||
expect(timerified()).toBe(1);
|
||||
});
|
||||
|
||||
test("should preserve arrow function return value", () => {
|
||||
const timerified = performance.timerify(() => 42);
|
||||
expect(timerified()).toBe(42);
|
||||
});
|
||||
|
||||
test("should handle constructor calls", done => {
|
||||
class TestClass {
|
||||
value: number;
|
||||
constructor(val: number) {
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
|
||||
const obs = new PerformanceObserver(list => {
|
||||
const entries = list.getEntries();
|
||||
expect(entries.length).toBe(1);
|
||||
const entry = entries[0];
|
||||
expect(entry.name).toBe("TestClass");
|
||||
expect(entry.entryType).toBe("function");
|
||||
expect(entry[0]).toBe(123); // First argument
|
||||
obs.disconnect();
|
||||
done();
|
||||
});
|
||||
obs.observe({ entryTypes: ["function"] });
|
||||
|
||||
const TimerifiedClass = performance.timerify(TestClass);
|
||||
const instance = new TimerifiedClass(123);
|
||||
expect(instance).toBeInstanceOf(TestClass);
|
||||
expect(instance.value).toBe(123);
|
||||
});
|
||||
|
||||
test("should capture function arguments in entry", done => {
|
||||
const obs = new PerformanceObserver(list => {
|
||||
const entries = list.getEntries();
|
||||
const entry = entries[0];
|
||||
expect(entry[0]).toBe(1);
|
||||
expect(entry[1]).toBe("abc");
|
||||
expect(entry[2]).toEqual({ x: 3 });
|
||||
obs.disconnect();
|
||||
done();
|
||||
});
|
||||
obs.observe({ entryTypes: ["function"] });
|
||||
|
||||
function testFunc(a: number, b: string, c: object) {
|
||||
return a;
|
||||
}
|
||||
const timerified = performance.timerify(testFunc);
|
||||
timerified(1, "abc", { x: 3 });
|
||||
});
|
||||
|
||||
test("should bubble up errors from wrapped function", () => {
|
||||
const obs = new PerformanceObserver(() => {
|
||||
throw new Error("Should not be called");
|
||||
});
|
||||
obs.observe({ entryTypes: ["function"] });
|
||||
|
||||
function throwsError() {
|
||||
throw new Error("test error");
|
||||
}
|
||||
const timerified = performance.timerify(throwsError);
|
||||
|
||||
expect(() => timerified()).toThrow("test error");
|
||||
obs.disconnect();
|
||||
});
|
||||
|
||||
test("should handle async functions", async () => {
|
||||
let observerCalled = false;
|
||||
const obs = new PerformanceObserver(list => {
|
||||
const entries = list.getEntries();
|
||||
expect(entries.length).toBe(1);
|
||||
const entry = entries[0];
|
||||
expect(entry.name).toBe("asyncFunc");
|
||||
expect(entry.entryType).toBe("function");
|
||||
expect(typeof entry.duration).toBe("number");
|
||||
expect(entry.duration).toBeGreaterThanOrEqual(50); // Should be at least 50ms
|
||||
observerCalled = true;
|
||||
obs.disconnect();
|
||||
});
|
||||
obs.observe({ entryTypes: ["function"] });
|
||||
|
||||
async function asyncFunc() {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
return "done";
|
||||
}
|
||||
|
||||
const timerified = performance.timerify(asyncFunc);
|
||||
const result = await timerified();
|
||||
expect(result).toBe("done");
|
||||
|
||||
// Wait a bit for the observer to be called
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
expect(observerCalled).toBe(true);
|
||||
});
|
||||
|
||||
test("should preserve function properties", () => {
|
||||
function original(a: number, b: string = "default") {
|
||||
return a;
|
||||
}
|
||||
const timerified = performance.timerify(original);
|
||||
|
||||
expect(timerified.length).toBe(original.length);
|
||||
expect(timerified.name).toBe("timerified original");
|
||||
});
|
||||
|
||||
test("should handle anonymous functions", () => {
|
||||
const timerified = performance.timerify(function () {
|
||||
return 1;
|
||||
});
|
||||
expect(timerified.name).toBe("timerified anonymous");
|
||||
expect(timerified()).toBe(1);
|
||||
});
|
||||
|
||||
test("should allow wrapping the same function multiple times", () => {
|
||||
function func() {}
|
||||
const timerified1 = performance.timerify(func);
|
||||
const timerified2 = performance.timerify(func);
|
||||
const timerified3 = performance.timerify(timerified1);
|
||||
|
||||
expect(timerified1).not.toBe(timerified2);
|
||||
expect(timerified1).not.toBe(timerified3);
|
||||
expect(timerified2).not.toBe(timerified3);
|
||||
expect(timerified3.name).toBe("timerified timerified func");
|
||||
});
|
||||
|
||||
test("should validate function argument", () => {
|
||||
const invalidInputs = [1, {}, [], null, undefined, "string", Infinity];
|
||||
for (const input of invalidInputs) {
|
||||
expect(() => performance.timerify(input as any)).toThrow(
|
||||
expect.objectContaining({
|
||||
code: "ERR_INVALID_ARG_TYPE",
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("should validate options argument", () => {
|
||||
function func() {}
|
||||
|
||||
// Should accept empty options
|
||||
expect(() => performance.timerify(func, {})).not.toThrow();
|
||||
|
||||
// Should accept undefined options
|
||||
expect(() => performance.timerify(func)).not.toThrow();
|
||||
|
||||
// Should reject non-object options
|
||||
expect(() => performance.timerify(func, "invalid" as any)).toThrow(
|
||||
expect.objectContaining({
|
||||
code: "ERR_INVALID_ARG_TYPE",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test("should validate histogram option", () => {
|
||||
function func() {}
|
||||
|
||||
// Invalid histogram types
|
||||
const invalidHistograms = [1, "", {}, [], false];
|
||||
for (const histogram of invalidHistograms) {
|
||||
expect(() => performance.timerify(func, { histogram })).toThrow(
|
||||
expect.objectContaining({
|
||||
code: "ERR_INVALID_ARG_TYPE",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Valid histogram (with record method)
|
||||
const validHistogram = { record: () => {} };
|
||||
expect(() => performance.timerify(func, { histogram: validHistogram })).not.toThrow();
|
||||
});
|
||||
|
||||
test("should preserve 'this' context", () => {
|
||||
const obj = {
|
||||
value: 42,
|
||||
getValue() {
|
||||
return this.value;
|
||||
},
|
||||
};
|
||||
|
||||
obj.getValue = performance.timerify(obj.getValue);
|
||||
expect(obj.getValue()).toBe(42);
|
||||
});
|
||||
|
||||
test("should work with class methods", done => {
|
||||
class MyClass {
|
||||
value = 100;
|
||||
|
||||
getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
const obs = new PerformanceObserver(list => {
|
||||
const entries = list.getEntries();
|
||||
expect(entries.length).toBe(1);
|
||||
expect(entries[0].name).toBe("getValue");
|
||||
obs.disconnect();
|
||||
done();
|
||||
});
|
||||
obs.observe({ entryTypes: ["function"] });
|
||||
|
||||
const instance = new MyClass();
|
||||
instance.getValue = performance.timerify(instance.getValue);
|
||||
expect(instance.getValue()).toBe(100);
|
||||
});
|
||||
|
||||
test("should handle functions that return promises", async () => {
|
||||
let observerCalled = false;
|
||||
const obs = new PerformanceObserver(list => {
|
||||
const entries = list.getEntries();
|
||||
expect(entries.length).toBe(1);
|
||||
expect(entries[0].name).toBe("returnsPromise");
|
||||
observerCalled = true;
|
||||
obs.disconnect();
|
||||
});
|
||||
obs.observe({ entryTypes: ["function"] });
|
||||
|
||||
function returnsPromise() {
|
||||
return Promise.resolve(123);
|
||||
}
|
||||
|
||||
const timerified = performance.timerify(returnsPromise);
|
||||
const result = await timerified();
|
||||
expect(result).toBe(123);
|
||||
|
||||
// Wait for observer
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
expect(observerCalled).toBe(true);
|
||||
});
|
||||
|
||||
test("should not call constructor as regular function", () => {
|
||||
class C {}
|
||||
const wrapped = performance.timerify(C);
|
||||
|
||||
expect(() => wrapped()).toThrow(TypeError);
|
||||
expect(new wrapped()).toBeInstanceOf(C);
|
||||
});
|
||||
|
||||
test("entry should have toJSON method", done => {
|
||||
const obs = new PerformanceObserver(list => {
|
||||
const entry = list.getEntries()[0];
|
||||
const json = entry.toJSON();
|
||||
expect(json).toHaveProperty("name", "func");
|
||||
expect(json).toHaveProperty("entryType", "function");
|
||||
expect(json).toHaveProperty("startTime");
|
||||
expect(json).toHaveProperty("duration");
|
||||
expect(json).toHaveProperty("detail");
|
||||
expect(Array.isArray(json.detail)).toBe(true);
|
||||
obs.disconnect();
|
||||
done();
|
||||
});
|
||||
obs.observe({ entryTypes: ["function"] });
|
||||
|
||||
function func(x: number) {}
|
||||
const timerified = performance.timerify(func);
|
||||
timerified(42);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user