Skip to content

Devcontainer Feature - Test #5

Devcontainer Feature - Test

Devcontainer Feature - Test #5

name: Devcontainer Feature - Test
# Runs the official `devcontainer features test` matrix from @devcontainers/cli
# whenever the feature source or test fixtures change. PRs run a 4-scenario
# subset (one per package-manager family + option-coverage scenarios); the full
# 6-scenario matrix runs nightly to catch base-image drift on Debian and Fedora
# without blocking PR throughput.
#
# A single aggregation job ("Devcontainer Feature - Test Result") is what gets
# wired into branch protection -- adding/removing matrix legs does not require
# editing the required-checks list.
#
# Reference: https://containers.dev/implementors/features-distribution/
on:
pull_request:
branches: [ main ]
paths:
- 'devcontainer/**'
- '.github/workflows/devcontainer-feature-test.yml'
merge_group:
branches: [ main ]
types: [ checks_requested ]
schedule:
# Nightly full matrix at 06:00 UTC catches base-image drift (glibc, musl,
# apt/apk/dnf) within 24h independent of PR activity.
- cron: '0 6 * * *'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: devcontainer-feature-test-${{ github.ref }}
cancel-in-progress: true
jobs:
unit-bats:
name: bats unit tests
runs-on: ubuntu-24.04
# The bats sandbox uses PATH="$STUB_BIN:/bin" to isolate install.sh from
# the real environment. On Ubuntu 24.04 /bin is a symlink to /usr/bin
# (merged-/usr), so real apt-get / pip / curl shadow the stubs and
# negative-path tests false-pass. Alpine 3.20 keeps /bin as busybox-only
# (apk lives in /sbin which is not on the test PATH), so the stubs work
# exactly as the test author designed.
container:
image: alpine:3.20
permissions:
contents: read
steps:
- name: Install bash + git (bats needs bash; submodule checkout needs git)
run: apk add --no-cache bash git
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Run bats unit tests
working-directory: devcontainer/test/apm-cli/unit
run: ../../bats/bin/bats install.bats
integration-matrix:
name: features test (${{ matrix.scenario }})
runs-on: ubuntu-24.04
permissions:
contents: read
strategy:
fail-fast: false
matrix:
# PR + merge_group: cheap representative subset (one per pkg-manager
# family + option/ordering coverage). schedule/workflow_dispatch adds
# default-debian-12 + default-fedora to catch base-image drift.
scenario:
- default-ubuntu-24
- default-alpine-3
- pinned-version
- with-python-feature
include:
- scenario: default-debian-12
nightly_only: true
- scenario: default-fedora
nightly_only: true
steps:
- name: Skip nightly-only leg on PR
id: gate
run: |
if [ "${{ matrix.nightly_only }}" = "true" ] && [ "${{ github.event_name }}" != "schedule" ] && [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Skipping ${{ matrix.scenario }} (nightly-only)."
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@v4
if: steps.gate.outputs.skip == 'false'
- name: Set up Node
if: steps.gate.outputs.skip == 'false'
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install @devcontainers/cli
if: steps.gate.outputs.skip == 'false'
run: npm install -g @devcontainers/cli@latest
- name: Run devcontainer features test
if: steps.gate.outputs.skip == 'false'
run: |
devcontainer features test \
--features apm-cli \
--filter "${{ matrix.scenario }}" \
--skip-autogenerated \
--project-folder devcontainer
test-result:
name: Devcontainer Feature - Test Result
if: always()
needs: [ unit-bats, integration-matrix ]
runs-on: ubuntu-24.04
steps:
- name: Aggregate
run: |
if [ "${{ needs.unit-bats.result }}" != "success" ]; then
echo "unit-bats failed: ${{ needs.unit-bats.result }}"
exit 1
fi
if [ "${{ needs.integration-matrix.result }}" != "success" ] && [ "${{ needs.integration-matrix.result }}" != "skipped" ]; then
echo "integration-matrix failed: ${{ needs.integration-matrix.result }}"
exit 1
fi
echo "All devcontainer feature tests passed."