Skip to content

Hardware and Wokwi tests #2068

Hardware and Wokwi tests

Hardware and Wokwi tests #2068

name: Hardware and Wokwi tests
on:
workflow_run:
workflows: ["Runtime Tests"]
types:
- completed
# No permissions by default
permissions:
contents: read
jobs:
get-artifacts:
name: Get required artifacts
if: github.event.workflow_run.conclusion != 'cancelled'
runs-on: ubuntu-latest
permissions:
actions: read
statuses: write
pull-requests: read
outputs:
pr_num: ${{ steps.set-ref.outputs.pr_num }}
ref: ${{ steps.set-ref.outputs.ref }}
base: ${{ steps.set-ref.outputs.base }}
hw_types: ${{ steps.set-ref.outputs.hw_types }}
hw_targets: ${{ steps.set-ref.outputs.hw_targets }}
wokwi_types: ${{ steps.set-ref.outputs.wokwi_types }}
wokwi_targets: ${{ steps.set-ref.outputs.wokwi_targets }}
hw_tests_enabled: ${{ steps.set-ref.outputs.hw_tests_enabled }}
wokwi_tests_enabled: ${{ steps.set-ref.outputs.wokwi_tests_enabled }}
rerun_wokwi_test: ${{ steps.set-ref.outputs.rerun_wokwi_test }}
push_time: ${{ steps.set-ref.outputs.push_time }}
steps:
- name: Report pending
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const owner = '${{ github.repository_owner }}';
const repo = '${{ github.repository }}'.split('/')[1];
const sha = '${{ github.event.workflow_run.head_sha }}';
core.debug(`owner: ${owner}`);
core.debug(`repo: ${repo}`);
core.debug(`sha: ${sha}`);
const { context: name, state } = (await github.rest.repos.createCommitStatus({
context: 'Runtime Tests / Wokwi (Get artifacts) (${{ github.event.workflow_run.event }} -> workflow_run)',
owner: owner,
repo: repo,
sha: sha,
state: 'pending',
target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
})).data;
core.info(`${name} is ${state}`);
- name: Download and extract event file
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: event_file
path: artifacts/event_file
- name: Download and extract matrix info
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: matrix_info
path: artifacts/matrix_info
- name: Get info
env:
GITLAB_ACCESS_TOKEN: ${{ secrets.GITLAB_ACCESS_TOKEN }}
WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
WR_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
WR_EVENT: ${{ github.event.workflow_run.event }}
WR_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
WR_RUN_ID: ${{ github.event.workflow_run.id }}
WR_HEAD_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
id: set-ref
run: |
# Security: derive ref, base, and labels from trusted sources only.
# - workflow_run event properties are passed as env vars (tamper-proof, injection-safe)
# - PR metadata is fetched via GitHub API (tamper-proof)
# - Artifact event file is NOT trusted for security-critical values
# (a fork PR could upload a crafted event file in the triggering workflow)
# - All workflow_run values are passed via env vars rather than inline
# expressions to prevent script injection through crafted branch names
# - For PR events, the script fails closed if the base branch cannot be
# determined from trusted sources (never falls back to head branch)
pr_num=""
base=""
ref=""
has_hil_label="false"
has_rerun_label="false"
if [ "$WR_EVENT" == "pull_request" ]; then
echo "Original event: pull_request — resolving PR info from GitHub API..."
# Validate SHA format before using in API calls
if ! echo "$WR_HEAD_SHA" | grep -qE '^[a-f0-9]{40}$'; then
echo "ERROR: Invalid SHA format: $WR_HEAD_SHA"
exit 1
fi
# Trusted: get PR number from commit SHA via GitHub API
pr_num=$(gh api "repos/$GH_REPO/commits/$WR_HEAD_SHA/pulls" \
--jq '.[0].number // empty' 2>/dev/null | tr -cd "[:digit:]") || true
# Fallback: commits API only works for same-repo PRs; use search API for fork PRs
if [ -z "$pr_num" ]; then
echo "Commits API returned empty, scanning open PRs by head SHA for fork PRs..."
pr_num=$(gh api "repos/$GH_REPO/pulls?state=open&per_page=100" \
--paginate \
--jq ".[] | select(.head.sha == \"$WR_HEAD_SHA\") | .number" \
2>/dev/null | head -1 | tr -cd "[:digit:]") || true
fi
if [ -n "$pr_num" ]; then
echo "Found PR #$pr_num"
ref="$pr_num"
# Trusted: get base branch and labels from the PR via API
pr_data=$(gh api "repos/$GH_REPO/pulls/$pr_num" 2>/dev/null) || true
if [ -n "$pr_data" ]; then
base=$(echo "$pr_data" | jq -r '.base.ref // empty' | tr -cd "[:alnum:]/_.-")
has_hil_label=$(echo "$pr_data" | jq -r '[.labels[].name] | if index("hil_test") then "true" else "false" end')
has_rerun_label=$(echo "$pr_data" | jq -r '[.labels[].name] | if index("Re-trigger Wokwi Tests") then "true" else "false" end')
echo "PR base: $base"
fi
else
echo "WARNING: Could not find PR for SHA $WR_HEAD_SHA"
fi
# Fail closed: refuse to fall back to head branch for PR events
if [ -z "$ref" ] || [ -z "$base" ]; then
echo "ERROR: Could not determine PR number or base branch from GitHub API"
echo " - pr_num='$pr_num' base='$base' head_sha='$WR_HEAD_SHA'"
echo "Failing closed for security — will not fall back to head branch for PR events"
exit 1
fi
else
echo "Original event: $WR_EVENT — using workflow_run.head_branch"
# For non-PR events (workflow_dispatch, schedule), head_branch is the correct value
if [ -z "$ref" ]; then
ref=$(echo "$WR_HEAD_BRANCH" | tr -cd "[:alnum:]/_.-")
fi
if [ -z "$base" ]; then
base=$(echo "$WR_HEAD_BRANCH" | tr -cd "[:alnum:]/_.-")
fi
fi
# Non-security-critical values from artifacts
action=$(jq -r '.action' artifacts/event_file/event.json | tr -cd "[:alpha:]_")
if [ "$action" == "null" ]; then
action=""
fi
push_time=$(jq -r '.repository.pushed_at' artifacts/event_file/event.json | tr -cd "[:alnum:]:-")
if [ -z "$push_time" ] || [ "$push_time" == "null" ]; then
push_time=""
fi
# HW tests enablement
if [ -n "$GITLAB_ACCESS_TOKEN" ]; then
hw_tests_enabled="true"
if [[ -n "$pr_num" && "$has_hil_label" != "true" ]]; then
echo "PR does not have hil_test label, hardware tests will be disabled"
hw_tests_enabled="false"
fi
else
echo "GITLAB_ACCESS_TOKEN is not set, hardware tests will be disabled"
hw_tests_enabled="false"
fi
if [ -n "$WOKWI_CLI_TOKEN" ]; then
wokwi_tests_enabled="true"
else
echo "WOKWI_CLI_TOKEN is not set, wokwi tests will be disabled"
wokwi_tests_enabled="false"
fi
rerun_wokwi_test="false"
if [[ -n "$pr_num" && "$has_rerun_label" == "true" ]]; then
rerun_wokwi_test="true"
echo "PR has Re-trigger Wokwi Tests label, cached results will be ignored"
fi
# Test matrix from artifacts (non-security-critical)
hw_targets=$(jq -c '.hw_targets' artifacts/matrix_info/test_matrix.json | tr -cd "[:alnum:],[]\"")
hw_types=$(jq -c '.hw_types' artifacts/matrix_info/test_matrix.json | tr -cd "[:alpha:],[]\"")
wokwi_targets=$(jq -c '.wokwi_targets' artifacts/matrix_info/test_matrix.json | tr -cd "[:alnum:],[]\"")
wokwi_types=$(jq -c '.wokwi_types' artifacts/matrix_info/test_matrix.json | tr -cd "[:alpha:],[]\"")
qemu_tests_enabled=$(jq -r '.qemu_enabled' artifacts/matrix_info/test_matrix.json | tr -cd "[:alpha:]")
qemu_targets=$(jq -c '.qemu_targets' artifacts/matrix_info/test_matrix.json | tr -cd "[:alnum:],[]\"")
qemu_types=$(jq -c '.qemu_types' artifacts/matrix_info/test_matrix.json | tr -cd "[:alpha:],[]\"")
echo "base = $base"
echo "hw_targets = $hw_targets"
echo "hw_types = $hw_types"
echo "wokwi_targets = $wokwi_targets"
echo "wokwi_types = $wokwi_types"
echo "qemu_tests_enabled = $qemu_tests_enabled"
echo "qemu_targets = $qemu_targets"
echo "qemu_types = $qemu_types"
echo "pr_num = $pr_num"
echo "hw_tests_enabled = $hw_tests_enabled"
echo "wokwi_tests_enabled = $wokwi_tests_enabled"
echo "rerun_wokwi_test = $rerun_wokwi_test"
echo "push_time = $push_time"
echo "Has hil_test label: $has_hil_label"
echo "Has Re-trigger Wokwi Tests label: $has_rerun_label"
cat > artifacts/workflow_info.json <<EOF
{
"hw_tests_enabled": $hw_tests_enabled,
"hw_targets": $hw_targets,
"hw_types": $hw_types,
"wokwi_tests_enabled": $wokwi_tests_enabled,
"wokwi_targets": $wokwi_targets,
"wokwi_types": $wokwi_types,
"qemu_tests_enabled": $qemu_tests_enabled,
"qemu_targets": $qemu_targets,
"qemu_types": $qemu_types,
"ref": "$ref",
"event": "$WR_EVENT",
"sha": "$WR_HEAD_SHA",
"action": "$action",
"run_id": "$WR_RUN_ID",
"conclusion": "$WR_CONCLUSION"
}
EOF
echo "Ref = $ref"
echo "Event name = $WR_EVENT"
echo "Head SHA = $WR_HEAD_SHA"
echo "Action = $action"
echo "Run ID = $WR_RUN_ID"
echo "Conclusion = $WR_CONCLUSION"
if [ -z "$ref" ] || [ "$ref" == "null" ]; then
echo "Failed to get PR number or ref"
exit 1
fi
echo "pr_num=$pr_num" >> $GITHUB_OUTPUT
echo "base=$base" >> $GITHUB_OUTPUT
echo "hw_targets=$hw_targets" >> $GITHUB_OUTPUT
echo "hw_types=$hw_types" >> $GITHUB_OUTPUT
echo "wokwi_targets=$wokwi_targets" >> $GITHUB_OUTPUT
echo "wokwi_types=$wokwi_types" >> $GITHUB_OUTPUT
echo "ref=$ref" >> $GITHUB_OUTPUT
echo "hw_tests_enabled=$hw_tests_enabled" >> $GITHUB_OUTPUT
echo "wokwi_tests_enabled=$wokwi_tests_enabled" >> $GITHUB_OUTPUT
echo "rerun_wokwi_test=$rerun_wokwi_test" >> $GITHUB_OUTPUT
echo "push_time=$push_time" >> $GITHUB_OUTPUT
- name: Download and extract parent QEMU results
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
pattern: test-results-qemu-*
merge-multiple: true
path: artifacts/results/qemu
- name: Upload parent artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: parent-artifacts
path: artifacts
if-no-files-found: error
- name: Report conclusion
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
if: always()
with:
script: |
const owner = '${{ github.repository_owner }}';
const repo = '${{ github.repository }}'.split('/')[1];
const sha = '${{ github.event.workflow_run.head_sha }}';
core.debug(`owner: ${owner}`);
core.debug(`repo: ${repo}`);
core.debug(`sha: ${sha}`);
const { context: name, state } = (await github.rest.repos.createCommitStatus({
context: 'Runtime Tests / Wokwi (Get artifacts) (${{ github.event.workflow_run.event }} -> workflow_run)',
owner: owner,
repo: repo,
sha: sha,
state: '${{ job.status }}',
target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
})).data;
core.info(`${name} is ${state}`);
hardware-test:
name: Internal Hardware Tests
if: |
(github.event.workflow_run.conclusion == 'success' ||
github.event.workflow_run.conclusion == 'failure' ||
github.event.workflow_run.conclusion == 'timed_out') &&
needs.get-artifacts.outputs.hw_tests_enabled == 'true'
runs-on: ubuntu-latest
needs: get-artifacts
env:
id: ${{ needs.get-artifacts.outputs.ref }}-${{ github.event.workflow_run.head_sha || github.sha }}
permissions:
actions: read
statuses: write
steps:
- name: Report pending
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const owner = '${{ github.repository_owner }}';
const repo = '${{ github.repository }}'.split('/')[1];
const sha = '${{ github.event.workflow_run.head_sha }}';
core.debug(`owner: ${owner}`);
core.debug(`repo: ${repo}`);
core.debug(`sha: ${sha}`);
const { context: name, state } = (await github.rest.repos.createCommitStatus({
context: 'Runtime Tests / Internal Hardware Tests (${{ github.event.workflow_run.event }} -> workflow_run)',
owner: owner,
repo: repo,
sha: sha,
state: 'pending',
target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
})).data;
core.info(`${name} is ${state}`);
- name: Check if already passed
id: get-cache-results
if: needs.get-artifacts.outputs.pr_num
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
key: test-${{ env.id }}-results-hw
path: |
tests/**/*.xml
tests/**/result_*.json
- name: Evaluate if tests should be run
id: check-tests
env:
CACHE_HIT: ${{ steps.get-cache-results.outputs.cache-hit }}
run: |
enabled=true
# Check cache first
if [[ "$CACHE_HIT" == 'true' ]]; then
echo "Already ran, skipping GitLab pipeline trigger"
enabled=false
else
echo "Cache miss, hardware tests will run"
fi
echo "enabled=$enabled" >> $GITHUB_OUTPUT
- name: Wait for GitLab sync and prepare variables
if: ${{ steps.check-tests.outputs.enabled == 'true' }}
id: prepare-variables
env:
PUSH_TIME: ${{ needs.get-artifacts.outputs.push_time }}
HW_TYPES: ${{ needs.get-artifacts.outputs.hw_types }}
HW_TARGETS: ${{ needs.get-artifacts.outputs.hw_targets }}
run: |
# A webhook to sync the repository is sent to GitLab when a commit is pushed to GitHub
# We wait for 10 minutes after the push to GitHub to be safe
echo "Ensuring GitLab sync has completed before triggering pipeline..."
# Use push time determined in get-artifacts job
push_time="$PUSH_TIME"
if [ -n "$push_time" ]; then
echo "Push time: $push_time"
# Convert push time to epoch
push_epoch=$(date -d "$push_time" +%s 2>/dev/null || echo "")
if [ -n "$push_epoch" ]; then
current_epoch=$(date +%s)
elapsed_minutes=$(( (current_epoch - push_epoch) / 60 ))
echo "Elapsed time since push: ${elapsed_minutes} minutes"
if [ $elapsed_minutes -lt 10 ]; then
wait_time=$(( (10 - elapsed_minutes) * 60 ))
echo "Waiting ${wait_time} seconds for GitLab sync to complete..."
sleep $wait_time
else
echo "GitLab sync should be complete (${elapsed_minutes} minutes elapsed)"
fi
else
echo "Could not parse push timestamp, waiting 60 seconds as fallback..."
sleep 60
fi
else
echo "Could not determine push time, waiting 60 seconds as fallback..."
sleep 60
fi
echo "Proceeding with GitLab pipeline trigger..."
# Make targets/types comma-separated strings (remove brackets and quotes)
test_types=$(printf '%s' "$HW_TYPES" | sed -e 's/[][]//g' -e 's/"//g')
test_chips=$(printf '%s' "$HW_TARGETS" | sed -e 's/[][]//g' -e 's/"//g')
echo "test_types=$test_types"
echo "test_chips=$test_chips"
# Expose as step outputs
echo "test_types=$test_types" >> $GITHUB_OUTPUT
echo "test_chips=$test_chips" >> $GITHUB_OUTPUT
- name: Trigger GitLab Pipeline and Download Artifacts
if: ${{ steps.check-tests.outputs.enabled == 'true' }}
uses: digital-blueprint/gitlab-pipeline-trigger-action@20e77989b24af658ba138a0aa5291bdc657f1505 # v1.3.0
id: gitlab-trigger
with:
host: ${{ secrets.GITLAB_URL }}
id: ${{ secrets.GITLAB_PROJECT_ID }}
ref: ${{ needs.get-artifacts.outputs.base || github.ref }}
trigger_token: ${{ secrets.GITLAB_TRIGGER_TOKEN }}
access_token: ${{ secrets.GITLAB_ACCESS_TOKEN }}
download_artifacts: 'true'
download_artifacts_on_failure: 'true'
download_path: './gitlab-artifacts'
variables: >-
{
"TEST_TYPES":"${{ steps.prepare-variables.outputs.test_types }}",
"TEST_CHIPS":"${{ steps.prepare-variables.outputs.test_chips }}",
"PIPELINE_ID":"${{ env.id }}",
"BINARIES_RUN_ID":"${{ github.event.workflow_run.id }}",
"GITHUB_REPOSITORY":"${{ github.repository }}",
"GITHUB_REF":"${{ needs.get-artifacts.outputs.base || github.ref }}"
}
- name: Process Downloaded Artifacts
if: ${{ always() && steps.check-tests.outputs.enabled == 'true' }}
env:
GL_STATUS: ${{ steps.gitlab-trigger.outputs.status }}
GL_ARTIFACTS_DOWNLOADED: ${{ steps.gitlab-trigger.outputs.artifacts_downloaded }}
run: |
echo "GitLab Pipeline Status: $GL_STATUS"
echo "Artifacts Downloaded: $GL_ARTIFACTS_DOWNLOADED"
# Create tests directory structure expected by GitHub caching
mkdir -p tests
# Process downloaded GitLab artifacts
if [ "$GL_ARTIFACTS_DOWNLOADED" = "true" ]; then
echo "Processing downloaded GitLab artifacts..."
# Find and copy test result files while preserving directory structure
# The GitLab artifacts have the structure: gitlab-artifacts/job_*/artifacts/tests/...
# We want to preserve the tests/... part of the structure
for job_dir in ./gitlab-artifacts/job_*; do
if [ -d "$job_dir/artifacts/tests" ]; then
# Merge results into tests/ without failing on non-empty directories
echo "Merging $job_dir/artifacts/tests/ into tests/"
cp -a "$job_dir/artifacts/tests/." tests/
fi
done
echo "Test results found:"
ls -laR tests/ || echo "No test results found"
else
echo "No artifacts were downloaded from GitLab"
fi
- name: Upload hardware results as cache
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
if: steps.check-tests.outputs.enabled == 'true' && needs.get-artifacts.outputs.pr_num
with:
key: test-${{ env.id }}-results-hw
path: |
tests/**/*.xml
tests/**/result_*.json
- name: Upload hardware results as artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: always()
with:
name: test-results-hw
overwrite: true
path: |
tests/**/*.xml
tests/**/result_*.json
- name: Report conclusion
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
if: always()
with:
script: |
const owner = '${{ github.repository_owner }}';
const repo = '${{ github.repository }}'.split('/')[1];
const sha = '${{ github.event.workflow_run.head_sha }}';
core.debug(`owner: ${owner}`);
core.debug(`repo: ${repo}`);
core.debug(`sha: ${sha}`);
const { context: name, state } = (await github.rest.repos.createCommitStatus({
context: 'Runtime Tests / Internal Hardware Tests (${{ github.event.workflow_run.event }} -> workflow_run)',
owner: owner,
repo: repo,
sha: sha,
state: '${{ job.status }}',
target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
})).data;
core.info(`${name} is ${state}`);
wokwi-test:
name: Wokwi ${{ matrix.chip }} ${{ matrix.type }} tests
if: |
(github.event.workflow_run.conclusion == 'success' ||
github.event.workflow_run.conclusion == 'failure' ||
github.event.workflow_run.conclusion == 'timed_out') &&
needs.get-artifacts.outputs.wokwi_tests_enabled == 'true'
runs-on: ubuntu-latest
needs: get-artifacts
env:
id: ${{ needs.get-artifacts.outputs.ref }}-${{ github.event.workflow_run.head_sha || github.sha }}-${{ matrix.chip }}-${{ matrix.type }}
permissions:
actions: read
statuses: write
pull-requests: write
strategy:
fail-fast: false
matrix:
type: ${{ fromJson(needs.get-artifacts.outputs.wokwi_types) }}
chip: ${{ fromJson(needs.get-artifacts.outputs.wokwi_targets) }}
steps:
- name: Report pending
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const owner = '${{ github.repository_owner }}';
const repo = '${{ github.repository }}'.split('/')[1];
const sha = '${{ github.event.workflow_run.head_sha }}';
core.debug(`owner: ${owner}`);
core.debug(`repo: ${repo}`);
core.debug(`sha: ${sha}`);
const { context: name, state } = (await github.rest.repos.createCommitStatus({
context: 'Runtime Tests / Wokwi (${{ matrix.type }}, ${{ matrix.chip }}) / Wokwi ${{ matrix.chip }} ${{ matrix.type }} tests (${{ github.event.workflow_run.event }} -> workflow_run)',
owner: owner,
repo: repo,
sha: sha,
state: 'pending',
target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
})).data;
core.info(`${name} is ${state}`);
- name: Check if already passed
id: get-cache-results
if: needs.get-artifacts.outputs.pr_num && needs.get-artifacts.outputs.rerun_wokwi_test == 'false'
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
key: test-${{ env.id }}-results-wokwi
path: |
tests/**/*.xml
tests/**/result_*.json
- name: Evaluate if tests should be run
id: check-tests
env:
CACHE_HIT: ${{ steps.get-cache-results.outputs.cache-hit }}
RERUN_WOKWI: ${{ needs.get-artifacts.outputs.rerun_wokwi_test }}
run: |
if [[ "$RERUN_WOKWI" == "true" ]]; then
echo "Re-trigger Wokwi Tests label is present, running tests (cache restore was skipped)"
enabled=true
elif [[ "$CACHE_HIT" == "true" ]]; then
echo "Already ran, skipping"
enabled=false
else
echo "No cache found, tests will run"
enabled=true
fi
echo "enabled=$enabled" >> $GITHUB_OUTPUT
# Security: for PRs, `base` is the PR's base branch (from API), so only trusted code is checked out.
# For workflow_dispatch/schedule, `base` is the selected/default branch (from workflow_run event).
# DO NOT use artifact-derived values for the checkout ref — they can be forged by a malicious PR.
- name: Checkout repository
if: ${{ steps.check-tests.outputs.enabled == 'true' }}
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ needs.get-artifacts.outputs.base || github.ref }}
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.0.4
if: ${{ steps.check-tests.outputs.enabled == 'true' }}
with:
cache-dependency-path: tests/requirements.txt
cache: "pip"
python-version: "3.x"
- name: Install dependencies
if: ${{ steps.check-tests.outputs.enabled == 'true' }}
run: |
pip install -U pip
pip install -r tests/requirements.txt
- name: Wokwi CI Server
if: ${{ steps.check-tests.outputs.enabled == 'true' }}
uses: wokwi/wokwi-ci-server-action@a6fabb5a49e080158c7a1d121ea5b789536a82c3 # v1
- name: Install Wokwi CLI
if: ${{ steps.check-tests.outputs.enabled == 'true' }}
run: |
curl -L https://wokwi.com/ci/install.sh | sh
source ~/.bashrc
wokwi-cli --version
- name: Get binaries
if: ${{ steps.check-tests.outputs.enabled == 'true' }}
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: test-bin-${{ matrix.chip }}-${{ matrix.type }}
path: |
~/.arduino/tests/${{ matrix.chip }}
- name: Run Tests
if: ${{ steps.check-tests.outputs.enabled == 'true' }}
env:
WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }}
WOKWI_WIFI_SSID: "Wokwi-GUEST"
# The Wokwi Wi-Fi does not have a password, so we use an empty string
WOKWI_WIFI_PASSWORD: ""
TEST_TYPE: ${{ matrix.type }}
TEST_CHIP: ${{ matrix.chip }}
run: |
bash .github/scripts/tests_run.sh -c -type "$TEST_TYPE" -t "$TEST_CHIP" -i 0 -m 1 -W -wifi-ssid "$WOKWI_WIFI_SSID" -wifi-password "$WOKWI_WIFI_PASSWORD"
- name: Upload ${{ matrix.chip }} ${{ matrix.type }} Wokwi results as cache
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
if: steps.check-tests.outputs.enabled == 'true' && needs.get-artifacts.outputs.pr_num
with:
key: test-${{ env.id }}-results-wokwi
path: |
tests/**/*.xml
tests/**/result_*.json
- name: Upload ${{ matrix.chip }} ${{ matrix.type }} Wokwi results as artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: always()
with:
name: test-results-wokwi-${{ matrix.chip }}-${{ matrix.type }}
overwrite: true
path: |
tests/**/*.xml
tests/**/result_*.json
- name: Report conclusion
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
if: always()
with:
script: |
const owner = '${{ github.repository_owner }}';
const repo = '${{ github.repository }}'.split('/')[1];
const sha = '${{ github.event.workflow_run.head_sha }}';
core.debug(`owner: ${owner}`);
core.debug(`repo: ${repo}`);
core.debug(`sha: ${sha}`);
const { context: name, state } = (await github.rest.repos.createCommitStatus({
context: 'Runtime Tests / Wokwi (${{ matrix.type }}, ${{ matrix.chip }}) / Wokwi ${{ matrix.chip }} ${{ matrix.type }} tests (${{ github.event.workflow_run.event }} -> workflow_run)',
owner: owner,
repo: repo,
sha: sha,
state: '${{ job.status }}',
target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
})).data;
core.info(`${name} is ${state}`);
- name: Remove Re-trigger Wokwi Tests label if it was present
if: |
always() &&
needs.get-artifacts.outputs.pr_num &&
needs.get-artifacts.outputs.rerun_wokwi_test == 'true'
continue-on-error: true
run: |
gh pr edit "$PR_NUM" --repo "$GITHUB_REPOSITORY" --remove-label 'Re-trigger Wokwi Tests'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUM: ${{ needs.get-artifacts.outputs.pr_num }}
GITHUB_REPOSITORY: ${{ github.repository }}