tests: start adding quic test server utilities

PR-URL: https://github.com/nodejs/node/pull/59946
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
James M Snell 2025-09-20 08:31:22 -07:00
parent 65a32bac18
commit cff138c6b1
5 changed files with 216 additions and 38 deletions

View File

@ -266,8 +266,18 @@
'HAVE_CONFIG_H', 'HAVE_CONFIG_H',
'WITH_EXAMPLE_OSSL', 'WITH_EXAMPLE_OSSL',
'EV_STANDALONE=1', 'EV_STANDALONE=1',
'HAVE_UNISTD_H',
'HAVE_ARPA_INET_H',
'HAVE_NETINET_IN_H',
'HAVE_NETINET_IP_H',
], ],
'conditions': [ 'conditions': [
['OS=="aix" or OS=="win"', {
# AIX does not support some of the networking features used in
# the test server. Windows also lacks the Unix-specific headers
# and system calls required by the ngtcp2 examples.
'type': 'none', # Disable as executable on AIX and Windows
}],
['OS=="mac"', { ['OS=="mac"', {
'defines': [ 'defines': [
'__APPLE_USE_RFC_3542', '__APPLE_USE_RFC_3542',
@ -281,25 +291,6 @@
'libraries': [ '-lsocket', '-lnsl' ], 'libraries': [ '-lsocket', '-lnsl' ],
}, },
}], }],
['OS=="win"', {
'defines': [
'WIN32',
'_WINDOWS',
],
'msvs_settings': {
'VCCLCompilerTool': {
'CompileAs': '1'
},
},
}],
['OS!="win"', {
'defines': [
'HAVE_UNISTD_H',
'HAVE_ARPA_INET_H',
'HAVE_NETINET_IN_H',
'HAVE_NETINET_IP_H',
],
}],
[ 'OS=="linux" or OS=="openharmony"', { [ 'OS=="linux" or OS=="openharmony"', {
'link_settings': { 'link_settings': {
'libraries': [ '-ldl', '-lrt' ], 'libraries': [ '-ldl', '-lrt' ],
@ -333,8 +324,18 @@
'HAVE_CONFIG_H', 'HAVE_CONFIG_H',
'WITH_EXAMPLE_OSSL', 'WITH_EXAMPLE_OSSL',
'EV_STANDALONE=1', 'EV_STANDALONE=1',
'HAVE_UNISTD_H',
'HAVE_ARPA_INET_H',
'HAVE_NETINET_IN_H',
'HAVE_NETINET_IP_H',
], ],
'conditions': [ 'conditions': [
['OS=="aix" or OS=="win"', {
# AIX does not support some of the networking features used in
# the test client. Windows also lacks the Unix-specific headers
# and system calls required by the ngtcp2 examples.
'type': 'none', # Disable as executable on AIX and Windows
}],
['OS=="mac"', { ['OS=="mac"', {
'defines': [ 'defines': [
'__APPLE_USE_RFC_3542', '__APPLE_USE_RFC_3542',
@ -348,25 +349,6 @@
'libraries': [ '-lsocket', '-lnsl' ], 'libraries': [ '-lsocket', '-lnsl' ],
}, },
}], }],
['OS=="win"', {
'defines': [
'WIN32',
'_WINDOWS',
],
'msvs_settings': {
'VCCLCompilerTool': {
'CompileAs': '1'
},
},
}],
['OS!="win"', {
'defines': [
'HAVE_UNISTD_H',
'HAVE_ARPA_INET_H',
'HAVE_NETINET_IN_H',
'HAVE_NETINET_IP_H',
],
}],
[ 'OS=="linux" or OS=="openharmony"', { [ 'OS=="linux" or OS=="openharmony"', {
'link_settings': { 'link_settings': {
'libraries': [ '-ldl', '-lrt' ], 'libraries': [ '-ldl', '-lrt' ],

View File

@ -0,0 +1,64 @@
import { resolve } from 'node:path';
import { spawn } from 'node:child_process';
export default class QuicTestClient {
#pathToClient;
#runningProcess;
constructor() {
this.#pathToClient = resolve(process.execPath, '../ngtcp2_test_client');
console.log(this.#pathToClient);
}
help(options = { stdio: 'inherit' }) {
const { promise, resolve, reject } = Promise.withResolvers();
const proc = spawn(this.#pathToClient, ['--help'], options);
proc.on('error', reject);
proc.on('exit', (code, signal) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Process exited with code ${code} and signal ${signal}`));
}
});
return promise;
}
run(address, port, uri, options = { stdio: 'inherit' }) {
const { promise, resolve, reject } = Promise.withResolvers();
if (this.#runningProcess) {
reject(new Error('Server is already running'));
return promise;
}
const args = [
address,
port,
uri ?? '',
];
this.#runningProcess = spawn(this.#pathToClient, args, options);
this.#runningProcess.on('error', (err) => {
this.#runningProcess = undefined;
reject(err);
});
this.#runningProcess.on('exit', (code, signal) => {
if (code === 0) {
resolve();
} else {
if (code === null && signal === 'SIGTERM') {
// Normal termination due to stop() being called.
resolve();
return;
}
reject(new Error(`Process exited with code ${code} and signal ${signal}`));
}
});
return promise;
}
stop() {
if (this.#runningProcess) {
this.#runningProcess.kill();
this.#runningProcess = undefined;
}
}
};

View File

@ -0,0 +1,66 @@
import { resolve } from 'node:path';
import { spawn } from 'node:child_process';
export default class QuicTestServer {
#pathToServer;
#runningProcess;
constructor() {
this.#pathToServer = resolve(process.execPath, '../ngtcp2_test_server');
console.log(this.#pathToServer);
}
help(options = { stdio: 'inherit' }) {
const { promise, resolve, reject } = Promise.withResolvers();
const proc = spawn(this.#pathToServer, ['--help'], options);
proc.on('error', reject);
proc.on('exit', (code, signal) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Process exited with code ${code} and signal ${signal}`));
}
});
return promise;
}
run(address, port, keyFile, certFile, options = { stdio: 'inherit' }) {
const { promise, resolve, reject } = Promise.withResolvers();
if (this.#runningProcess) {
reject(new Error('Server is already running'));
return promise;
}
const args = [
address,
port,
keyFile,
certFile,
];
this.#runningProcess = spawn(this.#pathToServer, args, options);
this.#runningProcess.on('error', (err) => {
this.#runningProcess = undefined;
reject(err);
});
this.#runningProcess.on('exit', (code, signal) => {
this.#runningProcess = undefined;
if (code === 0) {
resolve();
} else {
if (code === null && signal === 'SIGTERM') {
// Normal termination due to stop() being called.
resolve();
return;
}
reject(new Error(`Process exited with code ${code} and signal ${signal}`));
}
});
return promise;
}
stop() {
if (this.#runningProcess) {
this.#runningProcess.kill();
this.#runningProcess = undefined;
}
}
};

View File

@ -0,0 +1,32 @@
// Flags: --experimental-quic
import { hasQuic, isAIX, isWindows, skip } from '../common/index.mjs';
import { rejects } from 'node:assert';
if (!hasQuic) {
skip('QUIC support is not enabled');
}
if (isAIX) {
// AIX does not support some of the networking features used in the ngtcp2
// example server and client.
skip('QUIC third-party tests are disabled on AIX');
}
if (isWindows) {
// Windows does not support the [Li/U]nix specific headers and system calls
// required by the ngtcp2 example server/client.
skip('QUIC third-party tests are disabled on Windows');
}
const { default: QuicTestClient } = await import('../common/quic/test-client.mjs');
const client = new QuicTestClient();
// If this completes without throwing, the test passes.
await client.help({ stdio: 'ignore' });
setTimeout(() => {
client.stop();
}, 100);
// We expect this to fail since there's no server running.
await rejects(client.run('localhost', '12345', undefined, { stdio: 'ignore' }),
{ message: /Process exited with code 1 and signal null/ });

View File

@ -0,0 +1,34 @@
// Flags: --experimental-quic
import { hasQuic, isAIX, isWindows, skip } from '../common/index.mjs';
if (!hasQuic) {
skip('QUIC support is not enabled');
}
if (isAIX) {
// AIX does not support some of the networking features used in the ngtcp2
// example server and client.
skip('QUIC third-party tests are disabled on AIX');
}
if (isWindows) {
// Windows does not support the [Li/U]nix specific headers and system calls
// required by the ngtcp2 example server/client.
skip('QUIC third-party tests are disabled on Windows');
}
const { default: QuicTestServer } = await import('../common/quic/test-server.mjs');
const fixtures = await import('../common/fixtures.mjs');
const server = new QuicTestServer();
const fixturesPath = fixtures.path();
// If this completes without throwing, the test passes.
await server.help({ stdio: 'ignore' });
setTimeout(() => {
server.stop();
}, 100);
await server.run('localhost', '12345',
`${fixturesPath}/keys/agent1-key.pem`,
`${fixturesPath}/keys/agent1-cert.pem`,
{ stdio: 'inherit' });