Skip to content

test: merge ongc and gcutil into gc.js #54355

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions test/addons/cppgc-object/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Flags: --expose-gc

const common = require('../../common');

const { gcUntil } = require('../../common/gc');
// Verify that addons can create GarbageCollected objects and
// have them traced properly.

Expand Down Expand Up @@ -35,7 +35,7 @@ setTimeout(async function() {
for (let i = 0; i < count; ++i) {
array[i] = new CppGCed();
}
await common.gcUntil(
await gcUntil(
'All old CppGCed are destroyed',
() => states[kDestructCount] === count,
);
Expand All @@ -44,7 +44,7 @@ setTimeout(async function() {
array = null;
globalThis.gc();

await common.gcUntil(
await gcUntil(
'All old CppGCed are destroyed',
() => states[kDestructCount] === count * 2,
);
Expand Down
2 changes: 1 addition & 1 deletion test/async-hooks/test-async-local-storage-gcable.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

const common = require('../common');
const { AsyncLocalStorage } = require('async_hooks');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');

let asyncLocalStorage = new AsyncLocalStorage();

Expand Down
2 changes: 1 addition & 1 deletion test/common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ module exports a single `onGC()` function.

```js
require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');

onGC({}, { ongc() { console.log('collected'); } });
```
Expand Down
69 changes: 67 additions & 2 deletions test/common/gc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,72 @@
'use strict';

const wait = require('timers/promises').setTimeout;
const assert = require('assert');
const common = require('../common');
const gcTrackerMap = new WeakMap();
const gcTrackerTag = 'NODE_TEST_COMMON_GC_TRACKER';

// TODO(joyeecheung): merge ongc.js and gcUntil from common/index.js
// into this.
/**
* Installs a garbage collection listener for the specified object.
* Uses async_hooks for GC tracking, which may affect test functionality.
* A full setImmediate() invocation passes between a global.gc() call and the listener being invoked.
* @param {object} obj - The target object to track for garbage collection.
* @param {object} gcListener - The listener object containing the ongc callback.
* @param {Function} gcListener.ongc - The function to call when the target object is garbage collected.
*/
function onGC(obj, gcListener) {
const async_hooks = require('async_hooks');

const onGcAsyncHook = async_hooks.createHook({
init: common.mustCallAtLeast(function(id, type) {
if (this.trackedId === undefined) {
assert.strictEqual(type, gcTrackerTag);
this.trackedId = id;
}
}),
destroy(id) {
assert.notStrictEqual(this.trackedId, -1);
if (id === this.trackedId) {
this.gcListener.ongc();
onGcAsyncHook.disable();
}
},
}).enable();
onGcAsyncHook.gcListener = gcListener;

gcTrackerMap.set(obj, new async_hooks.AsyncResource(gcTrackerTag));
obj = null;
}

/**
* Repeatedly triggers garbage collection until a specified condition is met or a maximum number of attempts is reached.
* @param {string|Function} [name] - Optional name, used in the rejection message if the condition is not met.
* @param {Function} condition - A function that returns true when the desired condition is met.
* @returns {Promise} A promise that resolves when the condition is met, or rejects after 10 failed attempts.
*/
function gcUntil(name, condition) {
if (typeof name === 'function') {
condition = name;
name = undefined;
}
return new Promise((resolve, reject) => {
let count = 0;
function gcAndCheck() {
setImmediate(() => {
count++;
global.gc();
if (condition()) {
resolve();
} else if (count < 10) {
gcAndCheck();
} else {
reject(name === undefined ? undefined : 'Test ' + name + ' failed');
}
});
}
gcAndCheck();
});
}

// This function can be used to check if an object factor leaks or not,
// but it needs to be used with care:
Expand Down Expand Up @@ -124,4 +187,6 @@ module.exports = {
checkIfCollectable,
runAndBreathe,
checkIfCollectableByCounting,
onGC,
gcUntil,
};
25 changes: 0 additions & 25 deletions test/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -850,30 +850,6 @@ function skipIfDumbTerminal() {
}
}

function gcUntil(name, condition) {
if (typeof name === 'function') {
condition = name;
name = undefined;
}
return new Promise((resolve, reject) => {
let count = 0;
function gcAndCheck() {
setImmediate(() => {
count++;
global.gc();
if (condition()) {
resolve();
} else if (count < 10) {
gcAndCheck();
} else {
reject(name === undefined ? undefined : 'Test ' + name + ' failed');
}
});
}
gcAndCheck();
});
}

function requireNoPackageJSONAbove(dir = __dirname) {
let possiblePackage = path.join(dir, '..', 'package.json');
let lastPackage = null;
Expand Down Expand Up @@ -984,7 +960,6 @@ const common = {
expectsError,
expectRequiredModule,
expectWarning,
gcUntil,
getArrayBufferViews,
getBufferSources,
getCallSite,
Expand Down
32 changes: 0 additions & 32 deletions test/common/ongc.js

This file was deleted.

3 changes: 2 additions & 1 deletion test/js-native-api/6_object_wrap/test-object-wrap-ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
'use strict';
const common = require('../../common');
const addon = require(`./build/${common.buildType}/6_object_wrap`);
const { gcUntil } = require('../../common/gc');

(function scope() {
addon.objectWrapDanglingReference({});
})();

common.gcUntil('object-wrap-ref', () => {
gcUntil('object-wrap-ref', () => {
return addon.objectWrapDanglingReferenceTest();
});
5 changes: 3 additions & 2 deletions test/js-native-api/7_factory_wrap/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
const common = require('../../common');
const assert = require('assert');
const test = require(`./build/${common.buildType}/7_factory_wrap`);
const { gcUntil } = require('../../common/gc');

assert.strictEqual(test.finalizeCount, 0);
async function runGCTests() {
Expand All @@ -13,14 +14,14 @@ async function runGCTests() {
assert.strictEqual(obj.plusOne(), 12);
assert.strictEqual(obj.plusOne(), 13);
})();
await common.gcUntil('test 1', () => (test.finalizeCount === 1));
await gcUntil('test 1', () => (test.finalizeCount === 1));

(() => {
const obj2 = test.createObject(20);
assert.strictEqual(obj2.plusOne(), 21);
assert.strictEqual(obj2.plusOne(), 22);
assert.strictEqual(obj2.plusOne(), 23);
})();
await common.gcUntil('test 2', () => (test.finalizeCount === 2));
await gcUntil('test 2', () => (test.finalizeCount === 2));
}
runGCTests();
5 changes: 3 additions & 2 deletions test/js-native-api/8_passing_wrapped/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
const common = require('../../common');
const assert = require('assert');
const addon = require(`./build/${common.buildType}/8_passing_wrapped`);
const { gcUntil } = require('../../common/gc');

async function runTest() {
let obj1 = addon.createObject(10);
Expand All @@ -14,7 +15,7 @@ async function runTest() {
// Make sure the native destructor gets called.
obj1 = null;
obj2 = null;
await common.gcUntil('8_passing_wrapped',
() => (addon.finalizeCount() === 2));
await gcUntil('8_passing_wrapped',
() => (addon.finalizeCount() === 2));
}
runTest();
10 changes: 6 additions & 4 deletions test/js-native-api/test_finalizer/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ const common = require('../../common');
const test_finalizer = require(`./build/${common.buildType}/test_finalizer`);
const assert = require('assert');

const { gcUntil } = require('../../common/gc');

// The goal of this test is to show that we can run "pure" finalizers in the
// current JS loop tick. Thus, we do not use common.gcUntil function works
// current JS loop tick. Thus, we do not use gcUntil function works
// asynchronously using micro tasks.
// We use IIFE for the obj scope instead of {} to be compatible with
// non-V8 JS engines that do not support scoped variables.
Expand All @@ -25,7 +27,7 @@ for (let i = 0; i < 10; ++i) {
assert.strictEqual(test_finalizer.getFinalizerCallCount(), 1);

// The finalizer that access JS cannot run synchronously. They are run in the
// next JS loop tick. Thus, we must use common.gcUntil.
// next JS loop tick. Thus, we must use gcUntil.
async function runAsyncTests() {
// We do not use common.mustCall() because we want to see the finalizer
// called in response to GC and not as a part of env destruction.
Expand All @@ -36,8 +38,8 @@ async function runAsyncTests() {
const obj = {};
test_finalizer.addFinalizerWithJS(obj, () => { js_is_called = true; });
})();
await common.gcUntil('ensure JS finalizer called',
() => (test_finalizer.getFinalizerCallCount() === 2));
await gcUntil('ensure JS finalizer called',
() => (test_finalizer.getFinalizerCallCount() === 2));
assert(js_is_called);
}
runAsyncTests();
7 changes: 4 additions & 3 deletions test/js-native-api/test_general/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
const common = require('../../common');
const test_general = require(`./build/${common.buildType}/test_general`);
const assert = require('assert');
const { gcUntil } = require('../../common/gc');

const val1 = '1';
const val2 = 1;
Expand Down Expand Up @@ -79,17 +80,17 @@ async function runGCTests() {
assert.strictEqual(test_general.derefItemWasCalled(), false);

(() => test_general.wrap({}))();
await common.gcUntil('deref_item() was called upon garbage collecting a ' +
await gcUntil('deref_item() was called upon garbage collecting a ' +
'wrapped object.',
() => test_general.derefItemWasCalled());
() => test_general.derefItemWasCalled());

// Ensure that removing a wrap and garbage collecting does not fire the
// finalize callback.
let z = {};
test_general.testFinalizeWrap(z);
test_general.removeWrap(z);
z = null;
await common.gcUntil(
await gcUntil(
'finalize callback was not called upon garbage collection.',
() => (!test_general.finalizeWasCalled()));
}
Expand Down
5 changes: 3 additions & 2 deletions test/js-native-api/test_general/testFinalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
const common = require('../../common');
const test_general = require(`./build/${common.buildType}/test_general`);
const assert = require('assert');
const { gcUntil } = require('../../common/gc');

let finalized = {};
const callback = common.mustCall(2);
Expand All @@ -30,7 +31,7 @@ async function testFinalizeAndWrap() {
test_general.wrap(finalizeAndWrap);
test_general.addFinalizerOnly(finalizeAndWrap, common.mustCall());
finalizeAndWrap = null;
await common.gcUntil('test finalize and wrap',
() => test_general.derefItemWasCalled());
await gcUntil('test finalize and wrap',
() => test_general.derefItemWasCalled());
}
testFinalizeAndWrap();
3 changes: 2 additions & 1 deletion test/js-native-api/test_reference/test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';
// Flags: --expose-gc

const { gcUntil, buildType } = require('../../common');
const { buildType } = require('../../common');
const { gcUntil } = require('../../common/gc');
const assert = require('assert');

const test_reference = require(`./build/${buildType}/test_reference`);
Expand Down
3 changes: 2 additions & 1 deletion test/node-api/test_reference_by_node_api_version/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
// and symbol types, while in newer versions they can be created for
// any value type.
//
const { gcUntil, buildType } = require('../../common');
const { buildType } = require('../../common');
const { gcUntil } = require('../../common/gc');
const assert = require('assert');
const addon_v8 = require(`./build/${buildType}/test_reference_obj_only`);
const addon_new = require(`./build/${buildType}/test_reference_all_types`);
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-common-gc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';
// Flags: --expose-gc
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');

{
onGC({}, { ongc: common.mustCall() });
Expand Down
5 changes: 3 additions & 2 deletions test/parallel/test-domain-async-id-map-leak.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Flags: --expose-gc
'use strict';
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const { gcUntil } = require('../common/gc');
const assert = require('assert');
const async_hooks = require('async_hooks');
const domain = require('domain');
Expand Down Expand Up @@ -40,7 +41,7 @@ d.run(() => {
d = null;

async function main() {
await common.gcUntil(
await gcUntil(
'All objects garbage collected',
() => resourceGCed && domainGCed && emitterGCed);
}
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-gc-http-client-connaborted.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// but aborting every connection that comes in.

const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const http = require('http');
const os = require('os');

Expand Down
Loading
Loading