@norskvideo/ctl-dev-kit 0.1.6 → 0.1.8
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/build/flake.nix +1 -0
- package/conventions/integration-testing.md +140 -0
- package/package.json +1 -1
package/build/flake.nix
CHANGED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Product integration testing on the DooD runners
|
|
2
|
+
|
|
3
|
+
Guidelines for writing (and debugging) a split product's slow-tier integration
|
|
4
|
+
suite against the org's self-hosted CI. These are hard-won: the failures below
|
|
5
|
+
_look_ like license, networking, or engine bugs but are almost always
|
|
6
|
+
**runner/harness setup** — mounts, ports, hosts, image provenance. Chasing them
|
|
7
|
+
as product bugs wastes time.
|
|
8
|
+
|
|
9
|
+
The reference implementations are the `norsk-ctl-product-playout` and
|
|
10
|
+
`norsk-ctl-product-commentary` harnesses; the shared helpers live in
|
|
11
|
+
`@norskvideo/ctl-test-harness`.
|
|
12
|
+
|
|
13
|
+
## Runner topology (docker-outside-of-docker)
|
|
14
|
+
|
|
15
|
+
The CI runner is itself a **container**. The daemon (`norsk-ctl`) and the product
|
|
16
|
+
backend are spawned **in-process inside that container**. Studio + the Norsk
|
|
17
|
+
media engine launch as **host siblings** (the runner shares the host docker
|
|
18
|
+
socket), with their ports **published on the host**.
|
|
19
|
+
|
|
20
|
+
Consequence — two address classes the harness must not confuse:
|
|
21
|
+
|
|
22
|
+
| Reaching… | Address |
|
|
23
|
+
| ------------------------------------ | ----------------------------------------- |
|
|
24
|
+
| the daemon, the product backend | `localhost:<port>` (in-process) |
|
|
25
|
+
| studio / media host-published ports | `${NORSK_TEST_HOST}:<port>` |
|
|
26
|
+
| a launched container by compose name | `<instance>-<service>-1` **on norsk-net** |
|
|
27
|
+
|
|
28
|
+
`NORSK_TEST_HOST` is `host.docker.internal` in CI and unset (→ `localhost`)
|
|
29
|
+
locally. Set it in the workflow env. The shared `studio-state` fetchers
|
|
30
|
+
(`fetchComponents`, `fetchComponentState`, …) already honour it; **any local
|
|
31
|
+
support code that hits a studio/media port must route through a `TEST_HOST`
|
|
32
|
+
helper** (`process.env.NORSK_TEST_HOST ?? "localhost"`), never a hardcoded
|
|
33
|
+
`localhost`. Daemon/backend fetches stay on `localhost`.
|
|
34
|
+
|
|
35
|
+
## Host-shared working directory (or studio EACCESes on boot)
|
|
36
|
+
|
|
37
|
+
Studio bind-mounts its instance working directory as `${workdir}:/data` and runs
|
|
38
|
+
as a **non-root** auto-detected user. If the workdir is the daemon default
|
|
39
|
+
(`~/norsk-runtime`, container-only), the sibling mount materialises **root-owned**
|
|
40
|
+
and studio dies at boot with `EACCES: mkdir '/data/moq-certs'` — the workflow
|
|
41
|
+
never starts, the SRT graph never builds, and every downstream test times out
|
|
42
|
+
looking like a networking/license fault.
|
|
43
|
+
|
|
44
|
+
Fix: point the daemon at a **host-shared** workdir under the test store dir.
|
|
45
|
+
Pre-write `config.yaml` with `defaultWorkingDirectory` under `makeStoreDir(...)`
|
|
46
|
+
(which roots in the gitignored, host-shared `test-temp/`):
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
writeFileSync(
|
|
50
|
+
join(storeDir, "config.yaml"),
|
|
51
|
+
`networkMode: docker\ndefaultWorkingDirectory: ${JSON.stringify(join(storeDir, "norsk-runtime"))}\n`,
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Docker still leaves **root-owned** read-only bind-mount target dirs inside that
|
|
56
|
+
workdir, which the non-root runner can't `rm`. So `teardown()` must nuke the
|
|
57
|
+
store dir from a throwaway **root** container, or `afterAll` (and the runner's
|
|
58
|
+
own checkout cleanup) blocks:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
spawnSync("docker", ["run", "--rm", "--user", "0:0", "-v", `${dirname(storeDir)}:/base`,
|
|
62
|
+
"--entrypoint", "sh", STUDIO_IMAGE, "-c", `rm -rf /base/${basename(storeDir)}`]);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Reaching served media / HLS output
|
|
66
|
+
|
|
67
|
+
Studio reports playback URLs (HLS multivariant, WHIP endpoints) with the host it
|
|
68
|
+
was **configured with** — loopback locally, which the in-container test can't
|
|
69
|
+
reach. Two cases:
|
|
70
|
+
|
|
71
|
+
- **Proxied harness** — the URL is on the daemon's `publicHost` (default
|
|
72
|
+
`:443`). Route the fetch through `applyTestHost(url)` (swaps a loopback host
|
|
73
|
+
for `NORSK_TEST_HOST`). `assertMultivariantHasRenditions` does this by default.
|
|
74
|
+
- **Proxy-less harness** — the advertised `/instance/<id>/media/...` route is
|
|
75
|
+
served **only by the daemon's nginx proxy**; with no proxy, nothing answers on
|
|
76
|
+
`:443`. Reach the media container **directly on norsk-net** instead:
|
|
77
|
+
`mediaDirectUrl(url)` rewrites it to `http://<id>-media-1:8080/<native-path>`
|
|
78
|
+
(the same bypass `whip-driver` uses; the media route is unauthenticated). Pass
|
|
79
|
+
it via `assertMultivariantHasRenditions`'s `resolveFetchUrl` hook, and **join
|
|
80
|
+
the runner to norsk-net** in setup so the compose service name resolves:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
spawnSync("docker", ["network", "create", "norsk-net"]); // idempotent
|
|
84
|
+
spawnSync("docker", ["network", "connect", "norsk-net", hostname()]);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Capture servers (studio pushes → the runner)
|
|
88
|
+
|
|
89
|
+
When a test stands up an HLS/SCTE-35 capture server that **studio pushes to**,
|
|
90
|
+
studio (a host sibling on norsk-net) can't reach the runner via the host LAN IP.
|
|
91
|
+
Join norsk-net and hand studio the runner's **norsk-net IP** (`docker inspect`
|
|
92
|
+
self), falling back to the host LAN IP only when `NORSK_TEST_HOST` is unset. See
|
|
93
|
+
`manifest-capture.ts`'s `captureHost()`.
|
|
94
|
+
|
|
95
|
+
## Build the product image in the integration job
|
|
96
|
+
|
|
97
|
+
A `kind: dev` product still launches an instance whose compose references the
|
|
98
|
+
product's **image** (e.g. the runtime-screen `funke-backend` service on
|
|
99
|
+
`<product>:dev`). The `build-image` job builds it on a _different_ runner, so the
|
|
100
|
+
integration job must build it too — otherwise `docker compose up` tries to
|
|
101
|
+
**pull** the local-only tag and dies with `pull access denied` before any test
|
|
102
|
+
runs. Add `bun run build:image` inside the `nix develop .#build` block (docker is
|
|
103
|
+
reachable there); it also builds the dist the dev backend serves `/configure`
|
|
104
|
+
from.
|
|
105
|
+
|
|
106
|
+
## Stale node_modules on `clean: false` runners
|
|
107
|
+
|
|
108
|
+
The runners check out `clean: false` (to preserve `test-temp/`), so
|
|
109
|
+
`node_modules` **persists between runs**, and `bun install --frozen-lockfile`
|
|
110
|
+
does **not** reliably relink a workspace-hoisted package whose version moved. A
|
|
111
|
+
stale `@norskvideo/ctl-test-harness@0.1.2` once survived a bump to `0.1.3`, so a
|
|
112
|
+
newly-added export was `SyntaxError: export 'X' not found` and the suite crashed
|
|
113
|
+
in <1s. The bun **global cache is integrity-checked** (it can't serve a wrong
|
|
114
|
+
version); only the persisted `node_modules` dir goes stale. Guard the install:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
rm -rf node_modules/@norskvideo/ctl-test-harness
|
|
118
|
+
bun install --frozen-lockfile
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
When a product pins the shared harness in **both** `package.json` **and**
|
|
122
|
+
`tests/package.json`, bump **both** — a lone root bump can leave the tests
|
|
123
|
+
workspace resolving the old version.
|
|
124
|
+
|
|
125
|
+
## GPU
|
|
126
|
+
|
|
127
|
+
Instances launch with `--hardware nvidia` and encode on **nvenc** on the runners
|
|
128
|
+
(the nvenc log-grep + no-software-fallback assertions pass). Note the
|
|
129
|
+
`/usr/bin/nvidia-smi` **binary** may be absent in the media container even when
|
|
130
|
+
nvenc works — assert on the encoder logs, not on `nvidia-smi` exit status.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
The org secret `NORSK_LICENSE_V2` + a modern engine accept the license (a green
|
|
135
|
+
`probe` run proves it). V2 licenses need a **post-2026-07-17** engine, and SDK
|
|
136
|
+
clients that create media nodes (e.g. a WHIP driver) must answer the license
|
|
137
|
+
**handshake** via `createLicenseChallengeResponder` or the engine aborts node
|
|
138
|
+
creation with `10 ABORTED: License handshake required`. The `license is marked
|
|
139
|
+
NON-PRODUCTION` warning is expected in CI, not an error. The media engine's
|
|
140
|
+
`hugepages / rte_eal_init failed` line is **benign noise** — do not chase it.
|