Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: launchNode.cleanup not killing node in last test of test group #2718

Merged
merged 39 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
23e8898
test: add test
nedsalk Jul 7, 2024
41de3f2
fix: the problem
nedsalk Jul 7, 2024
3db78c0
add comment
nedsalk Jul 7, 2024
b64dcd4
remove `tree-kill` dependency and refactor accordingly
nedsalk Jul 7, 2024
4061564
chore: changeset
nedsalk Jul 7, 2024
f19b83f
chore: changeset v2
nedsalk Jul 7, 2024
b87cb98
remove unused type
nedsalk Jul 7, 2024
db7404a
return early
nedsalk Jul 7, 2024
48cb1eb
clean up all side effects and then kill
nedsalk Jul 7, 2024
16ddc83
"a test", not "an test"
nedsalk Jul 7, 2024
07d2f6c
oops, remove `test.only`
nedsalk Jul 7, 2024
b454fa3
add test group
nedsalk Jul 7, 2024
aeb7ff8
add `.only` to be explicit about it
nedsalk Jul 7, 2024
44271a1
remove `tree-kill` from `fuels`
nedsalk Jul 8, 2024
dc8a91a
Merge remote-tracking branch 'origin/master' into ns/fix/launch-node-…
nedsalk Jul 8, 2024
3640d19
re-add accidental `bun` removal
nedsalk Jul 8, 2024
4e33a34
use object
nedsalk Jul 8, 2024
eb09115
works now?
nedsalk Jul 8, 2024
1563169
Merge branch 'master' into ns/fix/launch-node-cleanup
nedsalk Jul 8, 2024
a2526c5
Merge branch 'master' into ns/fix/launch-node-cleanup
nedsalk Jul 8, 2024
6211101
Merge branch 'master' into ns/fix/launch-node-cleanup
nedsalk Jul 8, 2024
8a1b070
Merge branch 'master' into ns/fix/launch-node-cleanup
nedsalk Jul 8, 2024
5fab757
Merge branch 'master' into ns/fix/launch-node-cleanup
nedsalk Jul 8, 2024
fcb88b0
Merge remote-tracking branch 'origin/master' into ns/fix/launch-node-…
nedsalk Jul 9, 2024
1babf70
remove side effects on `'error'` and `'exit'` effects
nedsalk Jul 9, 2024
a73ff57
cover `child.pid` as `undefined`
nedsalk Jul 9, 2024
4cf98c8
Merge branch 'master' into ns/fix/launch-node-cleanup
Torres-ssf Jul 10, 2024
823c33a
fix: kill process group
nedsalk Jul 10, 2024
bf612e3
Merge remote-tracking branch 'origin/master' into ns/fix/launch-node-…
nedsalk Jul 10, 2024
ebcf55c
verify external killing removes side-effects
nedsalk Jul 10, 2024
b7cfab6
remove unnecessary truthy check
nedsalk Jul 10, 2024
3e1b704
remove enormous timeout
nedsalk Jul 10, 2024
e7abaee
avoid dirty exit when node already killed
nedsalk Jul 10, 2024
3c0c6d5
Merge branch 'master' into ns/fix/launch-node-cleanup
nedsalk Jul 10, 2024
71d4add
Merge remote-tracking branch 'origin/master' into ns/fix/launch-node-…
nedsalk Jul 10, 2024
c4491e6
Merge branch 'master' into ns/fix/launch-node-cleanup
nedsalk Jul 10, 2024
817c6ea
Merge branch 'master' into ns/fix/launch-node-cleanup
maschad Jul 10, 2024
c3d00a2
Merge branch 'master' into ns/fix/launch-node-cleanup
nedsalk Jul 11, 2024
e006dbe
Merge branch 'master' into ns/fix/launch-node-cleanup
nedsalk Jul 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/breezy-tables-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/account": minor
"fuels": minor
---

fix!: `launchNode.cleanup` not killing node in last test of test group
1 change: 0 additions & 1 deletion packages/account/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@
"graphql-tag": "^2.12.6",
"portfinder": "^1.0.32",
"ramda": "^0.29.0",
"tree-kill": "^1.2.2",
"uuid": "^10.0.0"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { launchNode } from './launchNode';

/**
* The test runner creates a test environment per file,
* which we can use to isolate the faulty behavior.
*/
/**
* @group node
*/
describe('launchNode-singular-test', () => {
let killedNodeUrl = '';
afterAll(async () => {
await expect(fetch(killedNodeUrl)).rejects.toThrow('fetch failed');

Check failure on line 13 in packages/account/src/test-utils/launchNode-singular-test.test.ts

View workflow job for this annotation

GitHub Actions / node@18

packages/account/src/test-utils/launchNode-singular-test.test.ts > launchNode-singular-test

AssertionError: expected [Function] to throw error including 'fetch failed' but got 'Failed to parse URL from ' - Expected + Received - fetch failed + Failed to parse URL from ❯ packages/account/src/test-utils/launchNode-singular-test.test.ts:13:5

Check failure on line 13 in packages/account/src/test-utils/launchNode-singular-test.test.ts

View workflow job for this annotation

GitHub Actions / node@20

packages/account/src/test-utils/launchNode-singular-test.test.ts > launchNode-singular-test

AssertionError: expected [Function] to throw error including 'fetch failed' but got 'Failed to parse URL from ' - Expected + Received - fetch failed + Failed to parse URL from ❯ packages/account/src/test-utils/launchNode-singular-test.test.ts:13:5

Check failure on line 13 in packages/account/src/test-utils/launchNode-singular-test.test.ts

View workflow job for this annotation

GitHub Actions / node@22

packages/account/src/test-utils/launchNode-singular-test.test.ts > launchNode-singular-test

AssertionError: expected [Function] to throw error including 'fetch failed' but got 'Failed to parse URL from ' - Expected + Received - fetch failed + Failed to parse URL from ❯ packages/account/src/test-utils/launchNode-singular-test.test.ts:13:5
maschad marked this conversation as resolved.
Show resolved Hide resolved
});
test.only('synchronous cleanup kills node before test runner exits', async () => {
const { cleanup, url } = await launchNode();
killedNodeUrl = url;
cleanup();
});
});
64 changes: 8 additions & 56 deletions packages/account/src/test-utils/launchNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import * as childProcessMod from 'child_process';

import { Provider } from '../providers';

import { killNode, launchNode } from './launchNode';

type ChildProcessWithoutNullStreams = childProcessMod.ChildProcessWithoutNullStreams;
import { launchNode } from './launchNode';

vi.mock('child_process', async () => {
const mod = await vi.importActual('child_process');
Expand Down Expand Up @@ -130,63 +128,17 @@ describe('launchNode', () => {
cleanup();
});

test('should kill process only if PID exists and node is alive', () => {
const killFn = vi.fn();
const state = { isDead: true };

// should not kill
let child = {
pid: undefined,
stdout: {
removeAllListeners: () => {},
},
stderr: {
removeAllListeners: () => {},
},
} as ChildProcessWithoutNullStreams;

killNode({
child,
configPath: '',
killFn,
state,
});
test('calling cleanup multiple times does not retry process killing', async () => {
const killSpy = vi.spyOn(process, 'kill');

expect(killFn).toHaveBeenCalledTimes(0);
expect(state.isDead).toEqual(true);

// should not kill
child = {
pid: 1,
stdout: {
removeAllListeners: () => {},
},
stderr: {
removeAllListeners: () => {},
},
} as ChildProcessWithoutNullStreams;

killNode({
child,
configPath: '',
killFn,
state,
});
const { cleanup } = await launchNode();

expect(killFn).toHaveBeenCalledTimes(0);
expect(state.isDead).toEqual(true);
cleanup();

// should kill
state.isDead = false;
expect(killSpy).toHaveBeenCalledTimes(1);

killNode({
child,
configPath: '',
killFn,
state,
});
cleanup();

expect(killFn).toHaveBeenCalledTimes(1);
expect(state.isDead).toEqual(true);
expect(killSpy).toHaveBeenCalledTimes(1);
});
});
72 changes: 28 additions & 44 deletions packages/account/src/test-utils/launchNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import { randomBytes } from '@fuel-ts/crypto';
import { FuelError } from '@fuel-ts/errors';
import type { SnapshotConfigs } from '@fuel-ts/utils';
import { defaultConsensusKey, hexlify, defaultSnapshotConfigs } from '@fuel-ts/utils';
import type { ChildProcessWithoutNullStreams } from 'child_process';
import { randomUUID } from 'crypto';
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
import os from 'os';
import path from 'path';
import { getPortPromise } from 'portfinder';
import treeKill from 'tree-kill';
nedsalk marked this conversation as resolved.
Show resolved Hide resolved

import { Provider } from '../providers';
import { Signer } from '../signer';
Expand Down Expand Up @@ -58,33 +56,6 @@ export type LaunchNodeResult = Promise<{
snapshotDir: string;
}>;

export type KillNodeParams = {
child: ChildProcessWithoutNullStreams;
configPath: string;
killFn: (pid: number) => void;
state: {
isDead: boolean;
};
};

export const killNode = (params: KillNodeParams) => {
const { child, configPath, state, killFn } = params;
if (!state.isDead) {
if (child.pid) {
state.isDead = true;
killFn(Number(child.pid));
}

// Remove all the listeners we've added.
child.stderr.removeAllListeners();

// Remove the temporary folder and all its contents.
if (existsSync(configPath)) {
rmSync(configPath, { recursive: true });
}
}
};

function getFinalStateConfigJSON({ stateConfig, chainConfig }: SnapshotConfigs) {
const defaultCoins = defaultSnapshotConfigs.stateConfig.coins.map((coin) => ({
...coin,
Expand Down Expand Up @@ -232,7 +203,7 @@ export const launchNode = async ({
'--debug',
...remainingArgs,
].flat(),
{ stdio: 'pipe' }
{ stdio: 'pipe', detached: true }
);

if (loggingEnabled) {
Expand All @@ -242,13 +213,26 @@ export const launchNode = async ({
});
}

const cleanupConfig: KillNodeParams = {
child,
configPath: tempDir,
killFn: treeKill,
state: {
isDead: false,
},
const childState = {
isDead: false,
};

const cleanup = () => {
if (childState.isDead) {
return;
}
childState.isDead = true;

// Remove all the listeners we've added.
child.stderr.removeAllListeners();

// Remove the temporary folder and all its contents.
if (existsSync(tempDir)) {
rmSync(tempDir, { recursive: true });
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
process.kill(-child.pid!);
nedsalk marked this conversation as resolved.
Show resolved Hide resolved
};

// Look for a specific graphql start point in the output.
Expand All @@ -264,7 +248,7 @@ export const launchNode = async ({

// Resolve with the cleanup method.
resolve({
cleanup: () => killNode(cleanupConfig),
cleanup,
ip: realIp,
port: realPort,
url: `http://${realIp}:${realPort}/v1/graphql`,
Expand All @@ -279,18 +263,18 @@ export const launchNode = async ({
});

// Process exit.
process.on('exit', () => killNode(cleanupConfig));
process.on('exit', cleanup);

// Catches ctrl+c event.
process.on('SIGINT', () => killNode(cleanupConfig));
process.on('SIGINT', cleanup);

// Catches "kill pid" (for example: nodemon restart).
process.on('SIGUSR1', () => killNode(cleanupConfig));
process.on('SIGUSR2', () => killNode(cleanupConfig));
process.on('SIGUSR1', cleanup);
process.on('SIGUSR2', cleanup);

// Catches uncaught exceptions.
process.on('beforeExit', () => killNode(cleanupConfig));
process.on('uncaughtException', () => killNode(cleanupConfig));
process.on('beforeExit', cleanup);
process.on('uncaughtException', cleanup);

child.on('error', reject);
});
Expand Down
9 changes: 0 additions & 9 deletions packages/fuels/src/cli/commands/dev/autoStartFuelCore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { defaultConsensusKey } from '@fuel-ts/utils';
import type { ChildProcessWithoutNullStreams } from 'child_process';
import { getPortPromise } from 'portfinder';

import { launchNode } from '../../../test-utils';
Expand All @@ -15,14 +14,6 @@ export type FuelCoreNode = {
killChildProcess: () => void;
};

export type KillNodeParams = {
core: ChildProcessWithoutNullStreams;
killFn: (pid: number) => void;
state: {
isDead: boolean;
};
};

export const autoStartFuelCore = async (config: FuelsConfig) => {
let fuelCore: FuelCoreNode | undefined;

Expand Down
Loading
Loading