@harperfast/integration-testing 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +291 -0
- package/dist/harperLifecycle.js +315 -0
- package/dist/harperLifecycle.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/loopbackAddressPool.js +337 -0
- package/dist/loopbackAddressPool.js.map +1 -0
- package/dist/run.js +94 -0
- package/dist/run.js.map +1 -0
- package/dist/targz.js +20 -0
- package/dist/targz.js.map +1 -0
- package/package.json +55 -0
- package/scripts/setup-loopback.sh +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# @harperfast/integration-testing
|
|
2
|
+
|
|
3
|
+
Integration testing utilities for Harper-based projects. Provides framework-agnostic Harper instance lifecycle management, a concurrent loopback address pool, directory compression utilities, and an optional preconfigured Node.js test runner script.
|
|
4
|
+
|
|
5
|
+
## Testing Ethos
|
|
6
|
+
|
|
7
|
+
There are three distinct levels of testing in the Harper ecosystem:
|
|
8
|
+
|
|
9
|
+
- **Unit** tests validate isolated logic against source or built code directly, relying on mocks or stubs in place of real dependencies.
|
|
10
|
+
- **Integration** tests run against the actual Harper software, but locally and ephemerally. Harper is started as a child process for each test suite, given a temporary install directory, and torn down when the suite finishes. There is no remote infrastructure to manage.
|
|
11
|
+
- **End-to-end** tests go further: they target a deployment that mirrors production as closely as possible — think a staging environment with real infrastructure, remote systems, and operational configuration.
|
|
12
|
+
|
|
13
|
+
This project is strictly for **integration** testing.
|
|
14
|
+
|
|
15
|
+
The distinction matters because integration tests give you confidence in Harper's actual behavior without the cost and complexity of managing a deployed environment. You get the real software, not mocks, but with fast local iteration.
|
|
16
|
+
|
|
17
|
+
Regardless of the framework or runner used, integration tests should strive to be:
|
|
18
|
+
|
|
19
|
+
- **Independent**: No dependency on execution order or shared state with other tests
|
|
20
|
+
- **Hermetic**: Self-contained with no external side-effects
|
|
21
|
+
- **Deterministic**: The same input always produces the same output
|
|
22
|
+
|
|
23
|
+
These properties are what make safe concurrent execution possible — without them, parallelization produces flaky, order-dependent failures.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm install --save-dev @harperfast/integration-testing
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Framework Agnosticism
|
|
32
|
+
|
|
33
|
+
The core lifecycle and loopback pool APIs in this package — `startHarper`, `teardownHarper`, `killHarper`, and the loopback utilities — are **not tied to any specific test runner or framework**. They spawn and manage Harper child processes and allocate loopback addresses safely across concurrent processes using a file-based locking mechanism. This means they can be used with any testing framework that supports async setup and teardown: Node.js `node:test`, Vitest, Playwright, or anything else.
|
|
34
|
+
|
|
35
|
+
The key responsibility these APIs place on you is **thinking critically about Harper instance scope**. Starting a Harper instance has real cost — it spawns a process, allocates a loopback address from the pool, and creates a temporary install directory. How you scope instances depends on your framework and what you're testing. A single instance shared across many assertions in one logical test block may be perfectly appropriate; spinning up a fresh instance for every individual assertion would be wasteful. What matters is that concurrently executing scopes each have their own instance and loopback address — the pool handles cross-process allocation safely, but your setup and teardown hooks determine the boundaries.
|
|
36
|
+
|
|
37
|
+
This package also includes a **preconfigured Node.js test runner script** (`harper-integration-test-run`) as a recommended, batteries-included way to run tests written with the `node:test` API. It is documented below, and its [source](./src/run.ts) serves as a reference implementation for anyone integrating these utilities with a different runner.
|
|
38
|
+
|
|
39
|
+
## Setup: Loopback Addresses
|
|
40
|
+
|
|
41
|
+
Running Harper integration tests concurrently requires multiple loopback addresses so that each Harper instance can bind to a distinct address without port conflicts.
|
|
42
|
+
|
|
43
|
+
Linux Ubuntu systems have `127.0.0.1–127.255.255.255` enabled by default. macOS and Windows do not.
|
|
44
|
+
|
|
45
|
+
Use the included script to configure them:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npx harper-integration-test-setup-loopback
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This script requires `sudo` and respects the `HARPER_INTEGRATION_TEST_LOOPBACK_POOL_COUNT` environment variable (defaults to 32).
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
The lifecycle and utility APIs below are framework-agnostic. They manage Harper child processes and a cross-process loopback address pool. Use them in the setup/teardown hooks of whichever test framework you prefer.
|
|
56
|
+
|
|
57
|
+
### `startHarper(ctx, options?)`
|
|
58
|
+
|
|
59
|
+
Allocates a loopback address from the pool, creates a temporary install directory, starts a Harper process, and waits for it to be ready. Populates `ctx.harper` with the instance details. Call in a setup/`before()` hook.
|
|
60
|
+
|
|
61
|
+
The Harper binary is resolved in the following order:
|
|
62
|
+
|
|
63
|
+
1. `harperBinPath` option passed directly to `startHarper()`
|
|
64
|
+
2. `HARPER_INTEGRATION_TEST_INSTALL_SCRIPT` environment variable (path to `dist/bin/harper.js`)
|
|
65
|
+
3. Auto-resolved from a `harper` package installed as a project dependency
|
|
66
|
+
|
|
67
|
+
**Options:**
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
interface StartHarperOptions {
|
|
71
|
+
startupTimeoutMs?: number; // Default: 30000 or HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS
|
|
72
|
+
config?: object; // Harper config overrides (passed via HARPER_SET_CONFIG)
|
|
73
|
+
env?: object; // Additional environment variables for the Harper process
|
|
74
|
+
harperBinPath?: string; // Explicit path to dist/bin/harper.js
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Environment Variables:**
|
|
79
|
+
|
|
80
|
+
- `HARPER_INTEGRATION_TEST_STARTUP_TIMEOUT_MS` - Default startup timeout
|
|
81
|
+
- `HARPER_INTEGRATION_TEST_INSTALL_PARENT_DIR` - Parent directory for temp Harper install dirs (default: OS tmpdir)
|
|
82
|
+
- `HARPER_INTEGRATION_TEST_INSTALL_SCRIPT` - Path to Harper CLI script
|
|
83
|
+
|
|
84
|
+
### `setupHarperWithFixture(ctx, fixturePath, options?)`
|
|
85
|
+
|
|
86
|
+
Like `startHarper()`, but copies a component directory into the Harper install before starting, so it's available on first boot without a deploy.
|
|
87
|
+
|
|
88
|
+
### `killHarper(ctx)`
|
|
89
|
+
|
|
90
|
+
Sends SIGTERM to the Harper process and waits for it to exit. Does not release the loopback address or clean up the install directory. Useful for restart scenarios where the test will call `startHarper` again.
|
|
91
|
+
|
|
92
|
+
### `teardownHarper(ctx)`
|
|
93
|
+
|
|
94
|
+
Kills Harper, releases the loopback address back to the pool, and removes the install directory. Call in a teardown/`after()` hook.
|
|
95
|
+
|
|
96
|
+
### `sendOperation(context, operation)`
|
|
97
|
+
|
|
98
|
+
Helper to POST an operation to the Operations API and assert HTTP 200.
|
|
99
|
+
|
|
100
|
+
### `ContextWithHarper`
|
|
101
|
+
|
|
102
|
+
TypeScript interface for the test context populated by `startHarper()`. When using `node:test`, pass it as the suite context type:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
interface HarperContext {
|
|
106
|
+
dataRootDir: string; // Harper install directory
|
|
107
|
+
admin: { username: string; password: string };
|
|
108
|
+
httpURL: string; // e.g. 'http://127.0.0.2:9926'
|
|
109
|
+
operationsAPIURL: string; // e.g. 'http://127.0.0.2:9925'
|
|
110
|
+
hostname: string; // e.g. '127.0.0.2'
|
|
111
|
+
process: ChildProcess;
|
|
112
|
+
logDir?: string;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Server Log Capture
|
|
117
|
+
|
|
118
|
+
When `HARPER_INTEGRATION_TEST_LOG_DIR` is set, each Harper instance writes its logs to a per-suite subdirectory. Logs from passing suites are automatically cleaned up; only failing suite logs are retained.
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
HARPER_INTEGRATION_TEST_LOG_DIR=/tmp/harper-test-logs npx harper-integration-test-run "integrationTests/**/*.test.ts"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `targz(dirPath)`
|
|
125
|
+
|
|
126
|
+
Packs and compresses a directory into a base64-encoded tar.gz string. Useful for `deploy_component` Operations API calls.
|
|
127
|
+
|
|
128
|
+
### Loopback Pool Utilities
|
|
129
|
+
|
|
130
|
+
These are used internally by `startHarper` and `teardownHarper`, but are exported for advanced use cases or custom runner integrations.
|
|
131
|
+
|
|
132
|
+
- `validateLoopbackAddressPool(): Promise<{ successful: string[]; failed: { loopbackAddress: string; error: Error }[] }>` - Validates all pool addresses can be bound to
|
|
133
|
+
- `getNextAvailableLoopbackAddress(): Promise<string>` - Allocates an address from the pool
|
|
134
|
+
- `releaseLoopbackAddress(address: string): Promise<void>` - Returns an address to the pool
|
|
135
|
+
- `releaseAllLoopbackAddressesForCurrentProcess(): Promise<void>` - Releases all addresses held by this process
|
|
136
|
+
|
|
137
|
+
## Node.js Test Runner
|
|
138
|
+
|
|
139
|
+
> Tests executed by this runner must use the `node:test` API. If you're using a different test framework, use the [lifecycle APIs](#api) directly and refer to the [runner source](./src/run.ts) as a reference implementation.
|
|
140
|
+
|
|
141
|
+
The included `harper-integration-test-run` script is the recommended way to run Harper integration tests. It is built on the Node.js `node:test` API and handles the concurrency configuration described in the [original parallelization analysis](https://github.com/Ethan-Arrowood/node-test-runner-parallelization-analysis): because each integration test spawns a resource-intensive Harper process, the safe default concurrency is roughly half of available system parallelism rather than Node.js's default of `availableParallelism() - 1`.
|
|
142
|
+
|
|
143
|
+
The runner executes each test **file** in its own process, which means files are the unit of isolation. For this to work safely and predictably, each test file must adhere to the independent, hermetic, and deterministic properties described in the [Testing Ethos](#testing-ethos). Strictly following these properties enables files to run concurrently without interfering with each other. When a test breaks, that file can be run in isolation to iterate on a fix without re-running everything.
|
|
144
|
+
|
|
145
|
+
The [Harper integration tests](https://github.com/HarperFast/harper/tree/main/integrationTests) are an open source example of this runner and framework in production use.
|
|
146
|
+
|
|
147
|
+
### Running Tests
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
npx harper-integration-test-run "integrationTests/**/*.test.ts"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Or add to your `package.json`:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"scripts": {
|
|
158
|
+
"test:integration": "harper-integration-test-run integrationTests/**/*.test.ts"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Runner Options
|
|
164
|
+
|
|
165
|
+
All CLI arguments can be overridden using the associative `HARPER_INTEGRATION_TEST_*` environment variable.
|
|
166
|
+
|
|
167
|
+
| CLI Argument | Environment Variable | Default | Description |
|
|
168
|
+
|---|---|---|---|
|
|
169
|
+
| `--concurrency=N` | `HARPER_INTEGRATION_TEST_CONCURRENCY` | `floor(parallelism/2)+1` | Number of concurrent test processes |
|
|
170
|
+
| `--isolation=mode` | `HARPER_INTEGRATION_TEST_ISOLATION` | `process` | `process` or `none` |
|
|
171
|
+
| `--shard=index/total` | `HARPER_INTEGRATION_TEST_SHARD` | `1/1` | Test sharding for CI |
|
|
172
|
+
| `--only` | `HARPER_INTEGRATION_TEST_ONLY` | `false` | Run only tests marked with `.only` |
|
|
173
|
+
|
|
174
|
+
Example - run sequentially without loopback pool:
|
|
175
|
+
|
|
176
|
+
```sh
|
|
177
|
+
npx harper-integration-test-run --isolation=none "integrationTests/**/*.test.ts"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Example - CI sharding across 4 jobs:
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
npm run test:integration -- --shard=1/4
|
|
184
|
+
npm run test:integration -- --shard=2/4
|
|
185
|
+
npm run test:integration -- --shard=3/4
|
|
186
|
+
npm run test:integration -- --shard=4/4
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Writing Tests
|
|
190
|
+
|
|
191
|
+
Test files should be written in ESM TypeScript, end in `.test.ts`, and begin with a comment block describing what they verify. File names should be short and hyphen-separated — e.g., `install.test.ts` or `application-management.test.ts`.
|
|
192
|
+
|
|
193
|
+
Tests should use `suite`/`describe`, `test`/`it`, and lifecycle methods (`before`, `beforeEach`, `after`, `afterEach`) from `node:test`, with assertions from `node:assert/strict`.
|
|
194
|
+
|
|
195
|
+
A test file can contain multiple suites. Suites always run **sequentially**. Tests within a suite run **sequentially by default**, but can be made concurrent with `{ concurrency: true }` — in which case each individual test within that suite must itself be independent, hermetic, and deterministic.
|
|
196
|
+
|
|
197
|
+
Since these tests interact with a running Harper instance, they often validate actual application output: standard streams (`stdout`/`stderr`), network responses, or file system state.
|
|
198
|
+
|
|
199
|
+
#### Suite Concurrency Example
|
|
200
|
+
|
|
201
|
+
This file contains two suites with two 1-second tests each. The first suite enables `{ concurrency: true }`, the second does not.
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { suite, test } from 'node:test';
|
|
205
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
206
|
+
|
|
207
|
+
suite('Concurrency Enabled', { concurrency: true }, () => {
|
|
208
|
+
test('1 second', async () => {
|
|
209
|
+
await sleep(1000);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('1 second', async () => {
|
|
213
|
+
await sleep(1000);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
suite('Concurrency Disabled', () => {
|
|
218
|
+
test('1 second', async () => {
|
|
219
|
+
await sleep(1000);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('1 second', async () => {
|
|
223
|
+
await sleep(1000);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- The first suite's tests run concurrently → ~1 second
|
|
229
|
+
- The second suite's tests run sequentially → ~2 seconds
|
|
230
|
+
- Suites always run sequentially → **total ~3 seconds**
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
▶ Concurrency Enabled
|
|
234
|
+
✔ 1 second (1001.359083ms)
|
|
235
|
+
✔ 1 second (1001.860375ms)
|
|
236
|
+
✔ Concurrency Enabled (1002.26125ms)
|
|
237
|
+
▶ Concurrency Disabled
|
|
238
|
+
✔ 1 second (1001.122166ms)
|
|
239
|
+
✔ 1 second (1000.5495ms)
|
|
240
|
+
✔ Concurrency Disabled (2001.850625ms)
|
|
241
|
+
ℹ tests 4
|
|
242
|
+
ℹ duration_ms 3087.786041
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### Template
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
/**
|
|
249
|
+
* Description of what this test file verifies.
|
|
250
|
+
*/
|
|
251
|
+
import { suite, test, before, after } from 'node:test';
|
|
252
|
+
import { strictEqual } from 'node:assert/strict';
|
|
253
|
+
import { startHarper, teardownHarper, type ContextWithHarper } from '@harperfast/integration-testing';
|
|
254
|
+
|
|
255
|
+
suite('short description', (ctx: ContextWithHarper) => {
|
|
256
|
+
before(async () => {
|
|
257
|
+
await startHarper(ctx);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
after(async () => {
|
|
261
|
+
await teardownHarper(ctx);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('test description', async () => {
|
|
265
|
+
const response = await fetch(ctx.harper.httpURL);
|
|
266
|
+
strictEqual(response.status, 200);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Using Other Frameworks
|
|
272
|
+
|
|
273
|
+
The lifecycle APIs work with any framework that supports async setup and teardown. The [runner source](./src/run.ts) is a concrete reference for how the Node.js `node:test` integration is wired up, including concurrency configuration and shard support. Use it as a starting point when building an integration for another framework.
|
|
274
|
+
|
|
275
|
+
## GitHub Actions Workflow Parallelization
|
|
276
|
+
|
|
277
|
+
Integration tests should run efficiently and reliably on CI, not just locally. As the number of test files grows — further compounded by verifying multiple operating systems or runtime versions — even a modest test suite can become a large CI operation. The goal is to strike a balance between cost and thoroughness. Tests must always run reliably; correctness is never compromised for speed. Overloaded runners cause flaky failures.
|
|
278
|
+
|
|
279
|
+
**Default GitHub Actions runners have limited performance.** They can reasonably handle a single integration test, but parallelizing many of them will often exceed the runner's capabilities. A recommended strategy is to **parallelize across multiple runners using workflow matrix jobs** combined with **test sharding** — distributing test files across runners rather than running them all on one.
|
|
280
|
+
|
|
281
|
+
Larger runners are available but come at a meaningful cost increase. The right trade-off depends on your organization's total CI job volume, since parallel job execution is typically capped at the org level. When wait time for available runners starts to exceed the time saved by parallelization, fewer jobs on larger runners may be more effective.
|
|
282
|
+
|
|
283
|
+
It's also worth being deliberate about *when* the full integration suite runs. Running every test across all supported configurations on every commit of every open PR can quickly exhaust concurrency limits. Strategies like file path filter triggers, manually triggered workflows, and limiting the full matrix to merge-time checks can keep CI responsive without sacrificing coverage.
|
|
284
|
+
|
|
285
|
+
A **merge queue** is worth considering for this last point — running the complete matrix only after a PR passes initial checks (e.g., a single configuration), then verifying the full matrix before merge. This allows for faster PR iteration while keeping the main branch thoroughly tested.
|
|
286
|
+
|
|
287
|
+
The [Harper integration tests workflow](https://github.com/HarperFast/harper/blob/main/.github/workflows/integration-tests.yml) is a real-world example of these strategies in practice.
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
Apache-2.0
|