Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright 2026 Han Li and contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# The repository dev container (see docs/e2e.md): a Linux userland that is
# identical on every host platform — Linux amd64/arm64 natively, Windows
# amd64/arm64 through Docker Desktop's Linux-container mode. It carries the
# pinned Go toolchain the E2E suites and the local checks need, matching the
# setup-go version CI uses, plus the Lua and license tooling pinned in the
# lint and license workflows.
#
# The suites themselves run testcontainers against the host daemon through
# the socket mounted by devcontainer.json (Docker-out-of-Docker), so the
# target containers are spawned exactly as a native CI runner spawns them.
#
# The image is multi-arch (linux/amd64 and linux/arm64); the Lua downloads
# below select their asset per target arch.

FROM golang:1.26.8

# Docker CLI (client only): drives the host daemon from the shell, the same
# daemon the suites drive through the Docker Go API. Engine 29.8.1 is the
# line tests/e2e/windows/install-docker-arm64.ps1 builds for Windows arm64.
COPY --from=docker:29-cli /usr/local/bin/docker /usr/local/bin/docker

# Keep the tool versions below in step with .github/workflows/lint.yml
# (stylua-action's version and the `mise x aqua:...` line).
ARG STYLUA_VERSION=2.5.2
ARG LUA_LANGUAGE_SERVER_VERSION=3.19.1

# Heredocs need BuildKit, which the dev container tooling uses by default. The
# E2E target Dockerfiles stay on the chained-`;` form: testcontainers-go's
# build path cannot use BuildKit (see tests/e2e/linux/Dockerfile).

RUN <<EOF
set -o errexit -o nounset -o xtrace
apt-get update
apt-get install --assume-yes --no-install-recommends \
ca-certificates \
curl \
git \
unzip \
zip
rm --recursive --force /var/lib/apt/lists/*
case "$(dpkg --print-architecture)" in
amd64) stylua_asset=stylua-linux-x86_64.zip; luals_asset="lua-language-server-${LUA_LANGUAGE_SERVER_VERSION}-linux-x64.tar.gz" ;;
arm64) stylua_asset=stylua-linux-aarch64.zip; luals_asset="lua-language-server-${LUA_LANGUAGE_SERVER_VERSION}-linux-arm64.tar.gz" ;;
*) echo "FAIL unsupported architecture $(dpkg --print-architecture)" >&2; exit 1 ;;
esac
curl --fail --silent --show-error --location --output /tmp/stylua.zip \
"https://github.com/JohnnyMorganz/StyLua/releases/download/v${STYLUA_VERSION}/${stylua_asset}"
unzip -q /tmp/stylua.zip -d /usr/local/bin
rm --force /tmp/stylua.zip
curl --fail --silent --show-error --location --output /tmp/luals.tar.gz \
"https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LANGUAGE_SERVER_VERSION}/${luals_asset}"
mkdir --parents /opt/lua-language-server
tar --extract --gzip --file /tmp/luals.tar.gz --directory /opt/lua-language-server
rm --force /tmp/luals.tar.gz
ln --symbolic /opt/lua-language-server/bin/lua-language-server /usr/local/bin/lua-language-server
stylua --version
lua-language-server --version
EOF

# License-header tooling at the versions pinned by .github/workflows/license.yml
# (SkyWalking Eyes v0.9.0 for the header check, addlicense v1.2.0 for the
# PowerShell files).
RUN <<EOF
set -o errexit -o nounset -o xtrace
GOBIN=/usr/local/bin go install github.com/apache/skywalking-eyes/cmd/license-eye@v0.9.0
GOBIN=/usr/local/bin go install github.com/google/addlicense@v1.2.0
command -v license-eye addlicense
EOF
19 changes: 19 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "vfox-flutter (Linux)",
"build": {
"dockerfile": "Dockerfile",
"context": "."
},
"mounts": [
"source=${localEnv:DOCKER_SOCK:/var/run/docker.sock},target=/var/run/docker.sock,type=bind"
],
"containerEnv": {
"TESTCONTAINERS_RYUK_DISABLED": "true"
},
"postCreateCommand": "cd tests/e2e && go mod download",
"customizations": {
"vscode": {
"extensions": ["golang.go", "sumneko.lua"]
}
}
}
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

.git
.gitignore
.devcontainer
.idea
.github
*.md
Expand Down
20 changes: 19 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ concurrency:
group: e2e-${{ github.ref }}
cancel-in-progress: true

# The Linux job runs the suite natively, the way the dev container runs it
# locally: Go at the dev container's version (.devcontainer/Dockerfile) plus
# the runner's stock Docker, with the same TESTCONTAINERS_RYUK_DISABLED knob.
# The Windows jobs keep the compose runner image
# (tests/e2e/windows/Dockerfile-runner), which carries its own Go toolchain,
# so a Windows host needs only Docker. See docs/e2e.md.

jobs:
e2e-linux:
if: github.repository == 'version-fox/vfox-flutter'
Expand All @@ -36,9 +43,20 @@ jobs:
runs-on: [ubuntu-latest, ubuntu-26.04-arm]
runs-on: ${{ matrix.runs-on }}
timeout-minutes: 60
env:
# Ryuk publishes a port on the host daemon and waits for the suite to
# dial back to it; the dev container disables it (it runs
# Docker-out-of-Docker), and CI matches so both paths stay identical.
# Containers are terminated explicitly via t.Cleanup instead.
TESTCONTAINERS_RYUK_DISABLED: "true"
steps:
- uses: actions/checkout@v7
- run: docker compose -f tests/e2e/linux/compose.yaml run --rm e2e
- uses: actions/setup-go@v7
with:
go-version: '1.26.8'
cache: false
- run: go test -v -timeout 55m -run TestE2E ./linux
working-directory: tests/e2e

e2e-windows:
if: github.repository == 'version-fox/vfox-flutter'
Expand Down
125 changes: 98 additions & 27 deletions docs/e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,93 @@

The offline Lua hook tests (`tests/hooks_test.lua`) run in the **Test Plugin** workflow.
The end-to-end suite runs in containers against real vfox on Linux and Windows;
each `vfox` x `flavor` x `mirror` combination gets its own throwaway container.
each `vfox` x `flavor` x `mirror` combination gets its own throwaway
container. On Linux, CI runs the suite natively on the stock runners (Docker
plus Go from `actions/setup-go`), the way the repository dev container
reproduces it locally. On Windows it runs through a Docker Compose runner
image, since dev containers cannot host Windows container images (see below).

## Dev container

`.devcontainer/` opens a Linux development environment that works on all four
host platforms:

| Host | How the dev container runs |
| --------------------- | ---------------------------------------------- |
| Linux amd64 / arm64 | natively |
| Windows amd64 / arm64 | Docker Desktop in Linux-container mode (WSL 2) |

It carries Go 1.26.8 (the version CI installs), the Docker CLI, stylua,
lua-language-server, and the license tools — all pinned to the versions in
`.github/workflows/lint.yml` and `license.yml` — and mounts the host Docker
daemon socket. The suites then run testcontainers against that daemon
(Docker-out-of-Docker), spawning the same target containers a native CI
runner spawns; this is deliberately not Docker-in-Docker, so image cache and
networking stay the host's. The image needs no workspace files, so the build
context stays at `.devcontainer/`; `postCreateCommand` warms the Go module
cache for `tests/e2e` so the first test run does not stop to download.

Open it with any Dev Containers spec tool — **Dev Containers: Reopen in
Container** in VS Code/Cursor, or the CLI:

## Ubuntu 26.04.1
```bash
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . bash
```

Only Docker Engine (in either rootful or rootless mode) is required on the host.
On ARM64 it runs the official flavor only:
OpenHarmony publishes no `linux-arm64` Dart SDK (see `docs/ohos.md`),
so the ohos flavor cannot bootstrap there.
`TESTCONTAINERS_RYUK_DISABLED=true` is preset: Ryuk publishes a port on the
host daemon and waits for the suite to dial it back, which a
Docker-out-of-Docker container cannot do. The suites terminate every
container explicitly via `t.Cleanup` instead; leftovers from killed runs
share the `vfox-flutter-e2e-` name prefix for pruning. Rootless Docker users
export `DOCKER_SOCK="$XDG_RUNTIME_DIR/docker.sock"` on the host before
opening the container. On Windows hosts, keeping the checkout inside WSL 2
(e.g. under `\\wsl$`) keeps file and bind-mount access fast; a checkout on
the Windows filesystem works too.

### rootful Docker
Windows *container* coverage (`tests/e2e/windows`) cannot run in a dev
container: the Dev Containers spec does not support Windows container images
([vscode-remote-release#445](https://github.com/microsoft/vscode-remote-release/issues/445)),
and Windows containers only run on a Windows daemon. That suite keeps its
Docker Compose runner on a Windows host; see [Windows 11 Pro](#windows-11-pro)
below.

Execute in Bash,
## Linux (Ubuntu 26.04)

Inside the dev container, from the repository root:

```bash
sudo apt install --assume-yes git
git clone git@github.com:version-fox/vfox-flutter.git
cd ./vfox-flutter/
docker compose -f tests/e2e/linux/compose.yaml run --rm e2e
cd tests/e2e
go test -v -timeout 55m -run TestE2E ./linux
```

### rootless Docker

With rootless Docker the daemon socket lives elsewhere; point `DOCKER_SOCK`
at it instead:
Scope it down while iterating the same way:

```bash
sudo apt install --assume-yes git
git clone git@github.com:version-fox/vfox-flutter.git
cd ./vfox-flutter/
DOCKER_SOCK="$XDG_RUNTIME_DIR/docker.sock" docker compose -f tests/e2e/linux/compose.yaml run --rm e2e
VFOX_VERSION=latest FLAVOR=official MIRROR=default E2E_MAX_JOBS=1 go test -v -timeout 55m -run TestE2E ./linux
```

On a native host the same command works with two prerequisites — Docker
Engine (rootful or rootless) and Go 1.26.8 — which is exactly what CI
provisions. Rootless Docker users point the Docker client at their socket
(`export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock"`); the dev
container's socket mount covers that case for you. On ARM64 the official
flavor runs only: OpenHarmony publishes no `linux-arm64` Dart SDK (see
`docs/ohos.md`), so the ohos flavor cannot bootstrap there.

## Windows 11 Pro

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

There is no dev container for this suite — the Dev Containers spec rejects
Windows container images (see [Dev container](#dev-container)) — so it keeps
its Docker Compose runner. The runner image (`Dockerfile-runner`: stock
servercore plus the pinned Go MSI) drives the host daemon through the mounted
named pipe; the host requirement is Docker in Windows-container mode plus the
Compose v5 plugin (installed once per machine below). Go never touches the
host, so it cannot conflict with a vfox-managed toolchain.

The `windows-11-arm` runner has no Windows Hypervisor Platform feature,
so Hyper-V isolation is unavailable; process isolation needs only the Containers feature.

Expand Down Expand Up @@ -114,22 +164,43 @@ curl.exe -fsSL `
docker compose -f tests/e2e/windows/compose.arm64.yaml run --rm --build e2e
```

On arm64, Go is also what builds the engine, so `install-docker-arm64.ps1`
runs before the suite; the compose runner image then carries its own pinned
Go for the tests, and the host Go is not used again. Scope the run down with
`$env:VFOX_VERSION = 'latest'; $env:FLAVOR = 'official'` in front of the
compose command, as on the other platforms.

## Local checks

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

```bash
docker run --rm -v "$PWD:/work" -w /work apache/skywalking-eyes:0.9.0 header check
docker run --rm -v "$PWD:/work" -w /work apache/skywalking-eyes:0.9.0 header fix
docker run --rm -v "$PWD:/src" ghcr.io/google/addlicense:v1.2.0 -check -f .license.tpl tests/e2e/windows/*.ps1
docker run --rm -v "$PWD:/src" ghcr.io/google/addlicense:v1.2.0 -f .license.tpl tests/e2e/windows/*.ps1

# Lua and Go checks (.github/workflows/lint.yml).
cd tests/e2e
go test -short ./...
test -z "$(gofmt -l .)"
go vet ./...
cd ../..

stylua --check .
mise x aqua:LuaLS/lua-language-server@3.19.1 -- lua-language-server --check . --checklevel=Warning
lua-language-server --check . --checklevel=Warning
```

```bash
# License headers (.github/workflows/license.yml).
license-eye header check
license-eye header fix
addlicense -check -f .license.tpl tests/e2e/windows/*.ps1
addlicense -f .license.tpl tests/e2e/windows/*.ps1
```

On a Docker-only host the license checks run as containers instead (the same
images CI uses):

```bash
docker run --rm -v "$PWD:/work" -w /work apache/skywalking-eyes:0.9.0 header check
docker run --rm -v "$PWD:/work" -w /work apache/skywalking-eyes:0.9.0 header fix
docker run --rm -v "$PWD:/src" ghcr.io/google/addlicense:v1.2.0 -check -f .license.tpl tests/e2e/windows/*.ps1
docker run --rm -v "$PWD:/src" ghcr.io/google/addlicense:v1.2.0 -f .license.tpl tests/e2e/windows/*.ps1
```
2 changes: 1 addition & 1 deletion tests/e2e/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// - common: target-independent shared logic (matrix expansion, version
// defaults, retry counts, name handling);
// - linux: the Linux container suite (bash-driven), run in CI and locally
// via tests/e2e/linux/compose.yaml as `go test ./linux/`;
// through the repository dev container (.devcontainer/, see docs/e2e.md);
// - windows: the Windows container suite (pwsh-driven), run via
// tests/e2e/windows/compose.amd64.yaml (amd64) or
// tests/e2e/windows/compose.arm64.yaml (arm64) as `go test ./windows/`.
Expand Down
10 changes: 5 additions & 5 deletions tests/e2e/linux/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ FROM ubuntu:26.04

ENV DEBIAN_FRONTEND=noninteractive

# NOTE: no heredoc here. This Dockerfile is built through the daemon's
# classic `POST /build` endpoint via testcontainers-go
# (tests/e2e/linux/e2e_test.go), which does not understand `RUN <<EOF`
# heredocs and silently drops their body. The chained-`;` form below works
# on both the classic builder and BuildKit (`docker build`).
# NOTE: no heredoc here. This image is built through testcontainers-go, whose
# classic-builder path accepts `RUN <<EOF` but silently drops the body, and
# whose BuildKit opt-in (the API's `version=2`) fails because it closes the
# build context before the daemon has consumed it. The chained-`;` form below
# is safe on both builders.
RUN set -o errexit -o nounset -o xtrace; \
apt-get update; \
apt-get install --assume-yes \
Expand Down
61 changes: 0 additions & 61 deletions tests/e2e/linux/compose.yaml

This file was deleted.

Loading
Loading