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