Skip to content

Commit 41f7839

Browse files
committed
feat: enhance test setup and execution with concurrency and timing improvements
1 parent bb7db07 commit 41f7839

11 files changed

Lines changed: 332 additions & 239 deletions

backend/_run-with-timing.mjs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { spawn } from 'node:child_process';
2+
import process from 'node:process';
3+
4+
if (process.argv.length < 3) {
5+
console.error('Usage: node _run-with-timing.mjs <command> [args...]');
6+
process.exit(2);
7+
}
8+
9+
const [, , command, ...args] = process.argv;
10+
const start = process.hrtime.bigint();
11+
12+
const child = spawn(command, args, { stdio: 'inherit', shell: false });
13+
14+
const formatDuration = (ns) => {
15+
const totalSeconds = Number(ns) / 1e9;
16+
const minutes = Math.floor(totalSeconds / 60);
17+
const seconds = totalSeconds - minutes * 60;
18+
if (minutes > 0) {
19+
return `${minutes}m ${seconds.toFixed(1)}s`;
20+
}
21+
return `${seconds.toFixed(2)}s`;
22+
};
23+
24+
const printTiming = (label) => {
25+
const elapsed = process.hrtime.bigint() - start;
26+
const stamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
27+
console.log(`\n[${stamp}] ${label} in ${formatDuration(elapsed)}`);
28+
};
29+
30+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
31+
process.on(signal, () => child.kill(signal));
32+
}
33+
34+
child.on('exit', (code, signal) => {
35+
if (signal) {
36+
printTiming(`Tests terminated by ${signal}`);
37+
process.exit(1);
38+
return;
39+
}
40+
printTiming(code === 0 ? 'Tests completed' : `Tests failed (exit ${code})`);
41+
process.exit(code ?? 1);
42+
});
43+
44+
child.on('error', (err) => {
45+
console.error(`Failed to start child process: ${err.message}`);
46+
process.exit(1);
47+
});

backend/_setup-worker-db.mjs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import path from 'node:path';
2+
import process from 'node:process';
3+
import knex from 'knex';
4+
5+
const TEMPLATE_DB = 'rocketadmin_test_template';
6+
const TEMPLATE_LOCK_ID = 4242424242;
7+
8+
const workerId = process.pid;
9+
const pgLiteFolderPath = process.env.PGLITE_FOLDER_PATH;
10+
11+
if (pgLiteFolderPath && pgLiteFolderPath.length > 0) {
12+
process.env.PGLITE_FOLDER_PATH = path.join(pgLiteFolderPath, `worker-${workerId}`);
13+
} else if (process.env.DATABASE_URL) {
14+
const url = new URL(process.env.DATABASE_URL);
15+
const sourceDb = url.pathname.replace(/^\//, '') || 'postgres';
16+
const workerDbName = `rocketadmin_test_w${workerId}`;
17+
const baseConnection = {
18+
host: url.hostname,
19+
port: Number.parseInt(url.port, 10) || 5432,
20+
user: decodeURIComponent(url.username),
21+
password: decodeURIComponent(url.password),
22+
};
23+
24+
const admin = knex({
25+
client: 'pg',
26+
connection: { ...baseConnection, database: 'template1' },
27+
});
28+
29+
try {
30+
await admin.raw('SELECT pg_advisory_lock(?)', [TEMPLATE_LOCK_ID]);
31+
try {
32+
const existing = await admin.raw('SELECT 1 FROM pg_database WHERE datname = ?', [TEMPLATE_DB]);
33+
if (existing.rows.length === 0) {
34+
await admin.raw(`CREATE DATABASE "${TEMPLATE_DB}" TEMPLATE "${sourceDb}"`);
35+
const templateConn = knex({
36+
client: 'pg',
37+
connection: { ...baseConnection, database: TEMPLATE_DB },
38+
});
39+
try {
40+
await templateConn.raw('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"');
41+
} finally {
42+
await templateConn.destroy();
43+
}
44+
await admin.raw('UPDATE pg_database SET datistemplate = TRUE WHERE datname = ?', [TEMPLATE_DB]);
45+
}
46+
} finally {
47+
await admin.raw('SELECT pg_advisory_unlock(?)', [TEMPLATE_LOCK_ID]);
48+
}
49+
50+
try {
51+
await admin.raw(`DROP DATABASE IF EXISTS "${workerDbName}" WITH (FORCE)`);
52+
} catch {
53+
await admin.raw(`DROP DATABASE IF EXISTS "${workerDbName}"`);
54+
}
55+
await admin.raw(`CREATE DATABASE "${workerDbName}" TEMPLATE "${TEMPLATE_DB}"`);
56+
} finally {
57+
await admin.destroy();
58+
}
59+
60+
url.pathname = `/${workerDbName}`;
61+
process.env.DATABASE_URL = url.toString();
62+
}

backend/ava.config.mjs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
const concurrencyFromEnv = Number.parseInt(process.env.AVA_CONCURRENCY ?? '', 10);
2+
13
export default {
2-
require: ['./_force-exit.mjs'],
4+
require: ['./_setup-worker-db.mjs', './_force-exit.mjs'],
35
files: ['test/ava-tests/**'],
46
typescript: {
57
extensions: ['ts'],
@@ -13,5 +15,5 @@ export default {
1315
verbose: true,
1416
timeout: '5m',
1517
failFast: false,
16-
concurrency: 3,
18+
concurrency: Number.isFinite(concurrencyFromEnv) && concurrencyFromEnv > 0 ? concurrencyFromEnv : 3,
1719
};

backend/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
"start:dev": "nest start --watch --preserveWatchOutput",
1414
"start:debug": "nest start --debug --watch",
1515
"start:prod": "node dist/main",
16-
"test": "ava test/ava-tests/non-saas-tests/* --serial",
17-
"test-all": "ava --timeout=5m",
18-
"test-saas": "ava test/ava-tests/saas-tests/* ",
16+
"test": "node _run-with-timing.mjs ava test/ava-tests/non-saas-tests/* --serial",
17+
"test-all": "node _run-with-timing.mjs ava --timeout=5m",
18+
"test-all-parallel": "AVA_CONCURRENCY=8 node _run-with-timing.mjs ava --timeout=5m",
19+
"test-saas": "node _run-with-timing.mjs ava test/ava-tests/saas-tests/*",
20+
"test-fast": "AVA_CONCURRENCY=6 node _run-with-timing.mjs ava --timeout=5m 'test/ava-tests/non-saas-tests/!(*oracle*|*ibmdb2*|*cassandra*|*elasticsearch*).test.ts' 'test/ava-tests/saas-tests/!(*oracle*|*ibmdb2*|*cassandra*|*elasticsearch*).test.ts'",
1921
"typeorm": "ts-node -r tsconfig-paths/register ../node_modules/.bin/typeorm",
2022
"migration:generate": "pnpm run typeorm migration:generate -d dist/src/shared/config/datasource.config.js",
2123
"migration:create": "pnpm run typeorm migration:create -d dist/src/shared/config/datasource.config.js",

backend/test/ava-tests/non-saas-tests/non-saas-user-e2e.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,14 @@ let app: INestApplication;
2525
let currentTest: string;
2626
let _testUtils: TestUtils;
2727

28-
test.beforeEach(async () => {
28+
test.before(async () => {
2929
setSaasEnvVariable();
3030
const moduleFixture = await Test.createTestingModule({
3131
imports: [ApplicationModule, DatabaseModule],
3232
providers: [DatabaseService, TestUtils],
3333
}).compile();
3434
app = moduleFixture.createNestApplication();
3535
_testUtils = moduleFixture.get<TestUtils>(TestUtils);
36-
// await testUtils.resetDb();
3736
app.use(cookieParser());
3837
app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger)));
3938
app.useGlobalPipes(

backend/test/ava-tests/saas-tests/table-mssql-agent-e2e.test.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,30 +116,25 @@ test.beforeEach('restDatabase', async (_t) => {
116116
table.string(testTableSecondColumnName);
117117
table.timestamps();
118118
});
119-
// const primaryKeyConstraintName ='id';
120-
// await Knex.schema.alterTable(testTableName, function (t) {
121-
// t.primary([pColumnName], primaryKeyConstraintName);
122-
// });
123-
// let counter = 0;
119+
const rowsToInsert: Array<Record<string, unknown>> = [];
124120
for (let i = 0; i < testEntitiesSeedsCount; i++) {
125121
if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) {
126-
await Knex(testTableName).insert({
127-
// [pColumnName]: ++counter,
122+
rowsToInsert.push({
128123
[testTableColumnName]: testSearchedUserName,
129124
[testTableSecondColumnName]: faker.internet.email(),
130125
created_at: new Date(),
131126
updated_at: new Date(),
132127
});
133128
} else {
134-
await Knex(testTableName).insert({
135-
// [pColumnName]: ++counter,
129+
rowsToInsert.push({
136130
[testTableColumnName]: faker.person.firstName(),
137131
[testTableSecondColumnName]: faker.internet.email(),
138132
created_at: new Date(),
139133
updated_at: new Date(),
140134
});
141135
}
142136
}
137+
await Knex(testTableName).insert(rowsToInsert);
143138

144139
await Knex.destroy();
145140
});

backend/test/ava-tests/saas-tests/table-mysql-agent-e2e.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,18 +116,19 @@ test.beforeEach('restDatabase', async (_t) => {
116116
await Knex.schema.alterTable(testTableName, (t) => {
117117
t.primary([pColumnName], primaryKeyConstraintName);
118118
});
119+
const rowsToInsert: Array<Record<string, unknown>> = [];
119120
let counter = 0;
120121
for (let i = 0; i < testEntitiesSeedsCount; i++) {
121122
if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) {
122-
await Knex(testTableName).insert({
123+
rowsToInsert.push({
123124
[pColumnName]: ++counter,
124125
[testTableColumnName]: testSearchedUserName,
125126
[testTableSecondColumnName]: faker.internet.email(),
126127
created_at: new Date(),
127128
updated_at: new Date(),
128129
});
129130
} else {
130-
await Knex(testTableName).insert({
131+
rowsToInsert.push({
131132
[pColumnName]: ++counter,
132133
[testTableColumnName]: faker.person.firstName(),
133134
[testTableSecondColumnName]: faker.internet.email(),
@@ -136,6 +137,7 @@ test.beforeEach('restDatabase', async (_t) => {
136137
});
137138
}
138139
}
140+
await Knex(testTableName).insert(rowsToInsert);
139141

140142
await Knex.destroy();
141143
});

backend/test/ava-tests/saas-tests/table-oracle-agent-e2e.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,18 +116,19 @@ test.beforeEach('restDatabase', async (_t) => {
116116
await Knex.schema.alterTable(testTableName, (t) => {
117117
t.primary([pColumnName], primaryKeyConstraintName);
118118
});
119+
const rowsToInsert: Array<Record<string, unknown>> = [];
119120
let counter = 0;
120121
for (let i = 0; i < testEntitiesSeedsCount; i++) {
121122
if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) {
122-
await Knex(testTableName).insert({
123+
rowsToInsert.push({
123124
[pColumnName]: ++counter,
124125
[testTableColumnName]: testSearchedUserName,
125126
[testTableSecondColumnName]: faker.internet.email(),
126127
created_at: new Date(),
127128
updated_at: new Date(),
128129
});
129130
} else {
130-
await Knex(testTableName).insert({
131+
rowsToInsert.push({
131132
[pColumnName]: ++counter,
132133
[testTableColumnName]: faker.person.firstName(),
133134
[testTableSecondColumnName]: faker.internet.email(),
@@ -136,6 +137,7 @@ test.beforeEach('restDatabase', async (_t) => {
136137
});
137138
}
138139
}
140+
await Knex(testTableName).insert(rowsToInsert);
139141

140142
await Knex.destroy();
141143
});

backend/test/ava-tests/saas-tests/table-postgres-agent-e2e.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,18 +116,19 @@ test.beforeEach('restDatabase', async (_t) => {
116116
await Knex.schema.alterTable(testTableName, (t) => {
117117
t.primary([pColumnName], primaryKeyConstraintName);
118118
});
119+
const rowsToInsert: Array<Record<string, unknown>> = [];
119120
let counter = 0;
120121
for (let i = 0; i < testEntitiesSeedsCount; i++) {
121122
if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) {
122-
await Knex(testTableName).insert({
123+
rowsToInsert.push({
123124
[pColumnName]: ++counter,
124125
[testTableColumnName]: testSearchedUserName,
125126
[testTableSecondColumnName]: faker.internet.email(),
126127
created_at: new Date(),
127128
updated_at: new Date(),
128129
});
129130
} else {
130-
await Knex(testTableName).insert({
131+
rowsToInsert.push({
131132
[pColumnName]: ++counter,
132133
[testTableColumnName]: faker.person.firstName(),
133134
[testTableSecondColumnName]: faker.internet.email(),
@@ -136,6 +137,7 @@ test.beforeEach('restDatabase', async (_t) => {
136137
});
137138
}
138139
}
140+
await Knex(testTableName).insert(rowsToInsert);
139141

140142
await Knex.destroy();
141143
});

0 commit comments

Comments
 (0)