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
36 changes: 36 additions & 0 deletions .github/workflows/test-slash-command.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Test (Slash Command)

# Listens for /test comments on pull requests.
# Because issue_comment always runs in the base-repo context, this workflow
# has access to repository secrets even for fork PRs.
#
# When a maintainer (write/maintain/admin) comments "/test" on a PR:
# - peter-evans/slash-command-dispatch validates the permission, adds 👀/🚀
# reactions, and fires a repository_dispatch event of type "test-command".
# - test.yaml picks up that event and runs the matrix tests against the
# PR's head SHA (available in client_payload.pull_request).
#
# Requires a repo-scoped PAT stored as the COMMUNITY_ACTIONS_PAT secret so that the action can
# create repository_dispatch events (GITHUB_TOKEN cannot do this).

on:
issue_comment:
types: [created]

permissions:
contents: read

jobs:
slash-command-dispatch:
name: Dispatch /test command
# Only process comments on pull requests
if: github.event.issue.pull_request != null
runs-on: ubuntu-latest
steps:
- name: Slash Command Dispatch
uses: peter-evans/slash-command-dispatch@v5
with:
token: ${{ secrets.COMMUNITY_ACTIONS_PAT }}
commands: test
permission: write
issue-type: pull-request
145 changes: 98 additions & 47 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -1,49 +1,98 @@
name: Test

on:
workflow_dispatch:
# Use a manual approval process before PR's are given access to
# the secrets which are required to run the integration tests.
# The PR code should be manually approved to see if it can be trusted.
# When in doubt, do not approve the test run.
# Reference: https://dev.to/petrsvihlik/using-environment-protection-rules-to-secure-secrets-when-building-external-forks-with-pullrequesttarget-hci
pull_request_target:
# Runs automatically for PRs from branches within this repo (maintainers).
# Fork PRs do not have access to secrets; maintainers must trigger those
# via a /test comment (see test-slash-command.yaml).
pull_request:
branches: [ main ]
# Triggered by test-slash-command.yaml via peter-evans/slash-command-dispatch
# when a maintainer comments /test on a fork PR.
repository_dispatch:
types: [test-command]
# Allows manually triggering a test run against a specific ref from the UI.
workflow_dispatch:
inputs:
ref:
description: "Commit SHA or branch to test"
required: false
default: ""
merge_group:

permissions:
contents: read

jobs:
approve:
name: Approve
environment:
# For security reasons, all pull requests need to be approved first before granting access to secrets
# So the environment should be set to have a reviewer/s inspect it before approving it
name: ${{ github.event_name == 'pull_request_target' && 'Test Pull Request' || 'Test Auto' }}
build:
name: Build
# Skip fork PRs on the pull_request event — they have no secret access.
# Those PRs are tested via repository_dispatch triggered by test-slash-command.yaml.
if: >-
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Wait for approval
run: echo "Approved"
- name: Checkout
uses: actions/checkout@v7
with:
ref: ${{ github.event.client_payload.pull_request.head.sha || inputs.ref || github.sha }}

- uses: taiki-e/install-action@just

- uses: actions/setup-go@v6
with:
go-version: stable
cache: false

- run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest
name: Install dependencies

- name: Build package (for testing)
run: just build

- name: Upload build artifacts
uses: actions/upload-artifact@v7
with:
name: dist-packages
path: dist/

test:
name: Test
needs: [approve]
permissions:
pull-requests: write
name: Test ${{ matrix.job.name }}
needs: [build]
environment:
name: Test Auto
permissions:
# Required to publish system test results to the PR
issues: write
pull-requests: write
contents: read
runs-on: ubuntu-latest
env:
IMAGE: ${{ matrix.job.image }}
TEST_IMAGE: ${{ matrix.job.image }}

strategy:
fail-fast: false
matrix:
job:
- { image: debian-12 }
- { name: "debian-12", image: "ghcr.io/thin-edge/tedge-demo-main-systemd:latest", test_options: "" }

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
# repository_dispatch: use the fork PR's head SHA from the slash command payload
# workflow_dispatch: use the manually specified ref (or default branch)
# pull_request / merge_group: use the default (current SHA)
ref: ${{ github.event.client_payload.pull_request.head.sha || inputs.ref || github.sha }}

- name: Download build artifacts
uses: actions/download-artifact@v8
with:
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }}
name: dist-packages
path: dist/

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: create .env file
run: |
Expand All @@ -52,53 +101,55 @@ jobs:
echo 'C8Y_USER="${{ secrets.C8Y_USER }}"' >> .env
echo 'C8Y_PASSWORD="${{ secrets.C8Y_PASSWORD }}"' >> .env

- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.9'
python-version-file: tests/.python-version
cache: 'pip'
cache-dependency-path: |
tests/requirements.txt

- uses: taiki-e/install-action@just

#
# Build
#
- uses: actions/setup-go@v5
with:
go-version: '>=1.17.0'
cache: false
- name: Install dependencies
run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest
- name: Build package (for testing)
run: just build

#
# Test
#
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
install: true

- name: Install dependencies
run: |
just venv
just build-test

- name: Run tests
run: just test
run: just test ${{ matrix.job.test_options }}

- name: Upload test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: always()
with:
name: reports-${{ matrix.job.image }}
name: reports-${{ matrix.job.name }}
path: output

- name: Send report to commit
if: ${{ always() && github.event_name == 'pull_request_target' }}
if: ${{ always() && (github.event_name == 'pull_request' || github.event_name == 'repository_dispatch') }}
uses: "joonvena/robotframework-reporter-action@v2.5"
with:
report_path: output
show_passed_tests: "false"
gh_access_token: ${{ secrets.GITHUB_TOKEN }}
pull_request_id: ${{ github.event.pull_request.number || github.event.client_payload.pull_request.number }}
sha: ${{ github.event.client_payload.pull_request.head.sha || github.sha }}

tests-pass:
name: Tests Pass
needs: [build, test]
if: always()
runs-on: ubuntu-latest
steps:
- name: Check all matrix jobs passed
run: |
if [ "${{ needs.build.result }}" != "success" ]; then
echo "Build job failed: ${{ needs.build.result }}"
exit 1
fi
if [ "${{ needs.test.result }}" != "success" ]; then
echo "One or more matrix jobs failed: ${{ needs.test.result }}"
exit 1
fi
7 changes: 7 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
tedge:
build:
dockerfile: images/Dockerfile
args:
TEST_IMAGE: ${TEST_IMAGE:-ghcr.io/thin-edge/tedge-demo-main-systemd:latest}
privileged: true
5 changes: 3 additions & 2 deletions test-images/debian-12/Dockerfile → images/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
FROM ghcr.io/thin-edge/tedge-demo-main-systemd:latest
ARG TEST_IMAGE=ghcr.io/thin-edge/tedge-demo-main-systemd:latest
FROM ${TEST_IMAGE}

# FIXME: Remove once thin-edge.io 1.4.0 is released
RUN wget -O - thin-edge.io/install.sh | sh -s -- --channel main
Expand All @@ -12,5 +13,5 @@ RUN tedge config unset c8y.proxy.client.host \
&& tedge config unset mqtt.client.host \
&& tedge config unset http.client.host

COPY dist/tedge-command-plugin_*.deb /tmp/
COPY ./dist/tedge-command-plugin_*.deb /tmp/
RUN DEBIAN_FRONTEND=noninteractive apt-get install --allow-downgrades -y --no-install-recommends /tmp/*.deb
5 changes: 0 additions & 5 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ set positional-arguments
set dotenv-load
set export

IMAGE := env_var_or_default("IMAGE", "debian-12")

build *ARGS:
./ci/build.sh {{ARGS}} -- -f nfpm.tedge-command-plugin.yaml

Expand All @@ -18,6 +16,3 @@ venv:
# Run tests
test *args='':
./.venv/bin/python3 -m robot.run --outputdir output {{args}} tests

build-test: build
docker buildx build -t {{IMAGE}} --load -f ./test-images/{{IMAGE}}/Dockerfile .
1 change: 1 addition & 0 deletions tests/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
4 changes: 2 additions & 2 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
robotframework~=7.0.0
robotframework-c8y @ git+https://github.com/thin-edge/robotframework-c8y.git@0.37.2
robotframework-devicelibrary[docker] @ git+https://github.com/thin-edge/robotframework-devicelibrary.git@1.15.1
robotframework-c8y @ git+https://github.com/thin-edge/robotframework-c8y.git@0.51.1
robotframework-devicelibrary[docker] @ git+https://github.com/thin-edge/robotframework-devicelibrary.git@1.26.1
robotframework-debuglibrary~=2.5.0
36 changes: 33 additions & 3 deletions tests/resources/common.robot
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
*** Settings ***
Library Cumulocity
Library DeviceLibrary

*** Variables ***

# Cumulocity settings
&{C8Y_CONFIG} host=%{C8Y_BASEURL= } username=%{C8Y_USER= } password=%{C8Y_PASSWORD= } tenant=%{C8Y_TENANT= }

# Docker adapter settings (to control which image is used in the system tests).
# The user just needs to set the IMAGE env variable
&{DOCKER_CONFIG} image=%{IMAGE= }
*** Keywords ***

Setup Device
${DEVICE_ID}= DeviceLibrary.Setup compose_file=${CURDIR}/../../docker-compose.yaml skip_bootstrap=${True}
Set Test Variable $DEVICE_ID
Bootstrap Device ${DEVICE_ID}

Bootstrap Device
[Arguments] ${device_id}
${domain}= Cumulocity.Get Domain
DeviceLibrary.Execute Command cmd=tedge config set c8y.url ${domain}
${credentials}= Cumulocity.Bulk Register Device With Cumulocity CA external_id=${device_id}
DeviceLibrary.Execute Command cmd=tedge cert download c8y --device-id ${device_id} --retry-every 5s --one-time-password '${credentials.one_time_password}'
DeviceLibrary.Execute Command cmd=tedge reconnect c8y
Cumulocity.External Identity Should Exist external_id=${device_id}
${operation}= Cumulocity.Get Configuration typename=tedge-configuration-plugin timeout=60
Cumulocity.Operation Should Be SUCCESSFUL ${operation}
Sleep 2s

Collect Logs
Run Keyword And Continue On Failure Get Workflow Logs
Run Keyword And Continue On Failure Get Service Logs

Get Workflow Logs
DeviceLibrary.Execute Command head -n-0 /var/log/tedge/agent/*

Get Service Logs
DeviceLibrary.Execute Command cmd=journalctl -n 10000 -u "tedge-*" -u "c8y-*" --no-pager

Teardown Device
Collect Logs
Cumulocity.Delete Managed Object And Device User external_id=${DEVICE_ID}
27 changes: 8 additions & 19 deletions tests/shell.robot
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
*** Settings ***
Resource ./resources/common.robot
Library Cumulocity
Library DeviceLibrary bootstrap_script=bootstrap.sh

Suite Setup Test Setup
Suite Teardown Collect Logs
Test Setup Setup Device
Test Teardown Teardown Device

*** Test Cases ***

Expand All @@ -27,19 +25,10 @@ Execute command that fails

Operation Should Be FAILED ${operation}

*** Keywords ***

Test Setup
${DEVICE_SN}= Setup
Set Suite Variable $DEVICE_SN
Device Should Exist ${DEVICE_SN}

Collect Logs
Get Workflow Logs
Get Service Logs

Get Workflow Logs
DeviceLibrary.Execute Command head -n-0 /var/log/tedge/agent/*
Execute command works even if the /tmp folder is full
Execute Command cmd=dd if=/dev/zero of=/tmp/example bs=$((64)) count=$((10000*1024)) || true
${operation}= Cumulocity.Create Operation
... description=execute command and ignore output
... fragments={"c8y_Command":{"text":"echo helloworld > /dev/null"}}

Get Service Logs
DeviceLibrary.Execute Command journalctl --no-pager
Operation Should Be SUCCESSFUL ${operation}
Loading