Skip to content
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

test: refactor test-abortcontroller to use node:test #54574

Closed
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
34 changes: 34 additions & 0 deletions test/parallel/test-abortcontroller-internal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Flags: --no-warnings --expose-gc --expose-internals
'use strict';
require('../common');

const {
strictEqual,
} = require('assert');

const {
test,
} = require('node:test');

const {
kWeakHandler,
} = require('internal/event_target');

const { setTimeout: sleep } = require('timers/promises');

// The tests in this file depend on Node.js internal APIs. These are not necessarily
// portable to other runtimes

test('A weak event listener should not prevent gc', async () => {
// If the event listener is weak, however, it should not prevent gc
let ref;
function handler() {}
{
ref = new globalThis.WeakRef(AbortSignal.timeout(1_200_000));
ref.deref().addEventListener('abort', handler, { [kWeakHandler]: {} });
}

await sleep(10);
globalThis.gc();
strictEqual(ref.deref(), undefined);
});
213 changes: 108 additions & 105 deletions test/parallel/test-abortcontroller.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Flags: --no-warnings --expose-gc --expose-internals
// Flags: --expose-gc
'use strict';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add an eslint rule to avoid re-adding expose-internals to files that we avoid.


const common = require('../common');
require('../common');
const { inspect } = require('util');

const {
Expand All @@ -12,80 +12,93 @@ const {
} = require('assert');

const {
kWeakHandler,
} = require('internal/event_target');
test,
mock,
} = require('node:test');

const { setTimeout: sleep } = require('timers/promises');

{
// All of the the tests in this file depend on public-facing Node.js APIs.
// For tests that depend on Node.js internal APIs, please add them to
// test-abortcontroller-internal.js instead.

test('Abort is fired with the correct event type on AbortControllers', () => {
// Tests that abort is fired with the correct event type on AbortControllers
const ac = new AbortController();
ok(ac.signal);
ac.signal.onabort = common.mustCall((event) => {

const fn = mock.fn((event) => {
ok(event);
strictEqual(event.type, 'abort');
});
ac.signal.addEventListener('abort', common.mustCall((event) => {
ok(event);
strictEqual(event.type, 'abort');
}), { once: true });

ac.signal.onabort = fn;
ac.signal.addEventListener('abort', fn);

ac.abort();
ac.abort();
ok(ac.signal.aborted);
}

{
strictEqual(fn.mock.calls.length, 2);
});

test('Abort events are trusted', () => {
// Tests that abort events are trusted
const ac = new AbortController();
ac.signal.addEventListener('abort', common.mustCall((event) => {

const fn = mock.fn((event) => {
ok(event.isTrusted);
}));
});

ac.signal.onabort = fn;
ac.abort();
}
strictEqual(fn.mock.calls.length, 1);
});

{
test('Abort events have the same isTrusted reference', () => {
// Tests that abort events have the same `isTrusted` reference
const first = new AbortController();
const second = new AbortController();
let ev1, ev2;
const ev3 = new Event('abort');
first.signal.addEventListener('abort', common.mustCall((event) => {

first.signal.addEventListener('abort', (event) => {
ev1 = event;
}));
second.signal.addEventListener('abort', common.mustCall((event) => {
});
second.signal.addEventListener('abort', (event) => {
ev2 = event;
}));
});
first.abort();
second.abort();
const firstTrusted = Reflect.getOwnPropertyDescriptor(Object.getPrototypeOf(ev1), 'isTrusted').get;
const secondTrusted = Reflect.getOwnPropertyDescriptor(Object.getPrototypeOf(ev2), 'isTrusted').get;
const untrusted = Reflect.getOwnPropertyDescriptor(Object.getPrototypeOf(ev3), 'isTrusted').get;
strictEqual(firstTrusted, secondTrusted);
strictEqual(untrusted, firstTrusted);
}
});

{
test('AbortSignal is impossible to construct manually', () => {
// Tests that AbortSignal is impossible to construct manually
const ac = new AbortController();
throws(() => new ac.signal.constructor(), {
code: 'ERR_ILLEGAL_CONSTRUCTOR',
});
}
{
});

test('Symbol.toStringTag is correct', () => {
// Symbol.toStringTag
const toString = (o) => Object.prototype.toString.call(o);
const ac = new AbortController();
strictEqual(toString(ac), '[object AbortController]');
strictEqual(toString(ac.signal), '[object AbortSignal]');
}
});

{
test('AbortSignal.abort() creates an already aborted signal', () => {
const signal = AbortSignal.abort();
ok(signal.aborted);
}
});

{
// Test that AbortController properties and methods validate the receiver
test('AbortController properties and methods valiate the receiver', () => {
const acSignalGet = Object.getOwnPropertyDescriptor(
AbortController.prototype,
'signal'
Expand Down Expand Up @@ -115,10 +128,9 @@ const { setTimeout: sleep } = require('timers/promises');
{ name: 'TypeError' }
);
}
}
});

{
// Test that AbortSignal properties validate the receiver
test('AbortSignal properties validate the receiver', () => {
const signalAbortedGet = Object.getOwnPropertyDescriptor(
AbortSignal.prototype,
'aborted'
Expand All @@ -142,96 +154,87 @@ const { setTimeout: sleep } = require('timers/promises');
{ name: 'TypeError' }
);
}
}
});

{
test('AbortController inspection depth 1 or null works', () => {
const ac = new AbortController();
strictEqual(inspect(ac, { depth: 1 }),
'AbortController { signal: [AbortSignal] }');
strictEqual(inspect(ac, { depth: null }),
'AbortController { signal: AbortSignal { aborted: false } }');
}
});

{
test('AbortSignal reason is set correctly', () => {
// Test AbortSignal.reason
const ac = new AbortController();
ac.abort('reason');
strictEqual(ac.signal.reason, 'reason');
}
});

{
test('AbortSignal reasonable is set correctly with AbortSignal.abort()', () => {
// Test AbortSignal.reason
const signal = AbortSignal.abort('reason');
strictEqual(signal.reason, 'reason');
}
});

{
test('AbortSignal.timeout() works as expected', async () => {
// Test AbortSignal timeout
const signal = AbortSignal.timeout(10);
ok(!signal.aborted);
setTimeout(common.mustCall(() => {

const { promise, resolve } = Promise.withResolvers();

const fn = mock.fn(() => {
ok(signal.aborted);
strictEqual(signal.reason.name, 'TimeoutError');
strictEqual(signal.reason.code, 23);
}), 20);
}

{
(async () => {
// Test AbortSignal timeout doesn't prevent the signal
// from being garbage collected.
let ref;
{
ref = new globalThis.WeakRef(AbortSignal.timeout(1_200_000));
}

await sleep(10);
globalThis.gc();
strictEqual(ref.deref(), undefined);
})().then(common.mustCall());

(async () => {
// Test that an AbortSignal with a timeout is not gc'd while
// there is an active listener on it.
let ref;
function handler() {}
{
ref = new globalThis.WeakRef(AbortSignal.timeout(1_200_000));
ref.deref().addEventListener('abort', handler);
}

await sleep(10);
globalThis.gc();
notStrictEqual(ref.deref(), undefined);
ok(ref.deref() instanceof AbortSignal);

ref.deref().removeEventListener('abort', handler);

await sleep(10);
globalThis.gc();
strictEqual(ref.deref(), undefined);
})().then(common.mustCall());

(async () => {
// If the event listener is weak, however, it should not prevent gc
let ref;
function handler() {}
{
ref = new globalThis.WeakRef(AbortSignal.timeout(1_200_000));
ref.deref().addEventListener('abort', handler, { [kWeakHandler]: {} });
}

await sleep(10);
globalThis.gc();
strictEqual(ref.deref(), undefined);
})().then(common.mustCall());

// Setting a long timeout (20 minutes here) should not
// keep the Node.js process open (the timer is unref'd)
resolve();
});

setTimeout(fn, 20);
await promise;
});

test('AbortSignal.timeout() does not prevent the signal from being collected', async () => {
// Test AbortSignal timeout doesn't prevent the signal
// from being garbage collected.
let ref;
{
ref = new globalThis.WeakRef(AbortSignal.timeout(1_200_000));
}

await sleep(10);
globalThis.gc();
strictEqual(ref.deref(), undefined);
});

test('AbortSignal with a timeout is not collected while there is an active listener', async () => {
// Test that an AbortSignal with a timeout is not gc'd while
// there is an active listener on it.
let ref;
function handler() {}
{
ref = new globalThis.WeakRef(AbortSignal.timeout(1_200_000));
ref.deref().addEventListener('abort', handler);
}

await sleep(10);
globalThis.gc();
notStrictEqual(ref.deref(), undefined);
ok(ref.deref() instanceof AbortSignal);

ref.deref().removeEventListener('abort', handler);

await sleep(10);
globalThis.gc();
strictEqual(ref.deref(), undefined);
});

test('Setting a long timeout should not keep the process open', () => {
AbortSignal.timeout(1_200_000);
}
});

{
test('AbortSignal.reason should default', () => {
// Test AbortSignal.reason default
const signal = AbortSignal.abort();
ok(signal.reason instanceof DOMException);
Expand All @@ -241,9 +244,9 @@ const { setTimeout: sleep } = require('timers/promises');
ac.abort();
ok(ac.signal.reason instanceof DOMException);
strictEqual(ac.signal.reason.code, 20);
}
});

{
test('abortSignal.throwIfAborted() works as expected', () => {
// Test abortSignal.throwIfAborted()
throws(() => AbortSignal.abort().throwIfAborted(), {
code: 20,
Expand All @@ -253,21 +256,21 @@ const { setTimeout: sleep } = require('timers/promises');
// Does not throw because it's not aborted.
const ac = new AbortController();
ac.signal.throwIfAborted();
}
});

{
test('abortSignal.throwIfAobrted() works as expected (2)', () => {
const originalDesc = Reflect.getOwnPropertyDescriptor(AbortSignal.prototype, 'aborted');
const actualReason = new Error();
Reflect.defineProperty(AbortSignal.prototype, 'aborted', { value: false });
throws(() => AbortSignal.abort(actualReason).throwIfAborted(), actualReason);
Reflect.defineProperty(AbortSignal.prototype, 'aborted', originalDesc);
}
});

{
test('abortSignal.throwIfAobrted() works as expected (3)', () => {
const originalDesc = Reflect.getOwnPropertyDescriptor(AbortSignal.prototype, 'reason');
const actualReason = new Error();
const fakeExcuse = new Error();
Reflect.defineProperty(AbortSignal.prototype, 'reason', { value: fakeExcuse });
throws(() => AbortSignal.abort(actualReason).throwIfAborted(), actualReason);
Reflect.defineProperty(AbortSignal.prototype, 'reason', originalDesc);
}
});
Loading