Skip to content

Commit

Permalink
fix: stop piping into process.stdout/stderr and use console.log (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
nedsalk committed Jul 3, 2024
1 parent 8676a9e commit 7befc6a
Show file tree
Hide file tree
Showing 21 changed files with 209 additions and 257 deletions.
8 changes: 8 additions & 0 deletions .changeset/flat-ways-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@fuel-ts/abi-typegen": patch
"@fuel-ts/account": minor
"create-fuels": patch
"fuels": patch
---

fix!: stop piping into `process.stdout/stderr` and use `console.log`
5 changes: 3 additions & 2 deletions packages/abi-typegen/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ describe('cli.ts', () => {
test('should handle errors when running cli action', () => {
const runTypegenError = new Error('Pretty message');

const { exit, err } = mockDeps({ runTypegenError });
const logSpy = vi.spyOn(console, 'log');
const { exit } = mockDeps({ runTypegenError });

const inputs = ['*-no-abis-here.json'];
const output = './aything';
Expand All @@ -175,6 +176,6 @@ describe('cli.ts', () => {
});

expect(exit).toBeCalledWith(1);
expect(err).toBeCalledWith(`error: ${runTypegenError.message}\n`);
expect(logSpy).toBeCalledWith(`error: ${runTypegenError.message}`);
});
});
3 changes: 2 additions & 1 deletion packages/abi-typegen/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export function runCliAction(options: ICliParams) {
silent: !!silent,
});
} catch (err) {
process.stderr.write(`error: ${(<Error>err).message}\n`);
// eslint-disable-next-line no-console
console.log(`error: ${(<Error>err).message}`);
process.exit(1);
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/abi-typegen/src/runTypegen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ describe('runTypegen.js', () => {
});

test('should log messages to stdout', async () => {
const stdoutWrite = vi.spyOn(process.stdout, 'write').mockResolvedValue(true);
const logSpy = vi.spyOn(console, 'log');

// setup temp sway project
const project = getTypegenForcProject(AbiTypegenProjectsEnum.SCRIPT);
Expand Down Expand Up @@ -189,7 +189,7 @@ describe('runTypegen.js', () => {
// validates execution was ok
expect(error).toBeFalsy();

expect(stdoutWrite).toHaveBeenCalledTimes(5);
expect(logSpy).toHaveBeenCalledTimes(5);
});

test('should raise error for non-existent Script BIN file', async () => {
Expand Down Expand Up @@ -279,7 +279,7 @@ describe('runTypegen.js', () => {
cpSync(fromBin, toBin);

// mocking
const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
const logSpy = vi.spyOn(console, 'log');

// executes program
const fn = () =>
Expand Down Expand Up @@ -313,7 +313,7 @@ describe('runTypegen.js', () => {
expect(existsSync(f)).toEqual(true);
});

expect(write).toHaveBeenCalled();
expect(logSpy).toHaveBeenCalled();
});

test('should error for no ABI in inputs', async () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/abi-typegen/src/runTypegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export function runTypegen(params: IGenerateFilesParams) {

function log(...args: unknown[]) {
if (!silent) {
process.stdout.write(`${args.join(' ')}\n`);
// eslint-disable-next-line no-console
console.log(args.join(' '));
}
}

Expand Down
187 changes: 70 additions & 117 deletions packages/account/src/test-utils/launchNode.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { safeExec } from '@fuel-ts/errors/test-utils';
import { ErrorCode } from '@fuel-ts/errors';
import { safeExec, expectToThrowFuelError } from '@fuel-ts/errors/test-utils';
import { defaultSnapshotConfigs } from '@fuel-ts/utils';
import { waitUntilUnreachable } from '@fuel-ts/utils/test-utils';
import * as childProcessMod from 'child_process';

import type { LaunchNodeOptions } from './launchNode';
import { Provider } from '../providers';

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

type ChildProcessWithoutNullStreams = childProcessMod.ChildProcessWithoutNullStreams;
Expand All @@ -15,51 +18,6 @@ vi.mock('child_process', async () => {
};
});

/**
* This should mimic the stderr.on('data') event, returning both
* success and error messages, as strings. These messages are like
* the ones from `fuel-core` startup log messages. We filter them
* to know fuel-core state.
*/
function mockSpawn(params: { shouldError: boolean } = { shouldError: false }) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stderrOn = (eventName: string, fn: (data: any) => void) => {
if (eventName === 'data') {
if (params.shouldError) {
// The `IO error` message simulates a possible fuel-core error log message
fn('IO error');
} else {
// The `Binding GraphQL provider to` message simulates a fuel-core
// successful startup log message, usually meaning that the node
// is up and waiting for connections
fn('Binding GraphQL provider to 0.0.0.0:4000');
}
}
};

const innerMocks = {
on: vi.fn(),
stderr: {
pipe: vi.fn(),
on: vi.fn(stderrOn),
removeAllListeners: vi.fn(),
},
stdout: {
pipe: vi.fn(),
removeAllListeners: vi.fn(),
},
} as unknown as ChildProcessWithoutNullStreams;

const spawn = vi.spyOn(childProcessMod, 'spawn').mockReturnValue(innerMocks);

return { spawn, innerMocks };
}

const defaultLaunchNodeConfig: Partial<LaunchNodeOptions> = {
ip: '0.0.0.0',
port: '4000',
};

/**
* @group node
*/
Expand All @@ -73,110 +31,105 @@ describe('launchNode', () => {
});

it('cleanup kills the started node', async () => {
const { cleanup, url } = await launchNode({});
const { cleanup, url } = await launchNode();
expect(await fetch(url)).toBeTruthy();

cleanup();

await waitUntilUnreachable(url);
});

test('should start `fuel-core` node using built-in binary', async () => {
mockSpawn();
test('should start `fuel-core` node using system binary', async () => {
const spawnSpy = vi.spyOn(childProcessMod, 'spawn');

const { cleanup, ip, port } = await launchNode({
...defaultLaunchNodeConfig,
});
process.env.FUEL_CORE_PATH = '';

expect(ip).toBe('0.0.0.0');
expect(port).toBe('4000');
cleanup();
});
const { result } = await safeExec(async () => launchNode());

test('should start `fuel-core` node using system binary', async () => {
mockSpawn();

const { cleanup, ip, port } = await launchNode(defaultLaunchNodeConfig);
const command = spawnSpy.mock.calls[0][0];
expect(command).toEqual('fuel-core');

expect(ip).toBe('0.0.0.0');
expect(port).toBe('4000');
process.env.FUEL_CORE_PATH = 'fuels-core';

cleanup();
/**
* result can be undefined when running in CI and fuel-core is not installed
* meaning that spawn(fuel-core, ...) threw an error
*/
if (result !== undefined) {
(await result)?.cleanup();
}
});

test('should start `fuel-core` node with custom binary', async () => {
const { spawn } = mockSpawn();
test('should start `fuel-core` node using custom binary', async () => {
const spawnSpy = vi.spyOn(childProcessMod, 'spawn');

const { cleanup, ip, port } = await launchNode({
...defaultLaunchNodeConfig,
fuelCorePath: 'fuels-core',
const fuelCorePath = './my-fuel-core-binary-path';
const { error } = await safeExec(async () => {
await launchNode({ fuelCorePath, loggingEnabled: true });
});

expect(ip).toBe('0.0.0.0');
expect(port).toBe('4000');
expect(spawn).toBeCalledWith('fuels-core', expect.any(Array), expect.any(Object));
expect(error).toBeTruthy();

cleanup();
const command = spawnSpy.mock.calls[0][0];
expect(command).toEqual(fuelCorePath);
});

test('should start `fuel-core` node with custom binaries', async () => {
const { spawn } = mockSpawn();

const { cleanup, ip, port } = await launchNode({
...defaultLaunchNodeConfig,
fuelCorePath: 'custom-fuels-core',
});
test('reads FUEL_CORE_PATH environment variable for fuel-core binary', async () => {
const spawnSpy = vi.spyOn(childProcessMod, 'spawn');
process.env.FUEL_CORE_PATH = 'fuels-core';
const { cleanup, url } = await launchNode();
await Provider.create(url);

expect(ip).toBe('0.0.0.0');
expect(port).toBe('4000');
expect(spawn).toBeCalledWith('custom-fuels-core', expect.any(Array), expect.any(Object));
const command = spawnSpy.mock.calls[0][0];
expect(command).toEqual('fuels-core');

cleanup();
});

test('should throw on error', async () => {
const { innerMocks } = mockSpawn({ shouldError: true });

const { error: safeError, result } = await safeExec(async () =>
launchNode(defaultLaunchNodeConfig)
test('should throw on error and log error message', async () => {
const logSpy = vi.spyOn(console, 'log');

const invalidCoin = {
asset_id: 'whatever',
tx_id: '',
output_index: 0,
tx_pointer_block_height: 0,
tx_pointer_tx_idx: 0,
owner: '',
amount: 0,
};

const error = await expectToThrowFuelError(
async () =>
launchNode({
loggingEnabled: false,
snapshotConfig: {
...defaultSnapshotConfigs,
stateConfig: {
coins: [invalidCoin],
messages: [],
},
},
}),
{
code: ErrorCode.NODE_LAUNCH_FAILED,
}
);

expect(safeError).toBeTruthy();
expect(result).not.toBeTruthy();
expect(error).toBeTruthy();

expect(innerMocks.on).toHaveBeenCalledTimes(1);
expect(innerMocks.stderr.pipe).toHaveBeenCalledTimes(1);
expect(innerMocks.stdout.pipe).toHaveBeenCalledTimes(0);
expect(logSpy).toHaveBeenCalledWith(error?.message);
});

test('should pipe stdout', async () => {
vi.spyOn(process.stdout, 'write');

const { innerMocks } = mockSpawn();

const { cleanup } = await launchNode(defaultLaunchNodeConfig);

expect(innerMocks.stderr.pipe).toHaveBeenCalledTimes(1);
expect(innerMocks.stdout.pipe).toHaveBeenCalledTimes(0);
test('logs fuel-core outputs via console.log', async () => {
const logSpy = vi.spyOn(console, 'log');

const { cleanup } = await launchNode({ loggingEnabled: true });
const logs = logSpy.mock.calls.map((call) => call[0]);
expect(logs.some((log) => log.includes('Binding GraphQL provider'))).toBe(true);
cleanup();
});

test('should pipe stdout and stderr', async () => {
vi.spyOn(process.stderr, 'write');
vi.spyOn(process.stdout, 'write');

const { innerMocks } = mockSpawn();

await launchNode({
...defaultLaunchNodeConfig,
debugEnabled: true,
});

expect(innerMocks.stderr.pipe).toHaveBeenCalledTimes(1);
expect(innerMocks.stdout.pipe).toHaveBeenCalledTimes(1);
});

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

0 comments on commit 7befc6a

Please sign in to comment.