Skip to content

Commit

Permalink
test: refactor test-abortcontroller to use node:test
Browse files Browse the repository at this point in the history
Starting the long process of refactoring our own tests to use
the node:test module and mocks.

PR-URL: #54574
Reviewed-By: Yagiz Nizipli <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
  • Loading branch information
jasnell authored and RafaelGSS committed Aug 30, 2024
1 parent 4baf463 commit 64442fc
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 105 deletions.
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';

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);
}
});

0 comments on commit 64442fc

Please sign in to comment.