diff --git a/.github/workflows/hardware-bu585.yml b/.github/workflows/hardware-bu585.yml new file mode 100644 index 00000000..9bff6bd0 --- /dev/null +++ b/.github/workflows/hardware-bu585.yml @@ -0,0 +1,299 @@ +# @copyright Copyright (c) contributors to Project Ocre, +# which has been established as Project Ocre a Series of LF Projects, LLC +# +# SPDX-License-Identifier: Apache-2.0 + +name: Hardware Checks (b_u585) + +concurrency: + group: pr-workflows + cancel-in-progress: false + +on: + workflow_call: + inputs: + devcontainer-tag: + description: The container tag to be used + default: latest + required: false + type: string + +jobs: + setup-local-runner: + name: Setup local runner + runs-on: zephyr-xlarge-runner + steps: + - name: Remove old workflow files + run: rm -rf /var/ocre-ci-files/* + + - name: Create wasm directory + run: mkdir /var/ocre-ci-files/wasm + + build-wasm-files: + name: Build .wasm Files + needs: setup-local-runner + runs-on: zephyr-xlarge-runner + container: + image: ghcr.io/${{ github.repository }}/devcontainer-zephyr:${{ inputs.devcontainer-tag }} + volumes: + - /var/ocre-ci-files/:/var/ocre-ci-files/ + options: --user root + strategy: + matrix: + sample: + - name: generic-hello-world + path: generic/hello-world + filename: hello-world.wasm + - name: generic-subscriber + path: generic/messaging/subscriber + filename: subscriber.wasm + - name: generic-publisher + path: generic/messaging/publisher + filename: publisher.wasm + - name: generic-blinky + path: generic/blinky + filename: blinky.wasm + + # Examples for future images to add + # - name: generic-filesystem-full + # path: generic/filesystem-full + # filename: filesystem-full.wasm + # - name: b_u585i-modbus-server + # path: board_specific/b_u585i_iot02a/modbus-server + # filename: modbus-server.wasm + + steps: + - name: Cleanup workspace + run: | + rm -rf ocre-runtime || true + rm -rf ../.west || true + continue-on-error: true + + - name: Checkout + uses: actions/checkout@v4 + with: + path: ocre-runtime + submodules: recursive + + - name: Build WASM sample + run: | + SAMPLE_DIR=$GITHUB_WORKSPACE/ocre-runtime/ocre-sdk/${{ matrix.sample.path }} + if [ ! -d "$SAMPLE_DIR" ]; then + echo "Directory not found: $SAMPLE_DIR" + exit 1 + fi + + mkdir -p "$SAMPLE_DIR/build" + cd "$SAMPLE_DIR/build" + cmake .. -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk.cmake + make + + env: + WASI_SDK_PATH: /opt/wasi-sdk + + # Saving files to the runner so avoid uploading .wasm files as artifacts individually, uploaded in separate step + - name: Save .wasm artifact locally + if: always() + run: | + mkdir /var/ocre-ci-files/wasm/${{ matrix.sample.name }}/ + cp "ocre-runtime/ocre-sdk/${{ matrix.sample.path }}/build/${{ matrix.sample.filename }}" "/var/ocre-ci-files/wasm/${{ matrix.sample.name }}/${{ matrix.sample.filename }}" + + artifact-wasm-files: + name: Artifact built .wasm Files + needs: build-wasm-files + runs-on: zephyr-xlarge-runner + steps: + - name: Artifact local wasm files + if: always() + uses: actions/upload-artifact@v4 + with: + name: wasm-build-artifacts + path: "/var/ocre-ci-files/wasm" + + mini-flash: + name: Mini sample build and flash + needs: artifact-wasm-files + runs-on: zephyr-xlarge-runner + container: + image: ghcr.io/${{ github.repository }}/devcontainer-zephyr:${{ inputs.devcontainer-tag }} + volumes: + - /var/ocre-ci-files/:/var/ocre-ci-files/ + - /usr/local/STMicroelectronics/:/usr/local/STMicroelectronics/ + - /github/home/STMicroelectronics/:/github/home/STMicroelectronics/ + - /dev/bus/usb:/dev/bus/usb + options: --user root --privileged --device=/dev/ttyACM0 + + steps: + - name: Clean workspace + run: | + rm -rf ../.west || true + find . -name . -o -prune -exec rm -rf -- {} + + + - name: Checkout + uses: actions/checkout@v4 + with: + path: ocre-runtime + submodules: recursive + + - name: Setup west environment + run: | + . /opt/zephyr-venv/bin/activate + west init -l ocre-runtime + west update + + - name: Download wasm artifact + uses: actions/download-artifact@v4 + with: + name: wasm-build-artifacts + path: wasm-build-artifacts + + - name: Build and Flash mini sample + run: | + . /opt/zephyr-venv/bin/activate + west build -p always -b b_u585i_iot02a ocre-runtime/src/samples/mini/zephyr + west flash --extload=/usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/ExternalLoader/MX25LM51245G_STM32U585I-IOT02A.stldr + + mini-test: + name: Mini Validation Test + needs: mini-flash + runs-on: zephyr-xlarge-runner + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run mini sample Test Case + run: | + cd tests_hw && bash beginTests.sh "mini" + + - name: Print mini sample Test Case Logs + if: always() + run: | + cat /tmp/mini.log + + demo-flash: + name: Demo sample build and flash + needs: [artifact-wasm-files, mini-test] + runs-on: zephyr-xlarge-runner + container: + image: ghcr.io/${{ github.repository }}/devcontainer-zephyr:${{ inputs.devcontainer-tag }} + volumes: + - /var/ocre-ci-files/:/var/ocre-ci-files/ + - /usr/local/STMicroelectronics/:/usr/local/STMicroelectronics/ + - /github/home/STMicroelectronics/:/github/home/STMicroelectronics/ + - /dev/bus/usb:/dev/bus/usb + options: --user root --privileged --device=/dev/ttyACM0 + + steps: + - name: Clean workspace + run: | + rm -rf ../.west || true + find . -name . -o -prune -exec rm -rf -- {} + + + - name: Checkout + uses: actions/checkout@v4 + with: + path: ocre-runtime + submodules: recursive + + - name: Setup west environment + run: | + . /opt/zephyr-venv/bin/activate + west init -l ocre-runtime + west update + + - name: Download wasm artifact + uses: actions/download-artifact@v4 + with: + name: wasm-build-artifacts + path: wasm-build-artifacts + + - name: Build and Flash Demo Sample + run: | + . /opt/zephyr-venv/bin/activate + west build -p always -b b_u585i_iot02a ocre-runtime/src/samples/demo/zephyr + west flash --extload=/usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/ExternalLoader/MX25LM51245G_STM32U585I-IOT02A.stldr --hex-file build/zephyr/merged.hex + + demo-test: + name: Demo Validation Test + needs: demo-flash + runs-on: zephyr-xlarge-runner + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run Demo Sample Test Case + run: | + cd tests_hw && bash beginTests.sh "demo" + + - name: Print Demo Sample Test Case Logs + if: always() + run: | + cat /tmp/demo.log + + build-flash-supervisor-set-1: + name: Build and Flash Supervisor with Image Set 1 + needs: [artifact-wasm-files, demo-test] + runs-on: zephyr-xlarge-runner + container: + image: ghcr.io/${{ github.repository }}/devcontainer-zephyr:${{ inputs.devcontainer-tag }} + volumes: + - /var/ocre-ci-files/:/var/ocre-ci-files/ + - /usr/local/STMicroelectronics/:/usr/local/STMicroelectronics/ + - /github/home/STMicroelectronics/:/github/home/STMicroelectronics/ + - /dev/bus/usb:/dev/bus/usb + options: --user root --privileged --device=/dev/ttyACM0 + + steps: + - name: Clean workspace + run: | + rm -rf ../.west || true + find . -name . -o -prune -exec rm -rf -- {} + + + - name: Checkout + uses: actions/checkout@v4 + with: + path: ocre-runtime + submodules: recursive + + - name: Setup west environment + run: | + . /opt/zephyr-venv/bin/activate + west init -l ocre-runtime + west update + + - name: Download wasm artifact + uses: actions/download-artifact@v4 + with: + name: wasm-build-artifacts + path: wasm-build-artifacts + + - name: Build and Flash Supervisor + run: | + . /opt/zephyr-venv/bin/activate + west build -p always -b b_u585i_iot02a ocre-runtime/src/samples/supervisor/zephyr -- "-DOCRE_SDK_PRELOADED_IMAGES=hello-world.wasm" + west flash --extload=/usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/ExternalLoader/MX25LM51245G_STM32U585I-IOT02A.stldr --hex-file build/zephyr/merged.hex + + supervisor-test-set-1: + name: Supervisor Test Set 1 + needs: build-flash-supervisor-set-1 + runs-on: zephyr-xlarge-runner + + strategy: + matrix: + test: + - name: Hello-World + group: supervisor-helloWorld + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Supervisor ${{ matrix.test.name }} Testcase + run: | + cd tests_hw && bash beginTests.sh ${{ matrix.test.group }} + + - name: Print Hello-World Test Case Logs + if: always() + run: cat /tmp/${{ matrix.test.group }}.log diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 92ade8e6..f4ec3196 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -72,3 +72,12 @@ jobs: secrets: inherit with: devcontainer-tag: ${{ needs.zephyr-devcontainer.outputs.devcontainer-tag }} + + hardware-b_u585i_iot02a: + name: Hardware b_u585i_iot02a + needs: + - zephyr-devcontainer + uses: ./.github/workflows/hardware-bu585.yml + secrets: inherit + with: + devcontainer-tag: ${{ needs.zephyr-devcontainer.outputs.devcontainer-tag }} diff --git a/tests_hw/groups/demo/clean.sh b/tests_hw/groups/demo/clean.sh new file mode 100644 index 00000000..6b895bf1 --- /dev/null +++ b/tests_hw/groups/demo/clean.sh @@ -0,0 +1 @@ +echo "Cleanup is complete" \ No newline at end of file diff --git a/tests_hw/groups/demo/config.json b/tests_hw/groups/demo/config.json new file mode 100644 index 00000000..524dcda6 --- /dev/null +++ b/tests_hw/groups/demo/config.json @@ -0,0 +1,28 @@ +{ + "name": "Demo Validation", + "description": "Test the Ocre demo sample with the default containers", + "setup": [ + { + "name": "Demo Validation Setup", + "exec": "bash setup.sh" + } + ], + "test_suites": [ + { + "name": "Demo Validation Tests", + "description": "Reads output from the demo sample to make sure it successfully ran", + "test_cases": [ + { + "name": "Check Demo Hello World", + "exec": "./testcase.py" + } + ] + } + ], + "cleanup": [ + { + "name": "Demo Validation Cleanup", + "exec": "bash clean.sh" + } + ] + } diff --git a/tests_hw/groups/demo/setup.sh b/tests_hw/groups/demo/setup.sh new file mode 100644 index 00000000..78545bd9 --- /dev/null +++ b/tests_hw/groups/demo/setup.sh @@ -0,0 +1 @@ +echo "Setup is complete" \ No newline at end of file diff --git a/tests_hw/groups/demo/testcase.py b/tests_hw/groups/demo/testcase.py new file mode 100755 index 00000000..260b4c8b --- /dev/null +++ b/tests_hw/groups/demo/testcase.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +import sys +sys.path.append("../..") + +import pexpect +import testlib + +""" +This testcase is to be used following the flashing of the default demo sample to a board. + +The testcase forms a serial connection to the board, sends a break and checks that +the strings from each section of a successful run appears in the output of that break command. +""" + +lines_to_check = [ + "powered by Ocre", + "Generic blinking started.", + "Container exited with status 0", + "Subscriber initialized", + "Publisher initialized", + "Subscriber exited with status 0", + "Publisher exited with status 0", + "Demo completed successfully", +] + +def main(): + serial_conn, pex = testlib.setup('/dev/ttyACM0') + serial_conn.send_break() + pex.expect([pexpect.EOF, pexpect.TIMEOUT], 45) + runtime_output = bytes(pex.before).decode(errors='ignore') + + print("Checking Runtime Output:") + for line in lines_to_check: + print(f"Checking for line: '{line}'") + if (line not in runtime_output): + print(f"Failed to find line: '{line}' in given timeout.") + + testlib.format_runtime_output(runtime_output, "Failed") + testlib.full_exit(serial_conn, 1) + + print("Able to successful verify runtime") + testlib.format_runtime_output(runtime_output, "Entire") + testlib.full_exit(serial_conn, 0) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests_hw/groups/mini/clean.sh b/tests_hw/groups/mini/clean.sh new file mode 100644 index 00000000..6b895bf1 --- /dev/null +++ b/tests_hw/groups/mini/clean.sh @@ -0,0 +1 @@ +echo "Cleanup is complete" \ No newline at end of file diff --git a/tests_hw/groups/mini/config.json b/tests_hw/groups/mini/config.json new file mode 100644 index 00000000..a5c5cea8 --- /dev/null +++ b/tests_hw/groups/mini/config.json @@ -0,0 +1,28 @@ +{ + "name": "Mini Validation", + "description": "Test the Ocre mini sample with the default hello-world container", + "setup": [ + { + "name": "Mini Validation Setup", + "exec": "bash setup.sh" + } + ], + "test_suites": [ + { + "name": "Mini Validation Tests", + "description": "Reads output from mini sample to make sure it successfully ran", + "test_cases": [ + { + "name": "Check Mini Hello World", + "exec": "./testcase.py" + } + ] + } + ], + "cleanup": [ + { + "name": "Mini Validation Cleanup", + "exec": "bash clean.sh" + } + ] + } diff --git a/tests_hw/groups/mini/setup.sh b/tests_hw/groups/mini/setup.sh new file mode 100644 index 00000000..78545bd9 --- /dev/null +++ b/tests_hw/groups/mini/setup.sh @@ -0,0 +1 @@ +echo "Setup is complete" \ No newline at end of file diff --git a/tests_hw/groups/mini/testcase.py b/tests_hw/groups/mini/testcase.py new file mode 100755 index 00000000..9a57bc01 --- /dev/null +++ b/tests_hw/groups/mini/testcase.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +import sys +sys.path.append("../..") + +import pexpect +import testlib + +""" +This testcase is to be used following the flashing of the default mini sample to a board. + +The testcase forms a serial connection to the board, sends a break and checks that +the string "powered by Ocre" appears in the output of that break command. +""" + +lines_to_check = [ + "powered by Ocre", +] + +def main(): + serial_conn, pex = testlib.setup('/dev/ttyACM0') + serial_conn.send_break() + pex.expect([pexpect.EOF, pexpect.TIMEOUT], 45) + runtime_output = bytes(pex.before).decode(errors='ignore') + + print("Checking Runtime Output:") + for line in lines_to_check: + print(f"Checking for line: '{line}'") + if (line not in runtime_output): + print(f"Failed to find line: '{line}' in given timeout.") + + testlib.format_runtime_output(runtime_output, "Failed") + testlib.full_exit(serial_conn, 1) + + print("Able to successful verify runtime") + testlib.format_runtime_output(runtime_output, "Entire") + testlib.full_exit(serial_conn, 0) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests_hw/groups/supervisor-helloWorld/clean.py b/tests_hw/groups/supervisor-helloWorld/clean.py new file mode 100755 index 00000000..a751d939 --- /dev/null +++ b/tests_hw/groups/supervisor-helloWorld/clean.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +import sys +sys.path.append("../..") + +import pexpect +import testlib + + +def main(): + serial_conn, pex = testlib.setup('/dev/ttyACM0') + + print("Cleaning up container hello-world") + pex.write(b'ocre container ps\n') + pex.expect(["ocre:~$", pexpect.TIMEOUT], 30) + runtime_output = bytes(pex.before).decode(errors='ignore') + if ("hello-world" in runtime_output): + pex.write(b'ocre rm hello-world\n') + runtime_output += bytes(pex.before).decode(errors='ignore') + + testlib.format_runtime_output(runtime_output, "Cleanup") + testlib.full_exit(serial_conn, 0) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests_hw/groups/supervisor-helloWorld/config.json b/tests_hw/groups/supervisor-helloWorld/config.json new file mode 100644 index 00000000..6b15bc97 --- /dev/null +++ b/tests_hw/groups/supervisor-helloWorld/config.json @@ -0,0 +1,28 @@ +{ + "name": "Supervisor Sample - Hello World", + "description": "Test hello world container on within the supervisor sample", + "setup": [ + { + "name": "Start Hello-World Container", + "exec": "./setup.py" + } + ], + "test_suites": [ + { + "name": "Test Container Output", + "description": "Reads output from container to make sure it successfully ran", + "test_cases": [ + { + "name": "Check Runtime Hello World", + "exec": "./testcase.py" + } + ] + } + ], + "cleanup": [ + { + "name": "Remove Hello-World Container", + "exec": "./clean.py" + } + ] + } diff --git a/tests_hw/groups/supervisor-helloWorld/setup.py b/tests_hw/groups/supervisor-helloWorld/setup.py new file mode 100755 index 00000000..e8421105 --- /dev/null +++ b/tests_hw/groups/supervisor-helloWorld/setup.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import sys +sys.path.append("../..") + +import testlib +import pexpect + +def main(): + serial_conn, pex = testlib.setup('/dev/ttyACM0') + + print("Creating container on board off of hello-world.wasm") + pex.write(b'ocre create -n hello-world -k ocre:api hello-world.wasm\n') + expect_index = pex.expect(["ocre:~$", pexpect.TIMEOUT], 30) + runtime_output = bytes(pex.before).decode(errors='ignore') + + if (expect_index == 1): + print("Container failed to create container in given timeout") + testlib.format_runtime_output(runtime_output, "Failed") + testlib.full_exit(serial_conn, 1) + + if "Failed to create container" in runtime_output: + print("Failed to create container") + testlib.format_runtime_output(runtime_output, "Failed") + testlib.full_exit(serial_conn, 1) + + testlib.full_exit(serial_conn, 0) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests_hw/groups/supervisor-helloWorld/testcase.py b/tests_hw/groups/supervisor-helloWorld/testcase.py new file mode 100755 index 00000000..4b6bd759 --- /dev/null +++ b/tests_hw/groups/supervisor-helloWorld/testcase.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +import sys +sys.path.append("../..") + +import pexpect +import testlib + +""" +This testcase is to be used following the flashing of the supervisor sample to a board with the hello-world container put up. + +The testcase forms a serial connection to the board, runs the hello-world container +and checks the string "powered by Ocre" appears in the output of the container. +""" + +line = "powered by Ocre" + +def main(): + serial_conn, pex = testlib.setup('/dev/ttyACM0') + + print("Running hello-world container:") + pex.write(b'ocre start hello-world\n') + + expect_index = pex.expect([testlib.shell_prompt, pexpect.TIMEOUT], 30) + runtime_output = bytes(pex.before).decode(errors='ignore') + + if (expect_index == 1 and testlib.shell_prompt not in runtime_output): + print("Container failed to exit in given timeout") + testlib.format_runtime_output(runtime_output, "Failed") + testlib.full_exit(serial_conn, 1) + + if (line not in runtime_output): + print(f"Failed to find line: '{line}' in given timeout.") + testlib.format_runtime_output(runtime_output, "Failed") + testlib.full_exit(serial_conn, 1) + + testlib.format_runtime_output(runtime_output, "Entire") + testlib.full_exit(serial_conn, 0) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests_hw/testlib.py b/tests_hw/testlib.py new file mode 100644 index 00000000..a138c633 --- /dev/null +++ b/tests_hw/testlib.py @@ -0,0 +1,32 @@ +import serial +import sys +import pexpect +import pexpect.fdpexpect +from typing import Tuple + +shell_prompt = "ocre:~$" + +def setup(device_filepath: str) -> Tuple[serial.Serial, pexpect.fdpexpect.fdspawn]: + conn = serial.Serial(device_filepath, 115200, timeout=10) + conn.reset_input_buffer() + conn.reset_output_buffer() + conn.flush() + pex = pexpect.fdpexpect.fdspawn(conn.fileno()) + return (conn, pex) + + +def full_exit(conn: serial.Serial, status: int): + conn.reset_input_buffer() + conn.reset_output_buffer() + conn.flush() + conn.close() + sys.exit(status) + + +def format_runtime_output(runtime_output: str, section: str) -> None: + print(f""" +========== {section} runtime output ========== +{runtime_output} +==========--------------------------========== +""") + return \ No newline at end of file