Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
115 changes: 115 additions & 0 deletions packages/cli-tools/src/__tests__/isPackagerRunning.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {createServer} from 'http';
import {AddressInfo} from 'net';
import {runInThisContext} from 'vm';
import isPackagerRunning from '../isPackagerRunning';
import findDevServerPort from '../findDevServerPort';
import {askForPortChange} from '../port';

jest.mock('../port', () => ({
askForPortChange: jest.fn(),
logAlreadyRunningBundler: jest.fn(),
logChangePortInstructions: jest.fn(),
}));

const withServer = async (
status: number,
body: string,
run: (port: number) => Promise<void>,
headers: Record<string, string> = {},
) => {
const server = createServer((_request, response) => {
response.writeHead(status, headers);
response.end(body);
});
await new Promise<void>((resolve) => server.listen(0, resolve));
try {
await run((server.address() as AddressInfo).port);
} finally {
await new Promise<void>((resolve, reject) =>
server.close((error) => (error ? reject(error) : resolve())),
);
}
};

// Jest 26 does not expose Node's built-in fetch in its test environment.
const originalFetch = global.fetch;
beforeAll(() => {
global.fetch = runInThisContext('fetch');
});
afterAll(() => {
global.fetch = originalFetch;
});

beforeEach(() => {
jest.clearAllMocks();
(askForPortChange as jest.Mock).mockResolvedValue({change: true});
});

test.each([400, 404, 500])(
'recognizes a port occupied by an HTTP %i server',
async (status) => {
await withServer(status, 'Not Metro', async (port) => {
expect(await isPackagerRunning(port)).toBe('unrecognized');
});
},
);

test.each([400, 404, 500])(
'offers another port when the current server responds with HTTP %i',
async (status) => {
await withServer(status, 'Not Metro', async (port) => {
const result = await findDevServerPort(port, '/test/project');
expect(askForPortChange).toHaveBeenCalledWith(port, result.port);
expect(result.port).toBeGreaterThan(port);
expect(result.startPackager).toBe(true);
});
},
);

test('recognizes a successful response from another server', async () => {
await withServer(200, 'Not Metro', async (port) => {
expect(await isPackagerRunning(port)).toBe('unrecognized');
});
});

test('recognizes Metro and its project root', async () => {
await withServer(
200,
'packager-status:running',
async (port) => {
expect(await isPackagerRunning(port)).toEqual({
status: 'running',
root: '/test/project',
});
},
{'X-React-Native-Project-Root': '/test/project'},
);
});

test('does not start another Metro instance for the same project', async () => {
await withServer(
200,
'packager-status:running',
async (port) => {
expect(await findDevServerPort(port, '/test/project')).toEqual({
port,
startPackager: false,
});
expect(askForPortChange).not.toHaveBeenCalled();
},
{'X-React-Native-Project-Root': '/test/project'},
);
});

test('returns not_running when no server is listening', async () => {
let port = 0;
await withServer(200, '', async (listeningPort) => {
port = listeningPort;
});
expect(await isPackagerRunning(port)).toBe('not_running');
expect(await findDevServerPort(port, '/test/project')).toEqual({
port,
startPackager: true,
});
expect(askForPortChange).not.toHaveBeenCalled();
});
6 changes: 4 additions & 2 deletions packages/cli-tools/src/isPackagerRunning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import {fetch} from './fetch';
import {CLIError} from './errors';

/**
* Indicates whether or not the packager is running. It returns a promise that
Expand Down Expand Up @@ -42,8 +43,9 @@ async function isPackagerRunning(
}

return 'unrecognized';
} catch (_error) {
return 'not_running';
} catch (error) {
// An HTTP error response still means a process is listening on this port.
return error instanceof CLIError ? 'unrecognized' : 'not_running';
}
}

Expand Down
Loading