@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 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