Skip to content

Commit 1b18fdd

Browse files
authored
test(e2e): replace the Linux compose runners with a dev container (#37)
1 parent 2eae0d7 commit 1b18fdd

11 files changed

Lines changed: 251 additions & 150 deletions

File tree

‎.devcontainer/Dockerfile‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2026 Han Li and contributors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# The repository dev container (see docs/e2e.md): a Linux userland that is
16+
# identical on every host platform — Linux amd64/arm64 natively, Windows
17+
# amd64/arm64 through Docker Desktop's Linux-container mode. It carries the
18+
# pinned Go toolchain the E2E suites and the local checks need, matching the
19+
# setup-go version CI uses, plus the Lua and license tooling pinned in the
20+
# lint and license workflows.
21+
#
22+
# The suites themselves run testcontainers against the host daemon through
23+
# the socket mounted by devcontainer.json (Docker-out-of-Docker), so the
24+
# target containers are spawned exactly as a native CI runner spawns them.
25+
#
26+
# The image is multi-arch (linux/amd64 and linux/arm64); the Lua downloads
27+
# below select their asset per target arch.
28+
29+
FROM golang:1.27.1
30+
31+
# Docker CLI (client only): drives the host daemon from the shell, the same
32+
# daemon the suites drive through the Docker Go API. Engine 29 is the line
33+
# tests/e2e/windows/install-docker-arm64.ps1 builds for Windows arm64.
34+
COPY --from=docker:29-cli /usr/local/bin/docker /usr/local/bin/docker
35+
36+
# Keep the tool versions below in step with .github/workflows/lint.yml
37+
# (stylua-action's version and the `mise x aqua:...` line).
38+
ARG STYLUA_VERSION=2.5.2
39+
ARG LUA_LANGUAGE_SERVER_VERSION=3.19.1
40+
41+
# Heredocs need BuildKit, which the dev container tooling uses by default. The
42+
# E2E target Dockerfiles stay on the chained-`;` form: testcontainers-go's
43+
# build path cannot use BuildKit (see tests/e2e/linux/Dockerfile).
44+
45+
RUN <<EOF
46+
set -o errexit -o nounset -o xtrace
47+
apt-get update
48+
apt-get install --assume-yes --no-install-recommends \
49+
ca-certificates \
50+
curl \
51+
git \
52+
unzip \
53+
zip
54+
rm --recursive --force /var/lib/apt/lists/*
55+
case "$(dpkg --print-architecture)" in
56+
amd64) stylua_asset=stylua-linux-x86_64.zip; luals_asset="lua-language-server-${LUA_LANGUAGE_SERVER_VERSION}-linux-x64.tar.gz" ;;
57+
arm64) stylua_asset=stylua-linux-aarch64.zip; luals_asset="lua-language-server-${LUA_LANGUAGE_SERVER_VERSION}-linux-arm64.tar.gz" ;;
58+
*) echo "FAIL unsupported architecture $(dpkg --print-architecture)" >&2; exit 1 ;;
59+
esac
60+
curl --fail --silent --show-error --location --output /tmp/stylua.zip \
61+
"https://github.com/JohnnyMorganz/StyLua/releases/download/v${STYLUA_VERSION}/${stylua_asset}"
62+
unzip -q /tmp/stylua.zip -d /usr/local/bin
63+
rm --force /tmp/stylua.zip
64+
curl --fail --silent --show-error --location --output /tmp/luals.tar.gz \
65+
"https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LANGUAGE_SERVER_VERSION}/${luals_asset}"
66+
mkdir --parents /opt/lua-language-server
67+
tar --extract --gzip --file /tmp/luals.tar.gz --directory /opt/lua-language-server
68+
rm --force /tmp/luals.tar.gz
69+
ln --symbolic /opt/lua-language-server/bin/lua-language-server /usr/local/bin/lua-language-server
70+
stylua --version
71+
lua-language-server --version
72+
EOF
73+
74+
# License-header tooling at the versions pinned by .github/workflows/license.yml
75+
# (SkyWalking Eyes v0.9.0 for the header check, addlicense v1.2.0 for the
76+
# PowerShell files).
77+
RUN <<EOF
78+
set -o errexit -o nounset -o xtrace
79+
GOBIN=/usr/local/bin go install github.com/apache/skywalking-eyes/cmd/license-eye@v0.9.0
80+
GOBIN=/usr/local/bin go install github.com/google/addlicense@v1.2.0
81+
command -v license-eye addlicense
82+
EOF

‎.devcontainer/devcontainer.json‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "vfox-nodejs (Linux)",
3+
"build": {
4+
"dockerfile": "Dockerfile",
5+
"context": "."
6+
},
7+
"mounts": [
8+
"source=${localEnv:DOCKER_SOCK:/var/run/docker.sock},target=/var/run/docker.sock,type=bind"
9+
],
10+
"containerEnv": {
11+
"TESTCONTAINERS_RYUK_DISABLED": "true"
12+
},
13+
"postCreateCommand": "cd tests/e2e && go mod download",
14+
"customizations": {
15+
"vscode": {
16+
"extensions": ["golang.go", "sumneko.lua"]
17+
}
18+
}
19+
}

‎.dockerignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
.git
1919
.gitignore
2020
.gitattributes
21+
.devcontainer
2122
.idea
2223
.github
2324
*.md

‎.github/workflows/e2e.yml‎

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ concurrency:
2626
group: e2e-${{ github.ref }}
2727
cancel-in-progress: true
2828

29+
# The Linux and WSL jobs run the suite natively, the way the dev container
30+
# runs it locally: Go at the dev container's version (.devcontainer/Dockerfile)
31+
# plus the runner's stock Docker, with the same TESTCONTAINERS_RYUK_DISABLED
32+
# knob. The Windows jobs keep the compose runner image
33+
# (tests/e2e/windows/Dockerfile-runner), which carries its own Go toolchain,
34+
# so a Windows host needs only Docker. See docs/e2e.md.
35+
2936
jobs:
3037
e2e-linux:
3138
if: github.repository == 'version-fox/vfox-nodejs'
@@ -36,11 +43,22 @@ jobs:
3643
runs-on: [ubuntu-latest, ubuntu-26.04-arm]
3744
runs-on: ${{ matrix.runs-on }}
3845
timeout-minutes: 60
46+
env:
47+
# Ryuk publishes a port on the host daemon and waits for the suite to
48+
# dial back to it; the dev container disables it (it runs
49+
# Docker-out-of-Docker), and CI matches so both paths stay identical.
50+
# Containers are terminated explicitly via t.Cleanup instead.
51+
TESTCONTAINERS_RYUK_DISABLED: "true"
3952
steps:
4053
- uses: actions/checkout@v7
4154
with:
4255
persist-credentials: false
43-
- run: docker compose -f tests/e2e/linux/compose.yaml run --rm e2e
56+
- uses: actions/setup-go@v6
57+
with:
58+
go-version: 1.27.1
59+
cache: false
60+
- run: go test -v -timeout 55m -run TestE2E ./linux
61+
working-directory: tests/e2e
4462

4563
e2e-linux-musl:
4664
if: github.repository == 'version-fox/vfox-nodejs'
@@ -51,11 +69,21 @@ jobs:
5169
runs-on: [ubuntu-latest, ubuntu-26.04-arm]
5270
runs-on: ${{ matrix.runs-on }}
5371
timeout-minutes: 60
72+
env:
73+
TESTCONTAINERS_RYUK_DISABLED: "true"
74+
# Selects the Alpine userland in the Linux suite; the dev container
75+
# equivalent is the same variable set on the interactive shell.
76+
E2E_LINUX_BASE: alpine
5477
steps:
5578
- uses: actions/checkout@v7
5679
with:
5780
persist-credentials: false
58-
- run: docker compose -f tests/e2e/linux/compose.alpine.yaml run --rm e2e
81+
- uses: actions/setup-go@v6
82+
with:
83+
go-version: 1.27.1
84+
cache: false
85+
- run: go test -v -timeout 55m -run TestE2E ./linux
86+
working-directory: tests/e2e
5987

6088
e2e-windows:
6189
if: github.repository == 'version-fox/vfox-nodejs'

‎docs/e2e.md‎

Lines changed: 110 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,54 @@
33
The offline Lua hook tests (`tests/hooks_test.lua`) run in the **Test Plugin**
44
workflow. The end-to-end suite runs in containers against real vfox on Linux
55
and Windows; each `vfox` x `mirror` combination gets its own throwaway
6-
container.
6+
container. On Linux, CI runs the suite natively on the stock runners (Docker
7+
plus Go from `actions/setup-go`), the way the repository dev container
8+
reproduces it locally. On Windows it runs through a Docker Compose runner
9+
image, since dev containers cannot host Windows container images (see below).
10+
11+
## Dev container
12+
13+
`.devcontainer/` opens a Linux development environment that works on all four
14+
host platforms:
15+
16+
| Host | How the dev container runs |
17+
| --------------------- | ---------------------------------------------- |
18+
| Linux amd64 / arm64 | natively |
19+
| Windows amd64 / arm64 | Docker Desktop in Linux-container mode (WSL 2) |
20+
21+
It carries Go 1.27.1 (the version CI installs), the Docker CLI, stylua,
22+
lua-language-server, and the license tools — all pinned to the versions in
23+
`.github/workflows/lint.yml` and `license.yml` — and mounts the host Docker
24+
daemon socket. The suites then run testcontainers against that daemon
25+
(Docker-out-of-Docker), spawning the same target containers a native CI
26+
runner spawns; this is deliberately not Docker-in-Docker, so image cache and
27+
networking stay the host's. The image needs no workspace files, so the build
28+
context stays at `.devcontainer/`; `postCreateCommand` warms the Go module
29+
cache for `tests/e2e` so the first test run does not stop to download.
30+
31+
Open it with any Dev Containers spec tool — **Dev Containers: Reopen in
32+
Container** in VS Code/Cursor, or the CLI:
33+
34+
```bash
35+
devcontainer up --workspace-folder .
36+
devcontainer exec --workspace-folder . bash
37+
```
38+
39+
`TESTCONTAINERS_RYUK_DISABLED=true` is preset: Ryuk publishes a port on the
40+
host daemon and waits for the suite to dial it back, which a
41+
Docker-out-of-Docker container cannot do. The suites terminate every
42+
container explicitly via `t.Cleanup` instead; leftovers from killed runs
43+
share the `vfox-nodejs-e2e-` name prefix for pruning. Rootless Docker users
44+
export `DOCKER_SOCK="$XDG_RUNTIME_DIR/docker.sock"` on the host before
45+
opening the container. On Windows hosts, keeping the checkout inside WSL 2
46+
(e.g. under `\\wsl$`) keeps file and bind-mount access fast; a checkout on
47+
the Windows filesystem works too.
48+
49+
Windows *container* coverage (`tests/e2e/windows`) cannot run in a dev
50+
container: the Dev Containers spec does not support Windows container images
51+
([vscode-remote-release#445](https://github.com/microsoft/vscode-remote-release/issues/445)),
52+
and Windows containers only run on a Windows daemon. That suite keeps its
53+
Docker Compose runner on a Windows host; see [Windows 11](#windows-11) below.
754

855
## What the suite verifies
956

@@ -62,55 +109,59 @@ pin `VFOX_VERSION` and `MIRROR` explicitly to widen or narrow the matrix.
62109
| `E2E_RETRY_ATTEMPTS`| `3` | Attempts per network-touching exec (linear 10s backoff) |
63110
| `E2E_LINUX_BASE` | `ubuntu` | Linux userland: `ubuntu` (glibc) or `alpine` (musl, see docs/musl.md) |
64111

65-
## Ubuntu
66-
67-
Only Docker Engine (in either rootful or rootless mode) is required on the
68-
host. ARM64 runs the default mirror only.
112+
## Linux (Ubuntu)
69113

70-
### rootful Docker
71-
72-
Execute in Bash,
114+
Inside the dev container, from the repository root:
73115

74116
```bash
75-
sudo apt install --assume-yes git
76-
git clone git@github.com:version-fox/vfox-nodejs.git
77-
cd ./vfox-nodejs/
78-
docker compose -f tests/e2e/linux/compose.yaml run --rm e2e
117+
cd tests/e2e
118+
go test -v -timeout 55m -run TestE2E ./linux
79119
```
80120

81-
### rootless Docker
82-
83-
With rootless Docker the daemon socket lives elsewhere; point `DOCKER_SOCK`
84-
at it instead:
121+
Scope it down while iterating the same way:
85122

86123
```bash
87-
sudo apt install --assume-yes git
88-
git clone git@github.com:version-fox/vfox-nodejs.git
89-
cd ./vfox-nodejs/
90-
DOCKER_SOCK="$XDG_RUNTIME_DIR/docker.sock" docker compose -f tests/e2e/linux/compose.yaml run --rm e2e
124+
VFOX_VERSION=latest MIRROR=default E2E_MAX_JOBS=1 go test -v -timeout 55m -run TestE2E ./linux
91125
```
92126

127+
On a native host the same command works with two prerequisites — Docker
128+
Engine (rootful or rootless) and Go 1.27.1 — which is exactly what CI
129+
provisions. Rootless Docker users point the Docker client at their socket
130+
(`export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock"`); the dev
131+
container's socket mount covers that case for you. ARM64 runs the default
132+
mirror only.
133+
93134
## Alpine (musl)
94135

95-
Same host requirement. The Alpine suite builds
96-
`tests/e2e/linux/Dockerfile-alpine` (musl userland with `libstdc++`) and
97-
asserts the musl userland before running the standard flow, proving the
98-
plugin selects the `*-musl` tarballs
136+
The Alpine suite builds `tests/e2e/linux/Dockerfile-alpine` (musl userland
137+
with `libstdc++`) and asserts the musl userland before running the standard
138+
flow, proving the plugin selects the `*-musl` tarballs
99139
([issue #29](https://github.com/version-fox/vfox-nodejs/issues/29)).
100140
See [docs/musl.md](musl.md) for the tarball matrix.
101141

142+
Set `E2E_LINUX_BASE` to switch the same Linux suite onto the Alpine image:
143+
102144
```bash
103-
docker compose -f tests/e2e/linux/compose.alpine.yaml run --rm e2e
145+
cd tests/e2e
146+
E2E_LINUX_BASE=alpine go test -v -timeout 55m -run TestE2E ./linux
104147
```
105148

106-
Scope it down locally the same way, e.g.
107-
`VFOX_VERSION=latest MIRROR=default E2E_MAX_JOBS=1` in front of the command.
149+
CI exports the same variable in its musl job; the dev container needs
150+
nothing else.
108151

109-
## Windows 11 Pro
152+
## Windows 11
110153

111154
Windows containers cannot be emulated across architectures, so the ARM64
112155
suite needs an ARM64 host (a Copilot+ PC or the `windows-11-arm` CI runner).
113156

157+
There is no dev container for this suite — the Dev Containers spec rejects
158+
Windows container images (see [Dev container](#dev-container)) — so it keeps
159+
its Docker Compose runner. The runner image (`Dockerfile-runner`: stock
160+
servercore plus the pinned Go MSI) drives the host daemon through the mounted
161+
named pipe; the host requirement is Docker in Windows-container mode plus the
162+
Compose v5 plugin (installed once per machine below). Go never touches the
163+
host, so it cannot conflict with a vfox-managed toolchain.
164+
114165
The `windows-11-arm` runner has no Windows Hypervisor Platform feature, so
115166
Hyper-V isolation is unavailable; process isolation needs only the
116167
Containers feature.
@@ -191,6 +242,12 @@ curl.exe -fsSL `
191242
docker compose -f tests/e2e/windows/compose.arm64.yaml run --rm --build e2e
192243
```
193244

245+
On arm64, Go is also what builds the engine, so `install-docker-arm64.ps1`
246+
runs before the suite; the compose runner image then carries its own pinned
247+
Go for the tests, and the host Go is not used again. Scope the run down with
248+
`$env:VFOX_VERSION = 'latest'; $env:MIRROR = 'default'` in front of the
249+
compose command, as on the other platforms.
250+
194251
## WSL
195252

196253
The WSL suite (`tests/e2e/wsl/`) drives the Ubuntu distribution on the
@@ -208,6 +265,9 @@ Combos share one distro and run sequentially, each isolated in
208265
its own `HOME`/`VFOX_HOME` slot. `MIRROR` defaults apply as usual; CI pins
209266
`MIRROR=default` to keep the run to the `latest`/`main` vfox channels.
210267

268+
It runs from the Windows host (it needs `wsl.exe` and the installed distro),
269+
not from the dev container.
270+
211271
### CI
212272

213273
The `WSL (windows-2025)` job installs `Ubuntu-24.04` with
@@ -231,14 +291,11 @@ go test -v -timeout 55m -run TestE2E ./wsl
231291

232292
## Local checks
233293

234-
Execute in Bash on Ubuntu before pushing.
294+
Execute from the dev container — everything below is preinstalled and pinned
295+
to the versions the workflows use — or provision the same tools on a host.
235296

236297
```bash
237-
docker run --rm -v "$PWD:/work" -w /work apache/skywalking-eyes:0.9.0 header check
238-
docker run --rm -v "$PWD:/work" -w /work apache/skywalking-eyes:0.9.0 header fix
239-
docker run --rm -v "$PWD:/src" ghcr.io/google/addlicense:v1.2.0 -check -f .license.tpl tests/e2e/windows/*.ps1
240-
docker run --rm -v "$PWD:/src" ghcr.io/google/addlicense:v1.2.0 -f .license.tpl tests/e2e/windows/*.ps1
241-
298+
# Lua and Go checks (.github/workflows/lint.yml).
242299
cd tests/e2e
243300
go test -short ./...
244301
test -z "$(gofmt -l .)"
@@ -248,5 +305,23 @@ cd ../..
248305
go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/hooks_test.lua
249306

250307
stylua --check .
251-
mise x aqua:LuaLS/lua-language-server@3.19.1 -- lua-language-server --check . --checklevel=Warning
308+
lua-language-server --check . --checklevel=Warning
309+
```
310+
311+
```bash
312+
# License headers (.github/workflows/license.yml).
313+
license-eye header check
314+
license-eye header fix
315+
addlicense -check -f .license.tpl tests/e2e/windows/*.ps1
316+
addlicense -f .license.tpl tests/e2e/windows/*.ps1
317+
```
318+
319+
On a Docker-only host the license checks run as containers instead (the same
320+
images CI uses):
321+
322+
```bash
323+
docker run --rm -v "$PWD:/work" -w /work apache/skywalking-eyes:0.9.0 header check
324+
docker run --rm -v "$PWD:/work" -w /work apache/skywalking-eyes:0.9.0 header fix
325+
docker run --rm -v "$PWD:/src" ghcr.io/google/addlicense:v1.2.0 -check -f .license.tpl tests/e2e/windows/*.ps1
326+
docker run --rm -v "$PWD:/src" ghcr.io/google/addlicense:v1.2.0 -f .license.tpl tests/e2e/windows/*.ps1
252327
```

‎tests/e2e/doc.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// - common: target-independent shared logic (matrix expansion, version
2222
// defaults, retry counts, name handling);
2323
// - linux: the Linux container suite (bash-driven), run in CI and locally
24-
// via tests/e2e/linux/compose.yaml;
24+
// through the repository dev container (.devcontainer/, see docs/e2e.md);
2525
// - windows: the Windows container suite (pwsh-driven), run via
2626
// tests/e2e/windows/compose.amd64.yaml (amd64) or
2727
// tests/e2e/windows/compose.arm64.yaml (arm64).

0 commit comments

Comments
 (0)