rc-js-util
Version:
A collection of TS and C++ utilities to help writing performant and correct applications, achieved through strict typing and (removable) invariant checking.
106 lines (88 loc) • 4.2 kB
text/typescript
import { ReferenceCountedNode } from "../../lifecycle/reference-counted-node.js";
import { lifecycleStack } from "./lifecycle-stack.js";
import { IManagedObject, type IManagedResourceNode } from "../../lifecycle/manged-resources.js";
import { ReferenceCountedStrategy } from "./reference-counted-strategy.js";
import { _Debug } from "../../debug/_debug.js";
import { SanitizedEmscriptenTestModule } from "./sanitized-emscripten-test-module.js";
import { getTestModuleOptions, TestGarbageCollector } from "../../test-util/test-utils.js";
import utilTestModule from "../../external/test-module.mjs";
import { SharedArray } from "../shared-array/shared-array.js";
import type { IJsUtilBindings } from "../i-js-util-bindings.js";
import type { IEmscriptenWrapper } from "./i-emscripten-wrapper.js";
import type { IInteropBindings } from "./i-interop-bindings.js";
describe("=> ReferenceCountedStrategy", () =>
{
let strategy: ReferenceCountedStrategy;
beforeEach(() =>
{
strategy = new ReferenceCountedStrategy();
});
it("| creates nodes bound to the last block scope", () =>
{
const blockNode = new ReferenceCountedNode();
spyOn(lifecycleStack, "getTop").and.returnValue(blockNode);
spyOn(lifecycleStack, "push").and.returnValue(blockNode);
spyOn(blockNode.getLinked(), "link").and.callThrough();
const node = strategy.createNode(null);
expect(blockNode.getLinked().link).toHaveBeenCalledWith(node);
expect(blockNode.getLinked().isLinkedTo(node)).toBe(true);
blockNode.getLinked().unlinkAll();
expect(node.getIsDestroyed()).toBe(true);
});
it("| creates root nodes", () =>
{
const rootNode = strategy.createRootNode();
expect(rootNode.getIsDestroyed()).toBe(false);
expect(rootNode.getLinked().getLinkedNodes().length).toBe(0);
});
it("| provides finalization checks in debug for pointers", async () =>
{
if (!_BUILD.DEBUG || !TestGarbageCollector.isAvailable)
{
return pending("Test not available in this environment");
}
const errorSpy = spyOn(_Debug, "error");
const testModule = new SanitizedEmscriptenTestModule(utilTestModule, getTestModuleOptions());
await testModule.initialize();
expect(errorSpy).not.toHaveBeenCalled();
// this is an error in all cases with reference counting (forgot to use return)
SharedArray.createOne(testModule.wrapper, Float32Array, testModule.wrapper.rootNode, 100);
expect(await TestGarbageCollector.testFriendlyGc()).toBeGreaterThan(0);
expect(errorSpy).toHaveBeenCalledWith(jasmine.stringMatching(/^A shared object was leaked/));
testModule.wrapper.rootNode.getLinked().unlinkAll();
testModule.endEmscriptenProgram();
});
it("| provides finalization checks in debug", async () =>
{
if (!_BUILD.DEBUG || !TestGarbageCollector.isAvailable)
{
return pending("Test not available in this environment");
}
const errorSpy = spyOn(_Debug, "error");
const testModule = new SanitizedEmscriptenTestModule(utilTestModule, getTestModuleOptions());
await testModule.initialize();
expect(errorSpy).not.toHaveBeenCalled();
// leak the object while registering it
testModule.wrapper.lifecycleStrategy.onManagedObjectCreated(createTestManagedObject(testModule));
expect(await TestGarbageCollector.testFriendlyGc()).toBeGreaterThan(0);
expect(errorSpy).toHaveBeenCalled();
testModule.wrapper.rootNode.getLinked().unlinkAll();
testModule.endEmscriptenProgram();
});
});
class TestManagedObject implements IManagedObject
{
public constructor(
public resourceHandle: IManagedResourceNode
)
{
}
public getWrapper(): IEmscriptenWrapper<IInteropBindings>
{
throw new Error("not implemented");
}
}
function createTestManagedObject(testModule: SanitizedEmscriptenTestModule<IJsUtilBindings, object>): IManagedObject
{
return new TestManagedObject(testModule.wrapper.lifecycleStrategy.createNode(testModule.wrapper.rootNode));
}