1'use strict'; 2// Flags: --expose-gc 3// 4// Testing API calls for Node-API references. 5// We compare their behavior between Node-API version 8 and later. 6// In version 8 references can be created only for object, function, 7// and symbol types, while in newer versions they can be created for 8// any value type. 9// 10const { gcUntil, buildType } = require('../../common'); 11const assert = require('assert'); 12const addon_v8 = require(`./build/${buildType}/test_reference_obj_only`); 13const addon_new = require(`./build/${buildType}/test_reference_all_types`); 14 15async function runTests(addon, isVersion8, isLocalSymbol) { 16 let allEntries = []; 17 18 (() => { 19 // Create values of all napi_valuetype types. 20 const undefinedValue = undefined; 21 const nullValue = null; 22 const booleanValue = false; 23 const numberValue = 42; 24 const stringValue = 'test_string'; 25 const globalSymbolValue = Symbol.for('test_symbol_global'); 26 const localSymbolValue = Symbol('test_symbol_local'); 27 const symbolValue = isLocalSymbol ? localSymbolValue : globalSymbolValue; 28 const objectValue = { x: 1, y: 2 }; 29 const functionValue = (x, y) => x + y; 30 const externalValue = addon.createExternal(); 31 const bigintValue = 9007199254740991n; 32 33 // The position of entries in the allEntries array corresponds to the 34 // napi_valuetype enum value. See the CreateRef function for the 35 // implementation details. 36 allEntries = [ 37 { value: undefinedValue, canBeWeak: false, canBeRefV8: false }, 38 { value: nullValue, canBeWeak: false, canBeRefV8: false }, 39 { value: booleanValue, canBeWeak: false, canBeRefV8: false }, 40 { value: numberValue, canBeWeak: false, canBeRefV8: false }, 41 { value: stringValue, canBeWeak: false, canBeRefV8: false }, 42 { value: symbolValue, canBeWeak: isLocalSymbol, canBeRefV8: true, 43 isAlwaysStrong: !isLocalSymbol }, 44 { value: objectValue, canBeWeak: true, canBeRefV8: true }, 45 { value: functionValue, canBeWeak: true, canBeRefV8: true }, 46 { value: externalValue, canBeWeak: true, canBeRefV8: true }, 47 { value: bigintValue, canBeWeak: false, canBeRefV8: false }, 48 ]; 49 50 // Go over all values of different types, create strong ref values for 51 // them, read the stored values, and check how the ref count works. 52 for (const entry of allEntries) { 53 if (!isVersion8 || entry.canBeRefV8) { 54 const index = addon.createRef(entry.value); 55 const refValue = addon.getRefValue(index); 56 assert.strictEqual(entry.value, refValue); 57 assert.strictEqual(addon.ref(index), 2); 58 assert.strictEqual(addon.unref(index), 1); 59 assert.strictEqual(addon.unref(index), 0); 60 } else { 61 assert.throws(() => { addon.createRef(entry.value); }, 62 { 63 name: 'Error', 64 message: 'Invalid argument', 65 }); 66 } 67 } 68 69 // When the reference count is zero, then object types become weak pointers 70 // and other types are released. 71 // Here we know that the GC is not run yet because the values are 72 // still in the allEntries array. 73 allEntries.forEach((entry, index) => { 74 if (!isVersion8 || entry.canBeRefV8) { 75 if (entry.canBeWeak || entry.isAlwaysStrong) { 76 assert.strictEqual(addon.getRefValue(index), entry.value); 77 } else { 78 assert.strictEqual(addon.getRefValue(index), undefined); 79 } 80 } 81 // Set to undefined to allow GC collect the value. 82 entry.value = undefined; 83 }); 84 85 // To check that GC pass is done. 86 const objWithFinalizer = {}; 87 addon.addFinalizer(objWithFinalizer); 88 })(); 89 90 addon.initFinalizeCount(); 91 assert.strictEqual(addon.getFinalizeCount(), 0); 92 await gcUntil('Wait until a finalizer is called', 93 () => (addon.getFinalizeCount() === 1)); 94 95 // Create and call finalizer again to make sure that we had another GC pass. 96 (() => { 97 const objWithFinalizer = {}; 98 addon.addFinalizer(objWithFinalizer); 99 })(); 100 await gcUntil('Wait until a finalizer is called again', 101 () => (addon.getFinalizeCount() === 2)); 102 103 // After GC and finalizers run, all values that support weak reference 104 // semantic must return undefined value. 105 allEntries.forEach((entry, index) => { 106 if (!isVersion8 || entry.canBeRefV8) { 107 if (!entry.isAlwaysStrong) { 108 assert.strictEqual(addon.getRefValue(index), undefined); 109 } else { 110 assert.notStrictEqual(addon.getRefValue(index), undefined); 111 } 112 addon.deleteRef(index); 113 } 114 }); 115} 116 117async function runAllTests() { 118 await runTests(addon_v8, /* isVersion8 */ true, /* isLocalSymbol */ true); 119 await runTests(addon_v8, /* isVersion8 */ true, /* isLocalSymbol */ false); 120 await runTests(addon_new, /* isVersion8 */ false, /* isLocalSymbol */ true); 121 await runTests(addon_new, /* isVersion8 */ false, /* isLocalSymbol */ false); 122} 123 124runAllTests(); 125