1'use strict';
2// Flags: --expose-gc
3
4const { gcUntil, buildType } = require('../../common');
5const assert = require('assert');
6
7const test_reference = require(`./build/${buildType}/test_reference`);
8
9// This test script uses external values with finalizer callbacks
10// in order to track when values get garbage-collected. Each invocation
11// of a finalizer callback increments the finalizeCount property.
12assert.strictEqual(test_reference.finalizeCount, 0);
13
14// Run each test function in sequence,
15// with an async delay and GC call between each.
16async function runTests() {
17  (() => {
18    const symbol = test_reference.createSymbol('testSym');
19    test_reference.createReference(symbol, 0);
20    assert.strictEqual(test_reference.referenceValue, symbol);
21  })();
22  test_reference.deleteReference();
23
24  (() => {
25    const symbol = test_reference.createSymbolFor('testSymFor');
26    test_reference.createReference(symbol, 0);
27    assert.strictEqual(test_reference.referenceValue, symbol);
28  })();
29  test_reference.deleteReference();
30
31  (() => {
32    const symbol = test_reference.createSymbolFor('testSymFor');
33    test_reference.createReference(symbol, 1);
34    assert.strictEqual(test_reference.referenceValue, symbol);
35    assert.strictEqual(test_reference.referenceValue, Symbol.for('testSymFor'));
36  })();
37  test_reference.deleteReference();
38
39  (() => {
40    const symbol = test_reference.createSymbolForEmptyString();
41    test_reference.createReference(symbol, 0);
42    assert.strictEqual(test_reference.referenceValue, Symbol.for(''));
43  })();
44  test_reference.deleteReference();
45
46  (() => {
47    const symbol = test_reference.createSymbolForEmptyString();
48    test_reference.createReference(symbol, 1);
49    assert.strictEqual(test_reference.referenceValue, symbol);
50    assert.strictEqual(test_reference.referenceValue, Symbol.for(''));
51  })();
52  test_reference.deleteReference();
53
54  assert.throws(() => test_reference.createSymbolForIncorrectLength(),
55                /Invalid argument/);
56
57  (() => {
58    const value = test_reference.createExternal();
59    assert.strictEqual(test_reference.finalizeCount, 0);
60    assert.strictEqual(typeof value, 'object');
61    test_reference.checkExternal(value);
62  })();
63  await gcUntil('External value without a finalizer',
64                () => (test_reference.finalizeCount === 0));
65
66  (() => {
67    const value = test_reference.createExternalWithFinalize();
68    assert.strictEqual(test_reference.finalizeCount, 0);
69    assert.strictEqual(typeof value, 'object');
70    test_reference.checkExternal(value);
71  })();
72  await gcUntil('External value with a finalizer',
73                () => (test_reference.finalizeCount === 1));
74
75  (() => {
76    const value = test_reference.createExternalWithFinalize();
77    assert.strictEqual(test_reference.finalizeCount, 0);
78    test_reference.createReference(value, 0);
79    assert.strictEqual(test_reference.referenceValue, value);
80  })();
81  // Value should be GC'd because there is only a weak ref
82  await gcUntil('Weak reference',
83                () => (test_reference.referenceValue === undefined &&
84                test_reference.finalizeCount === 1));
85  test_reference.deleteReference();
86
87  (() => {
88    const value = test_reference.createExternalWithFinalize();
89    assert.strictEqual(test_reference.finalizeCount, 0);
90    test_reference.createReference(value, 1);
91    assert.strictEqual(test_reference.referenceValue, value);
92  })();
93  // Value should NOT be GC'd because there is a strong ref
94  await gcUntil('Strong reference',
95                () => (test_reference.finalizeCount === 0));
96  test_reference.deleteReference();
97  await gcUntil('Strong reference (cont.d)',
98                () => (test_reference.finalizeCount === 1));
99
100  (() => {
101    const value = test_reference.createExternalWithFinalize();
102    assert.strictEqual(test_reference.finalizeCount, 0);
103    test_reference.createReference(value, 1);
104  })();
105  // Value should NOT be GC'd because there is a strong ref
106  await gcUntil('Strong reference, increment then decrement to weak reference',
107                () => (test_reference.finalizeCount === 0));
108  assert.strictEqual(test_reference.incrementRefcount(), 2);
109  // Value should NOT be GC'd because there is a strong ref
110  await gcUntil(
111    'Strong reference, increment then decrement to weak reference (cont.d-1)',
112    () => (test_reference.finalizeCount === 0));
113  assert.strictEqual(test_reference.decrementRefcount(), 1);
114  // Value should NOT be GC'd because there is a strong ref
115  await gcUntil(
116    'Strong reference, increment then decrement to weak reference (cont.d-2)',
117    () => (test_reference.finalizeCount === 0));
118  assert.strictEqual(test_reference.decrementRefcount(), 0);
119  // Value should be GC'd because the ref is now weak!
120  await gcUntil(
121    'Strong reference, increment then decrement to weak reference (cont.d-3)',
122    () => (test_reference.finalizeCount === 1));
123  test_reference.deleteReference();
124  // Value was already GC'd
125  await gcUntil(
126    'Strong reference, increment then decrement to weak reference (cont.d-4)',
127    () => (test_reference.finalizeCount === 1));
128}
129runTests();
130
131// This test creates a napi_ref on an object that has
132// been wrapped by napi_wrap and for which the finalizer
133// for the wrap calls napi_delete_ref on that napi_ref.
134//
135// Since both the wrap and the reference use the same
136// object the finalizer for the wrap and reference
137// may run in the same gc and in any order.
138//
139// It does that to validate that napi_delete_ref can be
140// called before the finalizer has been run for the
141// reference (there is a finalizer behind the scenes even
142// though it cannot be passed to napi_create_reference).
143//
144// Since the order is not guarranteed, run the
145// test a number of times maximize the chance that we
146// get a run with the desired order for the test.
147//
148// 1000 reliably recreated the problem without the fix
149// required to ensure delete could be called before
150// the finalizer in manual testing.
151for (let i = 0; i < 1000; i++) {
152  const wrapObject = new Object();
153  test_reference.validateDeleteBeforeFinalize(wrapObject);
154  global.gc();
155}
156