Skip to content

Commit

Permalink
test: merge ongc and gcutil into gc.js
Browse files Browse the repository at this point in the history
PR-URL: nodejs#54355
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
  • Loading branch information
tannal authored and legendecas committed Aug 29, 2024
1 parent 7616855 commit 0b6b2a4
Show file tree
Hide file tree
Showing 32 changed files with 122 additions and 101 deletions.
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 @@ -851,30 +851,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 @@ -985,7 +961,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

0 comments on commit 0b6b2a4

Please sign in to comment.