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
83 changes: 69 additions & 14 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,19 +152,79 @@ jobs:
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: size-${{ matrix.example }}
name: size-x86-${{ matrix.example }}
path: examples/${{ matrix.example }}/size.json
if-no-files-found: ignore

- name: Stop BuildKit
if: always()
run: docker stop buildkit

# Collects size.json artifacts from all matrix jobs, merges them into a single
# benchmark payload. On main, pushes results to gh-pages to track history.
# Same as `test`, but runs on native ARM hardware to catch ARM-specific failures
# without QEMU emulation overhead.
test-arm:
needs: [build-images, find-examples]
runs-on: ubuntu-24.04-arm
strategy:
matrix:
example: ${{ fromJson(needs.find-examples.outputs.examples) }}
fail-fast: false

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db

- name: Cache Go modules and build artifacts
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306
with:
path: |
~/.cache/go-build
~/go/pkg/mod
# include runner.arch so ARM and x86 caches don't collide
key: go-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('mise.lock') }}-${{ hashFiles('go.sum') }}
restore-keys: |
go-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('mise.lock') }}-

- name: Log in to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
registry: ${{ env.PRODUCTION_CONTAINER_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Start BuildKit
run: mise run run-buildkit-container

# QEMU provides x86 emulation on this ARM runner for any cross-platform build steps
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130

- name: Run test for ${{ matrix.example }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
go test -v ./integration_tests \
-run "^TestExamplesIntegration/${{ matrix.example }}$" \
-timeout 20m

- name: Upload size.json
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: size-arm-${{ matrix.example }}
path: examples/${{ matrix.example }}/size.json
if-no-files-found: ignore

- name: Stop BuildKit
if: always()
run: docker stop buildkit

# Collects size.json artifacts from both x86 and ARM matrix jobs, merges them
# into a single benchmark payload with arch-suffixed names (e.g. "node-bun (x86)").
# On main, pushes results to gh-pages to track history.
# On PRs, compares against stored history and posts a comment with the delta.
docker-image-benchmark:
needs: test
needs: [test, test-arm]
runs-on: ubuntu-latest
permissions:
# required for benchmark-action to push to gh-pages on main
Expand All @@ -177,24 +237,19 @@ jobs:

- name: Download all size artifacts
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
# each artifact lands in its own size-<example>/ directory
# each artifact lands in its own size-{x86,arm}-<example>/ directory
with:
pattern: size-*
merge-multiple: false

- name: Merge size.json files into benchmark format
# customSmallerIsBetter expects [{name, unit, value}]; also emit a simple size.json for manual inspection
# customSmallerIsBetter expects [{name, unit, value}]; suffix name with arch for side-by-side comparison
run: |
# create a master size.json for manual debugging
jq -s '[.[] | .] | sort_by(.name)' size-*/size.json > size.json
jq -s 'map(. + {name: (.name + " (x86)")})' size-x86-*/size.json > x86.json
jq -s 'map(. + {name: (.name + " (arm)")})' size-arm-*/size.json > arm.json
# this debugging data is compared against the "master" copy in gh-pages
jq -s '[.[] | {name: .name, unit: "bytes", value: .size}]' size-*/size.json > benchmark.json

- name: Upload merged size.json
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: sizes
path: size.json
jq -s 'add | sort_by(.name) | map({name, unit: "bytes", value: .size})' x86.json arm.json > benchmark.json

- name: Store benchmark results
uses: benchmark-action/github-action-benchmark@a7bc2366eda11037936ea57d811a43b3418d3073
Expand Down
7 changes: 6 additions & 1 deletion docs/src/content/docs/languages/node.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ Railpack automatically installs system dependencies for certain packages:

- **Puppeteer**: When detected in workspace dependencies, Railpack installs
all necessary system packages for running headless Chrome, including
`xvfb`, `chromium` dependencies, and font libraries
`xvfb`, `chromium` dependencies, and font libraries. Note that
Puppeteer's bundled Chromium [does not support
ARM64](https://github.com/puppeteer/puppeteer/issues/7740); if you need
to run on ARM hardware, consider switching to
[Playwright](#system-dependencies) or implementing a custom workaround
(e.g. installing a system Chromium and pointing `executablePath` at it).
- **Playwright**: When detected in workspace dependencies, Railpack installs
the necessary system packages and the headless shell version of Chromium
5 changes: 4 additions & 1 deletion examples/node-puppeteer/test.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
[
{
"expectedOutput": "Hello from puppeteer"
"expectedOutput": "Hello from puppeteer",
// Puppeteer's bundled Chromium does not ship ARM64 binaries; skip on ARM runners
// See https://github.com/puppeteer/puppeteer/issues/7740
"skipArch": ["arm64"]
}
]
7 changes: 7 additions & 0 deletions integration_tests/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
Expand Down Expand Up @@ -55,6 +56,8 @@ type TestCase struct {
ShouldFail bool `json:"shouldFail"`
HTTPCheck *HTTPCheck `json:"httpCheck"`
StderrAllowed bool `json:"stderrAllowed"`
// architectures to skip, e.g. ["arm64"]. Matches against runtime.GOARCH.
SkipArch []string `json:"skipArch"`
}

func TestExamplesIntegration(t *testing.T) {
Expand Down Expand Up @@ -119,6 +122,10 @@ func TestExamplesIntegration(t *testing.T) {
t.Run(testName, func(t *testing.T) {
t.Parallel()

if slices.Contains(testCase.SkipArch, runtime.GOARCH) {
t.Skipf("skipping %s on %s", entry.Name(), runtime.GOARCH)
}

fmt.Printf("\033[32mRunning: examples/%s\033[0m\n", entry.Name())

userApp, err := app.NewApp(examplePath)
Expand Down
Loading