fix(server): shard the server-test unit job to stop the intermittent crash (#23009)
## Problem `server-test` fails intermittently: exit 1 with **no `FAIL` line and no `Test Suites:/Tests:` summary** — the jest run is aborted mid-way, before the reporter's `onRunComplete`. ## Root cause The `test` target runs the entire unit suite (~6,600 tests) in **one in-band jest process on a single runner VM** (`nx.json` sets `maxWorkers: 1` for the `ci` configuration). A few minutes in, that process is killed by an **external `SIGKILL`** — confirmed *not* OOM (~15 GB free at kill time, no cgroup `oom_kill`) and *not* an in-process crash (a Node diagnostic report armed with `--report-on-fatalerror` + `--report-uncaught-exception` writes nothing). The whole-run kill is why it fails intermittently with no summary. `maxWorkers=2` on one VM still dies, so the threshold is **per-VM**, not per-process. ## Fix Shard the unit suite across VMs, the same way `server-integration-test` already does: - A `twenty-server` `test:ci` target runs jest directly (so `--shard` forwards) with `dependsOn: ["^build"]` so the workspace deps are built. - `server-test` becomes a 4-way matrix; each shard runs a quarter of the suite, well under the kill threshold. - `ci-server-status-check` already aggregates `server-test`, so required checks are unchanged. Also provides two mocks a completed run needs but the SIGKILL had been masking in `ApplicationRegistrationService.upsertFromCatalog` unit tests: the `MetricsService` provider and `applicationRegistrationRepository.createQueryBuilder`.
This commit is contained in:
@@ -376,6 +376,10 @@ jobs:
|
||||
needs: server-build
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4]
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
@@ -389,11 +393,8 @@ jobs:
|
||||
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
|
||||
- name: Build twenty-shared
|
||||
run: npx nx build twenty-shared
|
||||
- name: Server / Run Tests
|
||||
uses: ./.github/actions/nx-affected
|
||||
with:
|
||||
tag: scope:backend
|
||||
tasks: test
|
||||
- name: Server / Run Tests (shard ${{ matrix.shard }}/4)
|
||||
run: npx nx run twenty-server:test:ci --shard=${{ matrix.shard }}/4
|
||||
|
||||
server-integration-test:
|
||||
timeout-minutes: 30
|
||||
|
||||
@@ -36,6 +36,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"test:ci": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": ["^build"],
|
||||
"options": {
|
||||
"cwd": "packages/twenty-server",
|
||||
"command": "jest --config ./jest.config.mjs --ci --runInBand"
|
||||
}
|
||||
},
|
||||
"build:packageJson": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"options": {
|
||||
|
||||
+13
@@ -10,6 +10,7 @@ import { ApplicationEntity } from 'src/engine/core-modules/application/applicati
|
||||
import { CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service';
|
||||
import { CoreEntityCacheService } from 'src/engine/core-entity-cache/services/core-entity-cache.service';
|
||||
import { ServerFileStorageService } from 'src/engine/core-modules/file-storage/services/server-file-storage.service';
|
||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
describe('ApplicationRegistrationService - upsertFromCatalog', () => {
|
||||
@@ -18,6 +19,7 @@ describe('ApplicationRegistrationService - upsertFromCatalog', () => {
|
||||
findOne: jest.Mock;
|
||||
save: jest.Mock;
|
||||
create: jest.Mock;
|
||||
createQueryBuilder: jest.Mock;
|
||||
};
|
||||
|
||||
const catalogParams = {
|
||||
@@ -45,6 +47,13 @@ describe('ApplicationRegistrationService - upsertFromCatalog', () => {
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn(),
|
||||
create: jest.fn((entity) => entity),
|
||||
createQueryBuilder: jest.fn(() => ({
|
||||
update: jest.fn().mockReturnThis(),
|
||||
set: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
execute: jest.fn().mockResolvedValue({ affected: 0 }),
|
||||
})),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -82,6 +91,10 @@ describe('ApplicationRegistrationService - upsertFromCatalog', () => {
|
||||
provide: CoreEntityCacheService,
|
||||
useValue: { invalidate: jest.fn() },
|
||||
},
|
||||
{
|
||||
provide: MetricsService,
|
||||
useValue: { incrementCounterBy: jest.fn() },
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user