Skip to content

Commit

Permalink
Add ability to run tests with cluster mode
Browse files Browse the repository at this point in the history
Change-type: patch
  • Loading branch information
otaviojacobi committed Oct 27, 2024
1 parent cbd81b3 commit 8b2e1a9
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 20 deletions.
3 changes: 2 additions & 1 deletion test/00-basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import supertest from 'supertest';
import { expect } from 'chai';
const configPath = __dirname + '/fixtures/00-basic/config.js';
import { testInit, testDeInit, testLocalServer } from './lib/test-init';
import type { ChildProcess } from 'child_process';

describe('00 basic tests', function () {
let pineServer: Awaited<ReturnType<typeof testInit>>;
let pineServer: ChildProcess;
before(async () => {
pineServer = await testInit({ configPath });
});
Expand Down
3 changes: 2 additions & 1 deletion test/01-constrain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import supertest from 'supertest';
import { expect } from 'chai';
const configPath = __dirname + '/fixtures/01-constrain/config.js';
import { testInit, testDeInit, testLocalServer } from './lib/test-init';
import type { ChildProcess } from 'child_process';

describe('01 basic constrain tests', function () {
let pineServer: Awaited<ReturnType<typeof testInit>>;
let pineServer: ChildProcess;
before(async () => {
pineServer = await testInit({ configPath, deleteDb: true });
});
Expand Down
3 changes: 2 additions & 1 deletion test/04-translations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import type { AnyObject } from 'pinejs-client-core';

import { PineTest } from 'pinejs-client-supertest';
import { assertExists } from './lib/common';
import type { ChildProcess } from 'child_process';

describe('04 native translation tests', function () {
let pineServer: Awaited<ReturnType<typeof testInit>>;
let pineServer: ChildProcess;
let pineTest: PineTest;
let faculty: AnyObject;
before(async () => {
Expand Down
3 changes: 2 additions & 1 deletion test/05-request-cancellation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { testInit, testDeInit, testLocalServer } from './lib/test-init';
import { PineTest } from 'pinejs-client-supertest';
import request from 'request';
import { setTimeout } from 'timers/promises';
import type { ChildProcess } from 'child_process';

const requestAsync = (
opts:
Expand Down Expand Up @@ -43,7 +44,7 @@ async function expectLogs(pineTest: PineTest, expectedLogs: string[]) {

describe('05 request cancellation tests', function () {
let pineTest: PineTest;
let pineServer: Awaited<ReturnType<typeof testInit>>;
let pineServer: ChildProcess;

before(async () => {
pineServer = await testInit({ configPath, hooksPath, routesPath });
Expand Down
3 changes: 2 additions & 1 deletion test/06-webresource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ import {
} from '@aws-sdk/client-s3';
import { intVar, requiredVar } from '@balena/env-parsing';
import { assertExists } from './lib/common';
import type { ChildProcess } from 'child_process';

const pipeline = util.promisify(pipelineRaw);
const fs = fsBase.promises;

describe('06 webresources tests', function () {
let pineServer: Awaited<ReturnType<typeof testInit>>;
let pineServer: ChildProcess;

const filePath = `${testResourcePath}/avatar-profile.png`;
const newFilePath = `${testResourcePath}/other-image.png`;
Expand Down
3 changes: 2 additions & 1 deletion test/07-permissions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ const configPath = __dirname + '/fixtures/07-permissions/config.js';
import { testInit, testDeInit, testLocalServer } from './lib/test-init';
import { sbvrUtils, permissions } from '../src/server-glue/module';
import type UserModel from '../src/sbvr-api/user';
import type { ChildProcess } from 'child_process';

describe('07 permissions tests', function () {
let pineServer: Awaited<ReturnType<typeof testInit>>;
let pineServer: ChildProcess;
let userPineClient: sbvrUtils.PinejsClient<UserModel>;
before(async () => {
pineServer = await testInit({
Expand Down
3 changes: 2 additions & 1 deletion test/08-tasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { tasks as tasksEnv } from '../src/config-loader/env';
import type Model from '../src/tasks/tasks';
import * as cronParser from 'cron-parser';
import { PINE_TEST_SIGNALS } from './lib/common';
import type { ChildProcess } from 'node:child_process';

const actorId = 1;
const fixturesBasePath = __dirname + '/fixtures/08-tasks/';
Expand Down Expand Up @@ -63,7 +64,7 @@ async function expectTask(
}

describe('08 task tests', function () {
let pineServer: Awaited<ReturnType<typeof testInit>>;
let pineServer: ChildProcess;
let pineTest: PineTest;
let apikey: string;
before(async () => {
Expand Down
2 changes: 2 additions & 0 deletions test/lib/pine-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type PineTestOptions = {
withLoginRoute?: boolean;
deleteDb: boolean;
listenPort: number;
clusterMode?: boolean;
clusterNodes?: number;
};

export async function init(
Expand Down
90 changes: 77 additions & 13 deletions test/lib/test-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@ import type { PineTestOptions } from './pine-init';
import type { OptionalField } from '../../src/sbvr-api/common-types';
export const listenPortDefault = 1337;
export const testLocalServer = `http://localhost:${listenPortDefault}`;
import net, { type AddressInfo } from 'node:net';

export async function testInit(
options: OptionalField<PineTestOptions, 'listenPort' | 'deleteDb'>,
): Promise<ChildProcess> {
async function getAvailableTCPPort(): Promise<number> {
return new Promise((res) => {
const srv = net.createServer();
srv.listen(0, () => {
const port = (srv.address() as AddressInfo).port;
srv.close(() => {
res(port);
});
});
});
}

const forkServer = async (
processArgs: PineTestOptions,
): Promise<ChildProcess> => {
try {
const processArgs: PineTestOptions = {
listenPort: options.listenPort ?? listenPortDefault,
deleteDb: options.deleteDb ?? boolVar('DELETE_DB', false),
configPath: options.configPath,
hooksPath: options.hooksPath,
taskHandlersPath: options.taskHandlersPath,
routesPath: options.routesPath,
withLoginRoute: options.withLoginRoute,
};
const testServer = fork(
__dirname + '/pine-in-process.ts',
[JSON.stringify(processArgs)],
Expand Down Expand Up @@ -51,8 +55,68 @@ export async function testInit(
console.error(`TestServer wasn't created properly: ${err}`);
throw err;
}
};
export async function testInit(
options: OptionalField<PineTestOptions, 'listenPort' | 'deleteDb'> & {
clusterMode?: false;
clusterNodes?: never;
},
): Promise<ChildProcess>;
export async function testInit(
options: OptionalField<PineTestOptions, 'listenPort' | 'deleteDb'> & {
clusterMode: true;
clusterNodes: number;
},
): Promise<ChildProcess[]>;
export async function testInit(
options: OptionalField<PineTestOptions, 'listenPort' | 'deleteDb'>,
): Promise<ChildProcess | ChildProcess[]> {
const processArgs = {
listenPort: options.listenPort ?? listenPortDefault,
deleteDb: options.deleteDb ?? boolVar('DELETE_DB', false),
configPath: options.configPath,
hooksPath: options.hooksPath,
taskHandlersPath: options.taskHandlersPath,
routesPath: options.routesPath,
withLoginRoute: options.withLoginRoute,
clusterMode: options.clusterMode,
clusterNodes: options.clusterNodes,
};

if (options.clusterMode) {
if (!options.clusterNodes || options.clusterNodes < 2) {
throw new Error('Cluster mode requires at least 2 nodes');
}

const pineServerPromises: Array<Promise<ChildProcess>> = [];
const port = processArgs.listenPort ?? listenPortDefault;
const deleteDb = options.deleteDb ?? boolVar('DELETE_DB', false);
// Await for the first server to be available to avoid several
// process at the same time possibly trying to delete/recreate/read the DB tables
const server = await forkServer({
...processArgs,
listenPort: port,
deleteDb,
});

for (let i = 1; i < options.clusterNodes - 1; i++) {
const listenPort = await getAvailableTCPPort();
pineServerPromises.push(
forkServer({ ...processArgs, listenPort, deleteDb: false }),
);
}
return [server, ...(await Promise.all(pineServerPromises))];
}

return forkServer(processArgs);
}

export function testDeInit(testServer: ChildProcess) {
export function testDeInit(testServer: ChildProcess | ChildProcess[]) {
if (Array.isArray(testServer)) {
testServer.forEach((server) => {
server?.kill();
});
return;
}
testServer?.kill();
}

0 comments on commit 8b2e1a9

Please sign in to comment.