| 'use strict'; |
| // Flags: --expose-gc |
| |
| const { gcUntil, buildType } = require('../../common'); |
| const assert = require('assert'); |
| |
| const test_reference = require(`./build/${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. |
| async function runTests() { |
| (() => { |
| const symbol = test_reference.createSymbol('testSym'); |
| test_reference.createReference(symbol, 0); |
| assert.strictEqual(test_reference.referenceValue, symbol); |
| })(); |
| test_reference.deleteReference(); |
| |
| (() => { |
| const symbol = test_reference.createSymbolFor('testSymFor'); |
| test_reference.createReference(symbol, 0); |
| assert.strictEqual(test_reference.referenceValue, symbol); |
| assert.strictEqual(test_reference.referenceValue, Symbol.for('testSymFor')); |
| })(); |
| test_reference.deleteReference(); |
| |
| (() => { |
| const symbol = test_reference.createSymbolForEmptyString(); |
| test_reference.createReference(symbol, 0); |
| assert.strictEqual(test_reference.referenceValue, symbol); |
| assert.strictEqual(test_reference.referenceValue, Symbol.for('')); |
| })(); |
| test_reference.deleteReference(); |
| |
| assert.throws(() => test_reference.createSymbolForIncorrectLength(), |
| /Invalid argument/); |
| |
| (() => { |
| const value = test_reference.createExternal(); |
| assert.strictEqual(test_reference.finalizeCount, 0); |
| assert.strictEqual(typeof value, 'object'); |
| test_reference.checkExternal(value); |
| })(); |
| await gcUntil('External value without a finalizer', |
| () => (test_reference.finalizeCount === 0)); |
| |
| (() => { |
| const value = test_reference.createExternalWithFinalize(); |
| assert.strictEqual(test_reference.finalizeCount, 0); |
| assert.strictEqual(typeof value, 'object'); |
| test_reference.checkExternal(value); |
| })(); |
| await gcUntil('External value with a finalizer', |
| () => (test_reference.finalizeCount === 1)); |
| |
| (() => { |
| 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 |
| await gcUntil('Weak reference', |
| () => (test_reference.referenceValue === undefined && |
| test_reference.finalizeCount === 1)); |
| test_reference.deleteReference(); |
| |
| (() => { |
| 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 |
| await gcUntil('Strong reference', |
| () => (test_reference.finalizeCount === 0)); |
| test_reference.deleteReference(); |
| await gcUntil('Strong reference (cont.d)', |
| () => (test_reference.finalizeCount === 1)); |
| |
| (() => { |
| 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 |
| await gcUntil('Strong reference, increment then decrement to weak reference', |
| () => (test_reference.finalizeCount === 0)); |
| assert.strictEqual(test_reference.incrementRefcount(), 2); |
| // Value should NOT be GC'd because there is a strong ref |
| await gcUntil( |
| 'Strong reference, increment then decrement to weak reference (cont.d-1)', |
| () => (test_reference.finalizeCount === 0)); |
| assert.strictEqual(test_reference.decrementRefcount(), 1); |
| // Value should NOT be GC'd because there is a strong ref |
| await gcUntil( |
| 'Strong reference, increment then decrement to weak reference (cont.d-2)', |
| () => (test_reference.finalizeCount === 0)); |
| assert.strictEqual(test_reference.decrementRefcount(), 0); |
| // Value should be GC'd because the ref is now weak! |
| await gcUntil( |
| 'Strong reference, increment then decrement to weak reference (cont.d-3)', |
| () => (test_reference.finalizeCount === 1)); |
| test_reference.deleteReference(); |
| // Value was already GC'd |
| await gcUntil( |
| 'Strong reference, increment then decrement to weak reference (cont.d-4)', |
| () => (test_reference.finalizeCount === 1)); |
| } |
| runTests(); |
| |
| // 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(); |
| } |