From f8ecf1fc273d45b695a2512ed711ec54a2fbe89a Mon Sep 17 00:00:00 2001 From: AtomicFS Date: Thu, 19 Mar 2026 17:27:43 +0100 Subject: [PATCH 1/3] feat: add systemd service Signed-off-by: AtomicFS --- .firmwareci/dutagent.service | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .firmwareci/dutagent.service diff --git a/.firmwareci/dutagent.service b/.firmwareci/dutagent.service new file mode 100644 index 0000000..2322f3c --- /dev/null +++ b/.firmwareci/dutagent.service @@ -0,0 +1,15 @@ +[Unit] +Description=dutagent to run on SBC attached to a devices under test +Documentation=https://github.com/BlindspotSoftware/dutctl +Wants=network-online.target +After=network-online.target +StartLimitInterval=15 +StartLimitBurst=3 + +[Service] +Restart=always +RestartSec=3s +ExecStart=/usr/bin/dutagent -a 0.0.0.0:2024 -c /etc/dutctl.yaml + +[Install] +WantedBy=multi-user.target From 62eb3509d2661520bc889f35a6871e0aed783bb5 Mon Sep 17 00:00:00 2001 From: AtomicFS Date: Tue, 2 Dec 2025 13:22:38 +0100 Subject: [PATCH 2/3] build: add goreleaser config - goreleaser is great because we can use it to very easily build linux distribution packages, like a package for debian, fedora and so on - we do not have to use goreleaser to handle the releases themselves Signed-off-by: AtomicFS --- .gitignore | 3 +- .goreleaser.yaml | 141 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 .goreleaser.yaml diff --git a/.gitignore b/.gitignore index e375cf1..a8db976 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ taskfile.yml /cmds/dutagent/dutagent /dutserver /cmds/exp/dutserver/dutserver -/keys/ \ No newline at end of file +/keys/ +/bin/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..8d9c38e --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,141 @@ +--- +# Gorelease is used only as build-tool, not handling actual releases. +version: 2 + +dist: "./bin" + +before: + hooks: + - go mod tidy + - go generate ./... + +builds: + - id: "dutctl" + main: ./cmds/dutctl + binary: "dutctl" + ldflags: + - -s -w -X main.builtBy=goreleaser + env: + - CGO_ENABLED=0 + goos: + - linux + goarch: + - amd64 + - arm + - arm64 + goarm: + - "7" + goarm64: + - v8.0 + goamd64: + - v1 + mod_timestamp: "{{ .CommitTimestamp }}" + + - id: "dutagent" + main: ./cmds/dutagent + binary: "dutagent" + ldflags: + - -s -w -X main.builtBy=goreleaser + env: + - CGO_ENABLED=0 + goos: + - linux + goarch: + - amd64 + - arm + - arm64 + goarm: + - "7" + goarm64: + - v8.0 + goamd64: + - v1 + mod_timestamp: "{{ .CommitTimestamp }}" + + - id: "dutserver" + main: ./cmds/exp/dutserver + binary: "dutserver" + ldflags: + - -s -w -X main.builtBy=goreleaser + env: + - CGO_ENABLED=0 + goos: + - linux + goarch: + - amd64 + - arm + - arm64 + goarm: + - "7" + goarm64: + - v8.0 + goamd64: + - v1 + mod_timestamp: "{{ .CommitTimestamp }}" + +checksum: + name_template: "checksums.txt" + algorithm: blake2b + +archives: + - formats: ["tar.gz"] + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + formats: ["zip"] + +nfpms: + - id: packages + package_name: "dutctl" + file_name_template: >- + {{- trimsuffix .ConventionalFileName .ConventionalExtension -}} + {{- if and (eq .Arm "6") (eq .ConventionalExtension ".deb") }}6{{ end -}} + {{- if not (eq .Amd64 "v1")}}{{ .Amd64 }}{{ end -}} + {{- .ConventionalExtension -}} + ids: + - dutctl + - dutagent + provides: + - dutctl + - dutagent + vendor: "Blindspot Software" + homepage: "https://github.com/blindspotSoftware/dutctl" + maintainer: "Jens Topp " + description: | + DUT Control is an abstraction layer for remote hardware access. + license: "BSD 2-Clause" + formats: + - deb + - rpm + - archlinux + replaces: + - golang-github-fti + - golang-fti + - fti + bindir: /usr/bin + section: default + priority: extra + contents: + # systemd services + - src: ./.firmwareci/dutagent.service + dst: /usr/lib/systemd/system/dutagent.service + + rpm: + packager: "Jens Topp " + archlinux: + packager: "Jens Topp " + +source: + enabled: true + +release: + prerelease: auto + mode: append From a0276234975f6402d701861b663651cab8fd775e Mon Sep 17 00:00:00 2001 From: AtomicFS Date: Thu, 19 Mar 2026 17:29:30 +0100 Subject: [PATCH 3/3] ci: add fwci test - add fwci configuration files - add CI/CD job for triggering the FWCI testing suite Signed-off-by: AtomicFS --- .firmwareci/.jinja2_templates/defaults.j2 | 14 ++ .firmwareci/.jinja2_templates/defaults.yaml | 15 ++ .firmwareci/.jinja2_templates/post.yaml.j2 | 33 +++ .firmwareci/.jinja2_templates/pre.yaml.j2 | 76 ++++++ .firmwareci/README.md | 108 ++++++++ .firmwareci/Taskfile.yml | 50 ++++ .../duts/dut-rpi-dutctl-tester/dut.yaml | 6 + .../duts/dut-rpi-dutctl-tester/dut.yaml.j2 | 7 + .firmwareci/storage/.gitkeep | 0 .../tests/dummy-modules.yaml | 230 ++++++++++++++++++ .../tests/dummy-modules.yaml.j2 | 109 +++++++++ .../workflow-rpi-dutctl-tester/workflow.yaml | 4 + .../workflow.yaml.j2 | 5 + .gitattributes | 2 + .github/workflows/fwci-test.yml | 83 +++++++ .gitignore | 2 +- 16 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 .firmwareci/.jinja2_templates/defaults.j2 create mode 100644 .firmwareci/.jinja2_templates/defaults.yaml create mode 100644 .firmwareci/.jinja2_templates/post.yaml.j2 create mode 100644 .firmwareci/.jinja2_templates/pre.yaml.j2 create mode 100644 .firmwareci/README.md create mode 100644 .firmwareci/Taskfile.yml create mode 100644 .firmwareci/duts/dut-rpi-dutctl-tester/dut.yaml create mode 100644 .firmwareci/duts/dut-rpi-dutctl-tester/dut.yaml.j2 create mode 100644 .firmwareci/storage/.gitkeep create mode 100644 .firmwareci/workflows/workflow-rpi-dutctl-tester/tests/dummy-modules.yaml create mode 100644 .firmwareci/workflows/workflow-rpi-dutctl-tester/tests/dummy-modules.yaml.j2 create mode 100644 .firmwareci/workflows/workflow-rpi-dutctl-tester/workflow.yaml create mode 100644 .firmwareci/workflows/workflow-rpi-dutctl-tester/workflow.yaml.j2 create mode 100644 .gitattributes create mode 100644 .github/workflows/fwci-test.yml diff --git a/.firmwareci/.jinja2_templates/defaults.j2 b/.firmwareci/.jinja2_templates/defaults.j2 new file mode 100644 index 0000000..eb50cbb --- /dev/null +++ b/.firmwareci/.jinja2_templates/defaults.j2 @@ -0,0 +1,14 @@ +## Can be changed depending on the FWCI tester device used +{%- set RPI_HOSTNAME = "fwci-dutctl-tester" %} +{%- set RPI_FQDN = RPI_HOSTNAME ~ ".sec.9e.network" %} +## Do not change values below, unless you know what you are doing +{%- set PKG_NAME = "dutctl" %} +{%- set DUTCTL_CONFIG_FILE = "/etc/dutctl.yaml" %} +{%- set SYSTEMD_FILE = "dutagent.service" %} +{%- set DUTAGENT_PORT = "2024" %} +{%- set DUTCTL_ARGS = '"-s", "' ~ RPI_HOSTNAME ~ ':' ~ DUTAGENT_PORT ~ '"' %} +{%- set DUTCTL_ARGS_STR = '-s ' ~ RPI_HOSTNAME ~ ':' ~ DUTAGENT_PORT %} +## Values below should not change +{%- set GPIO_PIN_TEST_OLIMEX = "21" %} +{%- set FAKE_SERIAL_INPUT = "/home/oscar/ttyS1" %} +{%- set FAKE_SERIAL_OUTPUT = "/home/oscar/ttyS2" %} diff --git a/.firmwareci/.jinja2_templates/defaults.yaml b/.firmwareci/.jinja2_templates/defaults.yaml new file mode 100644 index 0000000..f28e72a --- /dev/null +++ b/.firmwareci/.jinja2_templates/defaults.yaml @@ -0,0 +1,15 @@ +transport_oscar: &transport_oscar + proto: ssh + options: + host: "[[attributes.Host]]" + user: oscar + identity_file: "/root/.ssh/fwci" + # TODO: The ssh keys (identity_file) are hard-coded in the FWCI docker at the moment + +transport_root: &transport_root + proto: ssh + options: + host: "[[attributes.Host]]" + user: root + identity_file: "/root/.ssh/fwci" + # TODO: The ssh keys (identity_file) are hard-coded in the FWCI docker at the moment diff --git a/.firmwareci/.jinja2_templates/post.yaml.j2 b/.firmwareci/.jinja2_templates/post.yaml.j2 new file mode 100644 index 0000000..c2f293b --- /dev/null +++ b/.firmwareci/.jinja2_templates/post.yaml.j2 @@ -0,0 +1,33 @@ +post-stage: + # =========================================== + # Delete the files copied over for the test + # =========================================== + - cmd: shell + name: Stop remotelab-server systemd service + transport: *transport_root + parameters: + command: "systemctl stop {{ defaults.SYSTEMD_FILE }} || true" + + - cmd: shell + name: Uninstall the package + transport: *transport_root + parameters: + command: "apt remove --assume-yes {{ defaults.PKG_NAME }} || true" + + - cmd: shell + name: Delete testing configuration file + transport: *transport_root + parameters: + command: "rm {{ defaults.DUTCTL_CONFIG_FILE }} || true" + + - cmd: shell + name: Delete package + transport: *transport_root + parameters: + command: "rm {{ defaults.PKG_NAME }}.deb || true" + + - cmd: shell + name: Shutdown fake serial + transport: *transport_oscar + parameters: + command: "pkill socat || true" diff --git a/.firmwareci/.jinja2_templates/pre.yaml.j2 b/.firmwareci/.jinja2_templates/pre.yaml.j2 new file mode 100644 index 0000000..ededcc6 --- /dev/null +++ b/.firmwareci/.jinja2_templates/pre.yaml.j2 @@ -0,0 +1,76 @@ +pre-stage: + # Fix permissions + - cmd: cmd + name: chmod dutctl + transport: + proto: local + parameters: + executable: chmod + args: ["755", "[[input.DUTCTL]]"] + + # ======================== + # Copy files over to RPI + # ======================== + - cmd: copy + name: Copy over {{ defaults.PKG_NAME }} debian package + transport: *transport_root + parameters: + source: "[[input.REMOTE_PKG]]" + destination: "{{ defaults.PKG_NAME }}.deb" + + - cmd: cmd + name: Install {{ defaults.PKG_NAME }} package + transport: *transport_root + parameters: + executable: dpkg + args: ["-i", "{{ defaults.PKG_NAME }}.deb"] + + - cmd: copy + name: Copy over test config file + transport: *transport_root + parameters: + source: "[[input.CONFIG]]" + destination: {{ defaults.DUTCTL_CONFIG_FILE }} + + - cmd: cmd + name: chmod config file + transport: *transport_root + parameters: + executable: chmod + args: ["644", {{ defaults.DUTCTL_CONFIG_FILE }}] + + # ============== + # Start server + # ============== + - cmd: cmd + name: Reload systemctl daemon + transport: *transport_root + parameters: + executable: systemctl + args: ["daemon-reload"] + + - cmd: cmd + name: Start systemd service + transport: *transport_root + parameters: + executable: systemctl + args: ["start", {{ defaults.SYSTEMD_FILE }}] + + - cmd: shell + name: Status systemd service + transport: *transport_root + parameters: + command: "systemctl status --no-pager {{ defaults.SYSTEMD_FILE }}" + expect: + - regex: "Active: active \\(running\\)" + + # ========================== + # Test dutctl help command + # ========================== + - cmd: cmd + name: dutctl help + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: ["--help"] diff --git a/.firmwareci/README.md b/.firmwareci/README.md new file mode 100644 index 0000000..968870f --- /dev/null +++ b/.firmwareci/README.md @@ -0,0 +1,108 @@ +# FirmwareCI configuration for dutctl testing + +This directory contains FirmwareCI configurations and tests. + +For detailed information on FirmwareCI, please refer to the [official documentation](https://docs.firmware-ci.com/). + + +## Requirements + +- Parametric and re-usable testing +- Easy to expand +- Reliable +- Can be used in CI/CD to verify each pull request + + +## Design decisions + +### Jinja templating +We want to test multiple features (flashing, power control, serial, etc). And to keep everything nicely organised we need to have each feature-specific test as it's own test. + +However, the setup and tear-down of the tests is rather complex: +- copy over the compiled binaries +- copy over configuration files +- spin up the server (agent) +- execute the test +- shut-down the server +- delete the copied binaries +- delete configuration files + +In case of the serial feature, it also means spinning-up "fake" / "dummy" serial that can be used for the testing. + +It would be stupid to copy-paste all this setup into each test file, especially because keeping all of them in sync would be a nightmare. + +For this reason I have decided to use [jinja2 templates](https://jinja.palletsprojects.com/en/stable/api/) to automate this menial and error-prone work. To make it even simpler, I have also included [Taskfile](https://taskfile.dev/docs/guide) which has already all the jobs written there, so to generate all of the templates is as easy as simply calling `task jinja2:templates`. + +Thanks to jinja templating, and how the tests are structured, tests are parametric and easy to expand. + + +### Debian packaging magic +As you might notice, instead of simply copying over the compiled binaries (`dutctl` / `dutagent` / ...) we use Debian packages. + +The biggest motivator here to do this was to accommodate for setup and tear-down, and the differences between tested versions (future-proofing). This way, we leverage the power of package manager to make sure that the cleanup (uninstalling) of old file is complete and that no files are left behind! This makes sure that the environment is always pristine. + +Another huge advantage is, that with this approach, that run-time dependencies are also handled by package manager. Because of this, the test hardware (Raspberry Pi) has minimal (if any) setup required for use in the FWCI tests. Just install package, test and uninstall! Easy! + + +## Project structure + +The most important files / directories are: +- `.firmwareci/Taskfile.yml` +- `.firmwareci/.jinja2_templates/` +- `.firmwareci/workflows/` + +### Taskfile +The `.firmwareci/Taskfile.yml` is there to help and automate some mundane and repetitive jobs. + +Contains task `jinja2:templates` which finds all jinja tempaltes in `.firmwareci/workflows/` and `.firmwareci/duts/` and runs a tempalting engine on them. + +The list of the templates is created dynamically with the shell command: +```bash +find './workflows/' './duts/' -type f -name '*.yaml.j2' +``` + +This list is then iterated over in a for-loop, such as that it takes `./duts/dut-rpi-fti-tester/dut.yaml.j2` as input, and produces `./duts/dut-rpi-fti-tester/dut.yaml`. + + +### jinja2_templates +The `.firmwareci/.jinja2_templates` contains the "common" building-blocks that are shared across templates. The `defaults.j2` and `defaults.yaml` are for storing some basics (they are intentionally separate): +- `defaults.j2` + - this is pure jinja2 syntax, and is imported into template with + ```j2 + {%- import ".jinja2_templates/defaults.j2" as defaults %} + ``` + - import means that the variables will become accessible, and can then be accessed in the template with: + ```j2 + {{ defaults.DUTCTL_ARGS }} + ``` +- `defaults.yaml` + - this is pure YAML file to be `include`d into a template with + ```j2 + {% include ".jinja2_templates/defaults.yaml" with context %} + ``` + - essentially this is to just simply render the content of the file, ignoring any variable assignments or macros within it + - technically speaking it could be renamed to `defaults.yaml.j2`, but it does not contain any jinja2 code and renaming it would have no effect +- `pre.yaml.j2` and `post.yaml.j2` + - for pre-stage and post-stage respectively + - they are copy-pasted into each test (they are `include`d in tests, meaning that their content will be rendered) + + +### workflows +The `workflows` directory is where the tests live. + +### Systemd service +The `dutagent.service` file is a systemd service file to start a dutagent on dutworker. + +At the moment it is only used for firmware-ci testing. + + +## Adding completely new test +To add a completely new tests from scratch, create a new `.yaml.j2` file in `.firmwareci/workflows/workflow-rpi-dutctl-tester/`. I would recommend to copy-paste some existing test (for example the `dummy-modules.yaml.j2` is rather simple) and use it as basis. + +Then update the test according to your needs. + +Run `task fwci:jinja2:templates` and it will automatically generate a complete tests. That is it. It is as simple as that. + +Next, I recommend to run `fwci fwci:validate`. + +As the last step, add all new files (even those jinja2 generated) into git commit. This is because FirmwareCI cannot pre-process the tests on it's own, which is why we have to include even the "generated code". diff --git a/.firmwareci/Taskfile.yml b/.firmwareci/Taskfile.yml new file mode 100644 index 0000000..64a396c --- /dev/null +++ b/.firmwareci/Taskfile.yml @@ -0,0 +1,50 @@ +--- +version: "3" + +tasks: + jinja2:templates: + desc: Run jinja2 on template files + cmds: + - task: jinja2:find-and-process-all-tempaltes + + fwci:validate: + desc: Validate FWCI files + cmds: + - fwci validate + + #================ + # Internal tasks + #================ + + jinja2:find-and-process-all-tempaltes: + desc: Run jinja2 on jinja2 templates + internal: true + label: 'jinja2-tests-{{.REPO}}' + vars: + WORKFLOWS: + sh: find './workflows/' './duts/' -type f -name '*.yaml.j2' + sources: + - .jinja2_templates/* + - duts/**/*.yaml.j2 + - workflows/workflow-*/defaults.j2 + - workflows/workflow-*/**/*.yaml.j2 + cmds: + - for: + var: WORKFLOWS + task: jinja2:run-on-file + vars: + INPUTFILE: + sh: echo "{{.ITEM}}" + OUTPUTFILE: + sh: echo "{{.ITEM}}" | sed -E 's/\.j2$//g' + + jinja2:run-on-file: + desc: Run jinja2 on a file + internal: true + cmds: + - pwd + - echo 'INPUTFILE = {{.INPUTFILE}}' + - echo 'OUTPUTFILE = {{.OUTPUTFILE}}' + - j2 --format env -o '{{.OUTPUTFILE}}' '{{.INPUTFILE}}' + requires: + vars: ["INPUTFILE", "OUTPUTFILE"] diff --git a/.firmwareci/duts/dut-rpi-dutctl-tester/dut.yaml b/.firmwareci/duts/dut-rpi-dutctl-tester/dut.yaml new file mode 100644 index 0000000..e2c9f11 --- /dev/null +++ b/.firmwareci/duts/dut-rpi-dutctl-tester/dut.yaml @@ -0,0 +1,6 @@ +--- +# Device-under-Test Configuration for RaspberryPi acting as dutctl tester +name: fwci-dutctl-tester +label: fwci-dutctl-tester +attributes: + Host: "fwci-dutctl-tester.sec.9e.network" diff --git a/.firmwareci/duts/dut-rpi-dutctl-tester/dut.yaml.j2 b/.firmwareci/duts/dut-rpi-dutctl-tester/dut.yaml.j2 new file mode 100644 index 0000000..322fec5 --- /dev/null +++ b/.firmwareci/duts/dut-rpi-dutctl-tester/dut.yaml.j2 @@ -0,0 +1,7 @@ +--- +# Device-under-Test Configuration for RaspberryPi acting as dutctl tester +{%- import ".jinja2_templates/defaults.j2" as defaults %} +name: {{ defaults.RPI_HOSTNAME }} +label: {{ defaults.RPI_HOSTNAME }} +attributes: + Host: "{{ defaults.RPI_FQDN }}" diff --git a/.firmwareci/storage/.gitkeep b/.firmwareci/storage/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.firmwareci/workflows/workflow-rpi-dutctl-tester/tests/dummy-modules.yaml b/.firmwareci/workflows/workflow-rpi-dutctl-tester/tests/dummy-modules.yaml new file mode 100644 index 0000000..c7138d4 --- /dev/null +++ b/.firmwareci/workflows/workflow-rpi-dutctl-tester/tests/dummy-modules.yaml @@ -0,0 +1,230 @@ +--- +name: Test dummy modules +description: Test that all dummy modules work + +defaults: + transport_oscar: &transport_oscar + proto: ssh + options: + host: "[[attributes.Host]]" + user: oscar + identity_file: "/root/.ssh/fwci" + # TODO: The ssh keys (identity_file) are hard-coded in the FWCI docker at the moment + + transport_root: &transport_root + proto: ssh + options: + host: "[[attributes.Host]]" + user: root + identity_file: "/root/.ssh/fwci" + # TODO: The ssh keys (identity_file) are hard-coded in the FWCI docker at the moment + + +pre-stage: + # Fix permissions + - cmd: cmd + name: chmod dutctl + transport: + proto: local + parameters: + executable: chmod + args: ["755", "[[input.DUTCTL]]"] + + # ======================== + # Copy files over to RPI + # ======================== + - cmd: copy + name: Copy over dutctl debian package + transport: *transport_root + parameters: + source: "[[input.REMOTE_PKG]]" + destination: "dutctl.deb" + + - cmd: cmd + name: Install dutctl package + transport: *transport_root + parameters: + executable: dpkg + args: ["-i", "dutctl.deb"] + + - cmd: copy + name: Copy over test config file + transport: *transport_root + parameters: + source: "[[input.CONFIG]]" + destination: /etc/dutctl.yaml + + - cmd: cmd + name: chmod config file + transport: *transport_root + parameters: + executable: chmod + args: ["644", /etc/dutctl.yaml] + + # ============== + # Start server + # ============== + - cmd: cmd + name: Reload systemctl daemon + transport: *transport_root + parameters: + executable: systemctl + args: ["daemon-reload"] + + - cmd: cmd + name: Start systemd service + transport: *transport_root + parameters: + executable: systemctl + args: ["start", dutagent.service] + + - cmd: shell + name: Status systemd service + transport: *transport_root + parameters: + command: "systemctl status --no-pager dutagent.service" + expect: + - regex: "Active: active \\(running\\)" + + # ========================== + # Test dutctl help command + # ========================== + - cmd: cmd + name: dutctl help + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: ["--help"] + + +# "contrib/dutagent-cfg-example.yaml" + +stages: + - name: Test Device 1 + steps: + - cmd: cmd + name: Test status + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: ["-s", "fwci-dutctl-tester:2024", "device1", "status"] + expect: + - regex: "Hello from dummy status module\nCalled with 0 arguments" + options: + timeout: 1s + + - cmd: cmd + name: Test status with argument + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: ["-s", "fwci-dutctl-tester:2024", "device1", "status", "hello"] + expect: + - regex: "Hello from dummy status module\nCalled with 1 arguments\nArg 0: hello" + options: + timeout: 1s + + - name: Test Device 2 + steps: + - cmd: cmd + name: Test status + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: ["-s", "fwci-dutctl-tester:2024", "device2", "status"] + expect: + - regex: "Hello from dummy status module\nCalled with 2 arguments" + options: + timeout: 1s + + - cmd: shell + name: Test repeat + transport: + proto: local + parameters: + command: "echo -e 'hello\nhello world\n' | [[input.DUTCTL]] -s fwci-dutctl-tester:2024 device2 repeat" + expect: + - regex: "Hello from dummy repeat module!\nEnter one word per line." + options: + timeout: 1s + + - name: Test Device 3 + steps: + - cmd: cmd + name: Test status + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: ["-s", "fwci-dutctl-tester:2024", "device3", "status"] + expect: + - regex: "Hello from dummy status module\nCalled with 0 arguments" + options: + timeout: 1s + + - cmd: shell + name: Create test file + transport: + proto: local + parameters: + command: "echo 'hello' > test.txt" + + - cmd: cmd + name: Test file transfer + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: ["-s", "fwci-dutctl-tester:2024", "device3", "file-transfer", "test.txt", "test2.txt"] + expect: + - regex: "Hello from dummy file transfer module" + options: + timeout: 1s + + - cmd: cmd + name: Read test file + transport: + proto: local + parameters: + executable: "cat" + args: ["test2.txt"] + expect: + - regex: "processed by dummy.FT module" + +post-stage: + # =========================================== + # Delete the files copied over for the test + # =========================================== + - cmd: shell + name: Stop remotelab-server systemd service + transport: *transport_root + parameters: + command: "systemctl stop dutagent.service || true" + + - cmd: shell + name: Uninstall the package + transport: *transport_root + parameters: + command: "apt remove --assume-yes dutctl || true" + + - cmd: shell + name: Delete testing configuration file + transport: *transport_root + parameters: + command: "rm /etc/dutctl.yaml || true" + + - cmd: shell + name: Delete package + transport: *transport_root + parameters: + command: "rm dutctl.deb || true" + + - cmd: shell + name: Shutdown fake serial + transport: *transport_oscar + parameters: + command: "pkill socat || true" diff --git a/.firmwareci/workflows/workflow-rpi-dutctl-tester/tests/dummy-modules.yaml.j2 b/.firmwareci/workflows/workflow-rpi-dutctl-tester/tests/dummy-modules.yaml.j2 new file mode 100644 index 0000000..f8ac81f --- /dev/null +++ b/.firmwareci/workflows/workflow-rpi-dutctl-tester/tests/dummy-modules.yaml.j2 @@ -0,0 +1,109 @@ +--- +name: Test dummy modules +description: Test that all dummy modules work + +defaults: + {%- import ".jinja2_templates/defaults.j2" as defaults %} + {%- macro someop() %}{% include ".jinja2_templates/defaults.yaml" with context %}{% endmacro %} + {{ someop()|indent(2) }} + +{% include ".jinja2_templates/pre.yaml.j2" %} + +# "contrib/dutagent-cfg-example.yaml" + +stages: + - name: Test Device 1 + steps: + - cmd: cmd + name: Test status + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: [{{ defaults.DUTCTL_ARGS }}, "device1", "status"] + expect: + - regex: "Hello from dummy status module\nCalled with 0 arguments" + options: + timeout: 1s + + - cmd: cmd + name: Test status with argument + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: [{{ defaults.DUTCTL_ARGS }}, "device1", "status", "hello"] + expect: + - regex: "Hello from dummy status module\nCalled with 1 arguments\nArg 0: hello" + options: + timeout: 1s + + - name: Test Device 2 + steps: + - cmd: cmd + name: Test status + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: [{{ defaults.DUTCTL_ARGS }}, "device2", "status"] + expect: + - regex: "Hello from dummy status module\nCalled with 2 arguments" + options: + timeout: 1s + + - cmd: shell + name: Test repeat + transport: + proto: local + parameters: + command: "echo -e 'hello\nhello world\n' | [[input.DUTCTL]] {{ defaults.DUTCTL_ARGS_STR }} device2 repeat" + expect: + - regex: "Hello from dummy repeat module!\nEnter one word per line." + options: + timeout: 1s + + - name: Test Device 3 + steps: + - cmd: cmd + name: Test status + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: [{{ defaults.DUTCTL_ARGS }}, "device3", "status"] + expect: + - regex: "Hello from dummy status module\nCalled with 0 arguments" + options: + timeout: 1s + + - cmd: shell + name: Create test file + transport: + proto: local + parameters: + command: "echo 'hello' > test.txt" + + - cmd: cmd + name: Test file transfer + transport: + proto: local + parameters: + executable: "[[input.DUTCTL]]" + args: [{{ defaults.DUTCTL_ARGS }}, "device3", "file-transfer", "test.txt", "test2.txt"] + expect: + - regex: "Hello from dummy file transfer module" + options: + timeout: 1s + + - cmd: cmd + name: Read test file + transport: + proto: local + parameters: + executable: "cat" + args: ["test2.txt"] + expect: + - regex: "processed by dummy.FT module" + +{% include ".jinja2_templates/post.yaml.j2" -%} diff --git a/.firmwareci/workflows/workflow-rpi-dutctl-tester/workflow.yaml b/.firmwareci/workflows/workflow-rpi-dutctl-tester/workflow.yaml new file mode 100644 index 0000000..e8a1588 --- /dev/null +++ b/.firmwareci/workflows/workflow-rpi-dutctl-tester/workflow.yaml @@ -0,0 +1,4 @@ +--- +name: test-dutctl +description: "This is workflow configuration for new dutctl testing" +runs-on: fwci-dutctl-tester diff --git a/.firmwareci/workflows/workflow-rpi-dutctl-tester/workflow.yaml.j2 b/.firmwareci/workflows/workflow-rpi-dutctl-tester/workflow.yaml.j2 new file mode 100644 index 0000000..1deed8b --- /dev/null +++ b/.firmwareci/workflows/workflow-rpi-dutctl-tester/workflow.yaml.j2 @@ -0,0 +1,5 @@ +--- +{%- import ".jinja2_templates/defaults.j2" as defaults %} +name: test-dutctl +description: "This is workflow configuration for new dutctl testing" +runs-on: {{ defaults.RPI_HOSTNAME }} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d5199fa --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +.firmwareci/duts/dut-rpi-dutctl-tester/*.yaml linguist-generated=true +.firmwareci/workflows/workflow-rpi-dutctl-tester/**/*.yaml linguist-generated=true diff --git a/.github/workflows/fwci-test.yml b/.github/workflows/fwci-test.yml new file mode 100644 index 0000000..3848672 --- /dev/null +++ b/.github/workflows/fwci-test.yml @@ -0,0 +1,83 @@ +--- +name: fwci-test +# Gorelease is used only as build-tool, not handling actual releases. + +on: + pull_request: {} + merge_group: {} + push: + branches: ["main"] + tags: ["v*"] + +env: + APPLY_FIXES: none + APPLY_FIXES_EVENT: pull_request + APPLY_FIXES_MODE: commit + DISABLE_TELEMETRY: 1 +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + # Job to build binaries and linux packages with goreleaser, without making release + build-with-goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: stable + - name: Build packages with goreleaser (without actually making release) + run: | + go install github.com/goreleaser/goreleaser/v2@latest + goreleaser release --draft --snapshot --clean + - name: Upload artifacts + uses: actions/upload-artifact@v7 + with: + name: binaries + path: bin/ + retention-days: 14 days + + + # Run a firmware-ci test suite + client-agent-integration: + runs-on: ubuntu-latest + needs: + - build-with-goreleaser + strategy: + fail-fast: false + matrix: + configfile: + - ./contrib/dutagent-cfg-example.yaml + #- ./test/test-dutagent-cfg.yaml + steps: + - name: Checkout + uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 + with: + name: binaries + path: bin/ + + - name: Get names of binaries + id: filenames + run: | + echo "dutctl_path=$(ls ./bin/dutctl_linux_amd64_*/dutctl)" >> "${GITHUB_OUTPUT}" + echo "dutctl_pkg_path=$(ls ./bin/dutctl_*_arm64.deb)" >> "${GITHUB_OUTPUT}" + - name: Debug + run: | + echo "dutctl_path: ${{ steps.filenames.outputs.dutctl_path }}" + echo "dutctl_pkg_path: ${{ steps.filenames.outputs.dutctl_pkg_path }}" + + - name: Upload to FirmwareCI + uses: docker://firmwareci/action:v5.1 + with: + TOKEN: "${{ secrets.FWCI_TOKEN }}" + WORKFLOW_ID: "${{ secrets.FWCI_WORKFLOW_ID }}" + COMMIT_HASH: ${{ github.event.pull_request.head.sha || github.sha }} + BINARIES: DUTCTL=${{ steps.filenames.outputs.dutctl_path }};REMOTE_PKG=${{ steps.filenames.outputs.dutctl_pkg_path }};CONFIG=${{ matrix.configfile }} + GITHUB_INSTALLATION_ID: 45795153 diff --git a/.gitignore b/.gitignore index a8db976..4a9a70c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .vscode/ -taskfile.yml +.task/ /dutctl /cmds/dutctl/dutctl /dutagent