'use strict'; // Flags: --expose-gc const common = require('../../common'); const assert = require('assert'); const test_reference = require(`./build/${common.buildType}/test_reference`); // This test script uses external values with finalizer callbacks // in order to track when values get garbage-collected. Each invocation // of a finalizer callback increments the finalizeCount property. assert.strictEqual(test_reference.finalizeCount, 0); // Run each test function in sequence, // with an async delay and GC call between each. function runTests(i, title, tests) { if (tests[i]) { if (typeof tests[i] === 'string') { title = tests[i]; runTests(i + 1, title, tests); } else { try { tests[i](); } catch (e) { console.error(`Test failed: ${title}`); throw e; } setImmediate(() => { global.gc(); runTests(i + 1, title, tests); }); } } } runTests(0, undefined, [ 'External value without a finalizer', () => { const value = test_reference.createExternal(); assert.strictEqual(test_reference.finalizeCount, 0); assert.strictEqual(typeof value, 'object'); test_reference.checkExternal(value); }, () => { assert.strictEqual(test_reference.finalizeCount, 0); }, 'External value with a finalizer', () => { const value = test_reference.createExternalWithFinalize(); assert.strictEqual(test_reference.finalizeCount, 0); assert.strictEqual(typeof value, 'object'); test_reference.checkExternal(value); }, () => { assert.strictEqual(test_reference.finalizeCount, 1); }, 'Weak reference', () => { const value = test_reference.createExternalWithFinalize(); assert.strictEqual(test_reference.finalizeCount, 0); test_reference.createReference(value, 0); assert.strictEqual(test_reference.referenceValue, value); }, () => { // Value should be GC'd because there is only a weak ref assert.strictEqual(test_reference.referenceValue, undefined); assert.strictEqual(test_reference.finalizeCount, 1); test_reference.deleteReference(); }, 'Strong reference', () => { const value = test_reference.createExternalWithFinalize(); assert.strictEqual(test_reference.finalizeCount, 0); test_reference.createReference(value, 1); assert.strictEqual(test_reference.referenceValue, value); }, () => { // Value should NOT be GC'd because there is a strong ref assert.strictEqual(test_reference.finalizeCount, 0); test_reference.deleteReference(); }, () => { // Value should be GC'd because the strong ref was deleted assert.strictEqual(test_reference.finalizeCount, 1); }, 'Strong reference, increment then decrement to weak reference', () => { const value = test_reference.createExternalWithFinalize(); assert.strictEqual(test_reference.finalizeCount, 0); test_reference.createReference(value, 1); }, () => { // Value should NOT be GC'd because there is a strong ref assert.strictEqual(test_reference.finalizeCount, 0); assert.strictEqual(test_reference.incrementRefcount(), 2); }, () => { // Value should NOT be GC'd because there is a strong ref assert.strictEqual(test_reference.finalizeCount, 0); assert.strictEqual(test_reference.decrementRefcount(), 1); }, () => { // Value should NOT be GC'd because there is a strong ref assert.strictEqual(test_reference.finalizeCount, 0); assert.strictEqual(test_reference.decrementRefcount(), 0); }, () => { // Value should be GC'd because the ref is now weak! assert.strictEqual(test_reference.finalizeCount, 1); test_reference.deleteReference(); }, () => { // Value was already GC'd assert.strictEqual(test_reference.finalizeCount, 1); }, ]); // This test creates a napi_ref on an object that has // been wrapped by napi_wrap and for which the finalizer // for the wrap calls napi_delete_ref on that napi_ref. // // Since both the wrap and the reference use the same // object the finalizer for the wrap and reference // may run in the same gc and in any order. // // It does that to validate that napi_delete_ref can be // called before the finalizer has been run for the // reference (there is a finalizer behind the scenes even // though it cannot be passed to napi_create_reference). // // Since the order is not guarranteed, run the // test a number of times maximize the chance that we // get a run with the desired order for the test. // // 1000 reliably recreated the problem without the fix // required to ensure delete could be called before // the finalizer in manual testing. for (let i = 0; i < 1000; i++) { const wrapObject = new Object(); test_reference.validateDeleteBeforeFinalize(wrapObject); global.gc(); }