Skip to content

Commit

Permalink
feat(processes): waitForExpectedResult helper (#461)
Browse files Browse the repository at this point in the history
  • Loading branch information
wellwelwel committed Jun 25, 2024
1 parent 3c09818 commit 7e08d59
Show file tree
Hide file tree
Showing 23 changed files with 483 additions and 66 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ Enjoying **Poku**? Give him a star to show your support 🌟

## Why does Poku exist?

**Poku** makes testing easy and takes on the testers' difficulties to _let you focus on your tests_:
💡 **Poku** makes testing easy and brings the [native **JavaScript** syntax back to tests](https://poku.io/docs/philosophy), letting you to write tests intuitively — _just like in real **JavaScript** code_.

<img width="16" height="16" alt="check" src="https://raw.githubusercontent.com/wellwelwel/poku/main/.github/assets/readme/check.svg"> No configurations<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><img width="16" height="16" alt="check" src="https://raw.githubusercontent.com/wellwelwel/poku/main/.github/assets/readme/check.svg"> Auto detect **ESM**, **CJS**, and **TypeScript** files<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><img width="16" height="16" alt="check" src="https://raw.githubusercontent.com/wellwelwel/poku/main/.github/assets/readme/check.svg"> Run the same test suite for [**Node.js**][node-version-url] _(6+)_, [**Bun**][bun-version-url] and [**Deno**][deno-version-url]<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><img width="16" height="16" alt="check" src="https://raw.githubusercontent.com/wellwelwel/poku/main/.github/assets/readme/check.svg"> Run the same test suite for [**Node.js**][node-version-url], [**Bun**][bun-version-url] and [**Deno**][deno-version-url]<br />

<img width="16" height="16" alt="check" src="https://raw.githubusercontent.com/wellwelwel/poku/main/.github/assets/readme/check.svg"> Easier and Less Verbose<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><img width="16" height="16" alt="check" src="https://raw.githubusercontent.com/wellwelwel/poku/main/.github/assets/readme/check.svg"> [**Node.js**][node-version-url] familiar **API**<br />
Expand All @@ -54,8 +54,6 @@ Enjoying **Poku**? Give him a star to show your support 🌟
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><img width="16" height="16" alt="check" src="https://raw.githubusercontent.com/wellwelwel/poku/main/.github/assets/readme/check.svg"> **Performant** and **lightweight**<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><img width="16" height="16" alt="check" src="https://raw.githubusercontent.com/wellwelwel/poku/main/.github/assets/readme/check.svg"> Compatible with **coverage** tools

> 💡 **Poku** brings the [essence of **JavaScript** back to testing](https://poku.io/docs/philosophy), allowing to use your knowledge to create tests intuitively.
---

## Quickstart
Expand Down Expand Up @@ -180,6 +178,7 @@ deno run npm:poku
- [**startService**](https://poku.io/docs/documentation/helpers/startService) _(run files in background)_
- [**kill**](https://poku.io/docs/documentation/helpers/processes/kill) _(terminate ports, port ranges, and PIDs)_
- [**waitForPort**](https://poku.io/docs/documentation/helpers/processes/wait-for-port) _(wait for specified ports to become active)_
- [**waitForExpectedResult**](https://poku.io/docs/documentation/helpers/processes/wait-for-expected-result) _(retry until an expected result or times out)_
- [**getPIDs**](https://poku.io/docs/documentation/helpers/processes/get-pids) _(debug processes IDs using ports and port ranges)_
- _and much more_ 👇🏻

Expand Down Expand Up @@ -227,6 +226,8 @@ Please check the [**SECURITY.md**](https://github.com/wellwelwel/poku/blob/main/
- [~**170x** lighter than **Jest**](https://pkg-size.dev/jest)
- [~**40x** lighter than **Mocha** + **Chai**](https://pkg-size.dev/mocha%20chai)

> **Poku** size is highly significant in development to ensure cost-saving **CI** that require servers that charge for storage and usage.
---

### Limitations
Expand Down
17 changes: 15 additions & 2 deletions src/@types/processes.ts → src/@types/wait-for.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type WaitForPortOptions = {
export type WaitForExpectedResultOptions = {
/**
* Retry interval in milliseconds
*
Expand All @@ -23,6 +23,19 @@ export type WaitForPortOptions = {
* @default 0
*/
delay?: number;
/**
* Ensure strict comparisons.
*
* - For **Bun** users, this option isn't necessary.
*
* ---
*
* @default false
*/
strict?: boolean;
};

export type WaitForPortOptions = {
/**
* Host to check the port on.
*
Expand All @@ -31,4 +44,4 @@ export type WaitForPortOptions = {
* @default "localhost"
*/
host?: string;
};
} & Omit<WaitForExpectedResultOptions, 'strict'>;
32 changes: 23 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
/* c8 ignore start */

// Essentials
export { poku } from './modules/poku.js';
export { assert } from './modules/assert.js';

// Helpers
export { test } from './modules/test.js';
export { describe } from './modules/describe.js';
export { it } from './modules/it.js';
export { log } from './modules/log.js';
export { assertPromise } from './modules/assert-promise.js';
export { beforeEach, afterEach } from './modules/each.js';
export { publicListFiles as listFiles } from './modules/list-files-sync.js';
export { startService, startScript } from './modules/create-service.js';
export { getPIDs, kill } from './modules/processes.js';
export { sleep, waitForPort } from './modules/wait-for.js';
export { exit } from './modules/exit.js';
export { docker } from './modules/container.js';
export { startScript, startService } from './modules/create-service.js';
export {
waitForExpectedResult,
waitForPort,
sleep,
} from './modules/wait-for.js';
export { kill, getPIDs } from './modules/processes.js';
export { exit } from './modules/exit.js';
export { log } from './modules/log.js';
export { publicListFiles as listFiles } from './modules/list-files-sync.js';
export { assertPromise } from './modules/assert-promise.js';

// Types
export type { Code } from './@types/code.js';
export type { Configs } from './@types/poku.js';
export type { WaitForPortOptions } from './@types/processes.js';
export type { Configs as ListFilesConfigs } from './@types/list-files.js';
export type {
DockerComposeConfigs,
DockerfileConfigs,
Expand All @@ -25,4 +33,10 @@ export type {
StartServiceOptions,
StartScriptOptions,
} from './@types/background-process.js';
export type {
WaitForExpectedResultOptions,
WaitForPortOptions,
} from './@types/wait-for.js';
export type { Configs as ListFilesConfigs } from './@types/list-files.js';

/* c8 ignore stop */
75 changes: 56 additions & 19 deletions src/modules/wait-for.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/* c8 ignore next */
import type { WaitForPortOptions } from '../@types/processes.js';
import type {
WaitForExpectedResultOptions,
WaitForPortOptions,
} from '../@types/wait-for.js';
import { createConnection } from 'node:net';
import { deepEqual, deepStrictEqual } from 'node:assert';

const checkPort = (port: number, host: string): Promise<boolean> =>
new Promise((resolve) => {
Expand All @@ -18,9 +22,7 @@ const checkPort = (port: number, host: string): Promise<boolean> =>
/* c8 ignore stop */
});

/**
* Wait until the defined milliseconds.
*/
/** Wait until the defined milliseconds. */
export const sleep = (milliseconds: number): Promise<void> => {
/* c8 ignore start */
if (!Number.isInteger(milliseconds)) {
Expand All @@ -31,23 +33,19 @@ export const sleep = (milliseconds: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
};

/* c8 ignore start */ // c8 bug
/**
* Wait until the defined port is active.
*/
export const waitForPort = async (
port: number,
options?: WaitForPortOptions
/** Wait until a result is equal the expected value */
export const waitForExpectedResult = async (
callback: () => unknown | Promise<unknown>,
expectedResult: unknown,
options?: WaitForExpectedResultOptions
): Promise<void> => {
/* c8 ignore stop */
const delay = options?.delay || 0;
const interval = options?.interval || 100;
const timeout = options?.timeout || 60000;
const host = options?.host || 'localhost';

/* c8 ignore start */
if (!Number.isInteger(port)) {
throw new Error('Port must be an integer.');
if (typeof callback !== 'function') {
throw new Error('Callback must be a function.');
}

if (!Number.isInteger(interval)) {
Expand All @@ -69,13 +67,30 @@ export const waitForPort = async (

// eslint-disable-next-line no-constant-condition
while (true) {
const hasPort = await checkPort(port, host);

if (hasPort) break;
const result = await callback();

if (typeof expectedResult === 'function') {
if (typeof result === 'function' && result.name === expectedResult.name)
break;
} else if (typeof expectedResult === 'symbol') {
if (
typeof result === 'symbol' &&
String(result) === String(expectedResult)
)
break;
} else {
try {
options?.strict
? deepStrictEqual(result, expectedResult)
: deepEqual(result, expectedResult);
break;
/* c8 ignore next */
} catch {}
}

/* c8 ignore start */
if (Date.now() - startTime >= timeout) {
throw new Error(`Timeout waiting for port ${port} to become active`);
throw new Error(`Timeout`);
}
/* c8 ignore stop */

Expand All @@ -84,3 +99,25 @@ export const waitForPort = async (

await sleep(delay);
};

/* c8 ignore start */ // c8 bug
/** Wait until the defined port is active. */
export const waitForPort = async (
port: number,
options?: WaitForPortOptions
): Promise<void> => {
/* c8 ignore stop */
const host = options?.host || 'localhost';

/* c8 ignore start */
if (!Number.isInteger(port)) {
throw new Error('Port must be an integer.');
}
/* c8 ignore stop */

await waitForExpectedResult(
async () => await checkPort(port, host),
true,
options
);
};
4 changes: 4 additions & 0 deletions test/integration/import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ index.test('Import Suite', () => {
index.assert.ok(index.log, 'Importing log helper');
index.assert.ok(index.test, 'Importing test helper');
index.assert.ok(index.sleep, 'Importing sleep helper');
index.assert.ok(
index.waitForExpectedResult,
'Importing waitForExpectedResult helper'
);
index.assert.ok(index.waitForPort, 'Importing waitForPort helper');
index.assert.ok(index.exit, 'Importing exit helper');
index.assert.ok(index.listFiles, 'Importing listFiles helper');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import process from 'node:process';
import { createServer, Server } from 'node:http';
import { describe } from '../../../src/modules/describe.js';
import { test } from '../../../src/modules/test.js';
import { it } from '../../../src/modules/it.js';
import { assert } from '../../../src/modules/assert.js';
import { waitForPort, sleep } from '../../../src/modules/wait-for.js';
import { waitForPort } from '../../../src/modules/wait-for.js';
import { kill } from '../../../src/modules/processes.js';

const startServer = (port: number): Promise<Server> =>
Expand All @@ -19,8 +18,10 @@ const startServer = (port: number): Promise<Server> =>
const stopServer = (server: Server): Promise<void> =>
new Promise((resolve) => server.close(() => resolve(undefined)));

describe('Wait For Port', async () => {
await kill.range(8000, 8003);
test('Wait For Port', async () => {
try {
await kill.range(8000, 8003);
} catch {}

await Promise.all([
it(async () => {
Expand Down Expand Up @@ -60,7 +61,7 @@ describe('Wait For Port', async () => {
} catch (error) {
assert.strictEqual(
(error as Error).message,
`Timeout waiting for port ${port} to become active`,
`Timeout`,
'Expected timeout for missing port'
);
}
Expand All @@ -80,19 +81,6 @@ describe('Wait For Port', async () => {
}
}),

it(async () => {
const startTime = Date.now();
const delay = 500;
await sleep(delay);
const elapsedTime = Date.now() - startTime;
const margin = 250;

assert.ok(
elapsedTime >= delay - margin && elapsedTime <= delay + margin,
`Expected sleep time to be around ${delay}ms (±${margin}ms), but was ${elapsedTime}ms`
);
}),

it(async () => {
try {
await waitForPort(NaN, { timeout: 2000 });
Expand All @@ -106,12 +94,8 @@ describe('Wait For Port', async () => {
}
}),
]);
});

process.on('exit', () => {
process.on('exit', () => {
try {
kill.range(8000, 8003);
} catch {}
});
try {
await kill.range(8000, 8003);
} catch {}
});
16 changes: 16 additions & 0 deletions test/unit/wait-for/sleep.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { test } from '../../../src/modules/test.js';
import { assert } from '../../../src/modules/assert.js';
import { sleep } from '../../../src/modules/wait-for.js';

test('Sleep "mini" helper', async () => {
const startTime = Date.now();
const delay = 500;
await sleep(delay);
const elapsedTime = Date.now() - startTime;
const margin = 250;

assert.ok(
elapsedTime >= delay - margin && elapsedTime <= delay + margin,
`Expected sleep time to be around ${delay}ms (±${margin}ms), but was ${elapsedTime}ms`
);
});
Loading

0 comments on commit 7e08d59

Please sign in to comment.