Skip to content

Commit 8c80da1

Browse files
committed
Merge pull request #1308 from petermm/wokwi-ci
Add Wokwi CI - esp32-simtest.yaml **NB:** The `WOKWI_CLI_TOKEN` (obtained here https://wokwi.com/dashboard/ci) needs to be set in the `Repository secrets` eg: Settings -> Actions secrets and variables. Runs the esp32 tests across 7 targets/models and 3 esp-idf versions. A limited test scope (latest esp-idf only) is the default unless the PR title contains "full_sim_test" or the last commit message contains it in title or body. Includes the wifi_example test on boards with wifi. The esp32 board has a SPI SD card attached and tests are run against it ("test_file.beam"). For PR brevity the test_mount (sdmmc test), is not yet adapted for sdspi testing. Likewise each board has potentiometer attached, for ADC testing - for PR brevity tests are not included. One can easily implement uart/i2c etc tests in the future, and massively improve CI coverage. Requires a `WOKWI_CLI_TOKEN` obtained here: https://wokwi.com/dashboard/ci - will gracefully handle it's absence. Includes readme for running from local install. Exercises the release build config, for largest coverage: ``` CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y ``` full_sim_test: https://github.com/petermm/AtomVM/actions/runs/11274530437 <img width="817" alt="Screenshot 2024-10-09 at 23 10 50" src="https://github.com/user-attachments/assets/9a307677-eddb-4ce8-b2d9-dc874b1a1bb9"> These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 65f644a + 8b3b997 commit 8c80da1

23 files changed

+599
-4
lines changed

.github/workflows/esp32-simtest.yaml

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#
2+
# Copyright 2024 Davide Bettio <davide@uninstall.it>
3+
# Copyright 2024 Peter Madsen-Mygdal <petermm@gmail.com>
4+
#
5+
# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
6+
#
7+
8+
name: ESP32 Sim test
9+
10+
on:
11+
push:
12+
paths:
13+
- ".github/workflows/esp32-simtest.yaml"
14+
- "CMakeLists.txt"
15+
- "libs/**"
16+
- "src/platforms/esp32/**"
17+
- "src/platforms/esp32/**/**"
18+
- "src/libAtomVM/**"
19+
- "tools/packbeam/**"
20+
pull_request:
21+
paths:
22+
- ".github/workflows/esp32-simtest.yaml"
23+
- "src/platforms/esp32/**"
24+
- "src/platforms/esp32/**/**"
25+
- "src/libAtomVM/**"
26+
27+
concurrency:
28+
group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/main' && github.ref || github.run_id }}
29+
cancel-in-progress: true
30+
31+
jobs:
32+
cli_token:
33+
name: WOKWI_CLI_TOKEN presence
34+
runs-on: ubuntu-24.04
35+
outputs:
36+
token_check: ${{ steps.token_check.outputs.should-run }}
37+
38+
steps:
39+
- name: Mark esp-sim-test job as 'to be run'
40+
id: token_check
41+
env:
42+
wokwi_secret: ${{ secrets.WOKWI_CLI_TOKEN }}
43+
run: |
44+
if [${{ env.wokwi_secret }} == ''];
45+
then
46+
echo "WOKWI_CLI_TOKEN not found, please add it to your repository secrets"
47+
48+
else
49+
echo "WOKWI_CLI_TOKEN found continuing..."
50+
echo "should-run=true" >> $GITHUB_OUTPUT
51+
52+
fi
53+
esp-sim-test:
54+
needs: cli_token
55+
runs-on: ubuntu-24.04
56+
if: needs.cli_token.outputs.token_check == 'true'
57+
container: espressif/idf:${{ matrix.idf-version }}
58+
strategy:
59+
fail-fast: false
60+
# focus on device diversity.
61+
matrix:
62+
esp-idf-target: ["esp32", "esp32s2", "esp32s3", "esp32c3", "esp32c6"]
63+
idf-version: ${{ ((contains(github.event.head_commit.message, 'full_sim_test')||contains(github.event.pull_request.title, 'full_sim_test')) && fromJSON('["v5.1.5", "v5.2.3", "v5.3.2", "v5.4-beta1"]')) || fromJSON('["v5.3.2"]') }}
64+
include:
65+
- esp-idf-target: "esp32p4"
66+
idf-version: "v5.3.2"
67+
- esp-idf-target: "esp32h2"
68+
idf-version: "v5.3.2"
69+
70+
steps:
71+
- name: Checkout repo
72+
uses: actions/checkout@v4
73+
74+
- name: Install dependencies to build host AtomVM
75+
run: |
76+
set -eu
77+
apt update
78+
DEBIAN_FRONTEND=noninteractive apt install -y -q \
79+
doxygen erlang-base erlang-dialyzer \
80+
libglib2.0-0 libpixman-1-0 \
81+
gcc g++ zlib1g-dev libsdl2-2.0-0 libslirp0 libmbedtls-dev
82+
83+
- name: Install the Wokwi CLI
84+
run: curl -L https://wokwi.com/ci/install.sh | sh
85+
86+
- name: Install pytest and pytest-embedded plugins
87+
run: |
88+
set -e
89+
. $IDF_PATH/export.sh
90+
pip install pytest==8.3.4 \
91+
pytest-embedded==1.12.1 \
92+
pytest-embedded-idf==1.12.1 \
93+
pytest-embedded-qemu==1.12.1 \
94+
pytest-embedded-wokwi==1.12.1
95+
96+
- name: Set SDKCONFIG_DEFAULTS and Build ESP32-sim tests using idf.py
97+
working-directory: ./src/platforms/esp32/test/
98+
run: |
99+
set -e
100+
. $IDF_PATH/export.sh
101+
idf.py -DSDKCONFIG_DEFAULTS='sdkconfig.ci.wokwi' set-target ${{matrix.esp-idf-target}}
102+
idf.py build
103+
104+
- name: Run ESP32-sim tests using Wokwi CI
105+
working-directory: ./src/platforms/esp32/test/
106+
env:
107+
WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }}
108+
timeout-minutes: 10
109+
run: |
110+
set -e
111+
. $IDF_PATH/export.sh
112+
pytest --embedded-services=idf,wokwi --wokwi-timeout=240000 --target=${{ matrix.esp-idf-target }} --wokwi-diagram=sim_boards/diagram.${{ matrix.esp-idf-target }}.json -s

src/platforms/esp32/test/README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!--
2+
Copyright 2024 Davide Bettio <davide@uninstall.it>
3+
4+
SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
5+
-->
6+
7+
# AtomVM emulator/simulator tests
8+
9+
AtomVM provides two paths for testing "on device", the locally run QEMU emulator or the remote Wokwi CI simulator.
10+
11+
# QEMU emulator testing
12+
13+
Instructions for running the tests [on QEMU are documented here](https://www.atomvm.net/doc/main/build-instructions.html#running-tests-for-esp32).
14+
15+
# Wokwi CI simulator testing
16+
17+
Wokwi CI is a commercial cloud CI, see [wokwi-ci/getting-started](https://docs.wokwi.com/wokwi-ci/getting-started), running it locally requires you to obtain a `WOKWI_CLI_TOKEN` [Get token](https://wokwi.com/dashboard/ci) and usage fees may apply in the future - AtomVM uses it through the [pytest-embedded-wokwi](https://github.com/espressif/pytest-embedded/tree/main/pytest-embedded-wokwi) integration.
18+
19+
## Github CI/Actions
20+
21+
The `WOKWI_CLI_TOKEN` needs to be set in your `Repository secrets` Settings -> Actions secrets and variables.
22+
23+
## Installing prerequisites
24+
25+
1. The Wokwi CLI needs to be installed:
26+
27+
```shell
28+
curl -L https://wokwi.com/ci/install.sh | sh
29+
```
30+
31+
Or [alternative installation methods here](https://docs.wokwi.com/wokwi-ci/getting-started#cli-installation).
32+
33+
2. `WOKWI_CLI_TOKEN` needs to be set in your enviroment variables:
34+
35+
```shell
36+
export WOKWI_CLI_TOKEN="your-api-key"
37+
```
38+
39+
3. A recent pytest, and pytest-embedded must be installed:
40+
41+
```shell
42+
$ pip install pytest==8.3.3 \
43+
pytest-embedded==1.11.5 \
44+
pytest-embedded-serial-esp==1.11.5 \
45+
pytest-embedded-idf==1.11.5 \
46+
pytest-embedded-wokwi==1.11.5
47+
```
48+
49+
4. The ESP-IDF build environment must be installed and available:
50+
51+
```shell
52+
get_idf
53+
```
54+
55+
## Compiling for and running Wokwi CI
56+
57+
1. We need to use a special sdkconfig (`sdkconfig.ci.wokwi`) different from the QEMU one. So we set `IDF_TARGET`, and run `idf.py -DSDKCONFIG_DEFAULTS=sdkconfig.ci.wokwi set-target ${IDF_TARGET}`:
58+
59+
```shell
60+
export IDF_TARGET=esp32 && idf.py -DSDKCONFIG_DEFAULTS=sdkconfig.ci.wokwi set-target ${IDF_TARGET}
61+
```
62+
63+
2. Wokwi CI uses a `diagram.json`, to describe the device used (specific board, pin connections, sensors, sd card etc). 'diagram.json' files are available for each target in the sim_boards folder.
64+
65+
3. Now we run `idf.py build` and run the CI:
66+
67+
```shell
68+
idf.py build -DSDKCONFIG_DEFAULTS='sdkconfig.ci.wokwi' && pytest --embedded-services=idf,wokwi --wokwi-timeout=90000 --target=${IDF_TARGET} --wokwi-diagram=sim_boards/diagram.${IDF_TARGET}.json -s -W ignore::DeprecationWarning
69+
```

src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ endfunction()
3939

4040
compile_erlang(test_esp_partition)
4141
compile_erlang(test_file)
42+
compile_erlang(test_wifi_example)
4243
compile_erlang(test_list_to_binary)
4344
compile_erlang(test_md5)
4445
compile_erlang(test_crypto)
@@ -59,6 +60,7 @@ add_custom_command(
5960
HostAtomVM-prefix/src/HostAtomVM-build/libs/atomvmlib.avm
6061
test_esp_partition.beam
6162
test_file.beam
63+
test_wifi_example.beam
6264
test_list_to_binary.beam
6365
test_md5.beam
6466
test_crypto.beam
@@ -75,6 +77,7 @@ add_custom_command(
7577
DEPENDS
7678
HostAtomVM
7779
"${CMAKE_CURRENT_BINARY_DIR}/test_esp_partition.beam"
80+
"${CMAKE_CURRENT_BINARY_DIR}/test_wifi_example.beam"
7881
"${CMAKE_CURRENT_BINARY_DIR}/test_file.beam"
7982
"${CMAKE_CURRENT_BINARY_DIR}/test_list_to_binary.beam"
8083
"${CMAKE_CURRENT_BINARY_DIR}/test_md5.beam"
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2020 Fred Dushin <fred@dushin.net>
5+
%
6+
% Licensed under the Apache License, Version 2.0 (the "License");
7+
% you may not use this file except in compliance with the License.
8+
% You may obtain a copy of the License at
9+
%
10+
% http://www.apache.org/licenses/LICENSE-2.0
11+
%
12+
% Unless required by applicable law or agreed to in writing, software
13+
% distributed under the License is distributed on an "AS IS" BASIS,
14+
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
% See the License for the specific language governing permissions and
16+
% limitations under the License.
17+
%
18+
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
%
20+
21+
-module(test_wifi_example).
22+
23+
-export([start/0]).
24+
25+
start() ->
26+
case verify_platform(atomvm:platform()) of
27+
ok ->
28+
start_network(),
29+
loop(0);
30+
Error ->
31+
Error
32+
end.
33+
34+
start_network() ->
35+
Config = [
36+
{ap, [
37+
{ap_started, fun ap_started/0},
38+
{sta_connected, fun sta_connected/1},
39+
{sta_ip_assigned, fun sta_ip_assigned/1},
40+
{sta_disconnected, fun sta_disconnected/1}
41+
| []
42+
]},
43+
{sta, [
44+
{connected, fun connected/0},
45+
{got_ip, fun got_ip/1},
46+
{disconnected, fun disconnected/0}
47+
| [
48+
{dhcp_hostname, "my_device_name"},
49+
{ssid, "Wokwi-GUEST"},
50+
{psk, ""}
51+
]
52+
]},
53+
{sntp, [
54+
{host, "time.aws.com"},
55+
{synchronized, fun sntp_synchronized/1}
56+
]}
57+
],
58+
case network:start(Config) of
59+
{ok, _Pid} ->
60+
io:format("Network started.~n");
61+
Error ->
62+
Error
63+
end.
64+
65+
ap_started() ->
66+
io:format("AP started.~n").
67+
68+
sta_connected(Mac) ->
69+
io:format("STA connected with mac ~p~n", [Mac]).
70+
71+
sta_disconnected(Mac) ->
72+
io:format("STA disconnected with mac ~p~n", [Mac]).
73+
74+
sta_ip_assigned(Address) ->
75+
io:format("STA assigned address ~p~n", [Address]).
76+
77+
connected() ->
78+
io:format("STA connected.~n").
79+
80+
got_ip(IpInfo) ->
81+
io:format("Got IP: ~p.~n", [IpInfo]).
82+
83+
disconnected() ->
84+
io:format("STA disconnected.~n").
85+
86+
sntp_synchronized({TVSec, TVUsec}) ->
87+
io:format("Synchronized time with SNTP server. TVSec=~p TVUsec=~p~n", [TVSec, TVUsec]).
88+
89+
verify_platform(esp32) ->
90+
ok;
91+
verify_platform(Platform) ->
92+
{error, {unsupported_platform, Platform}}.
93+
94+
loop(Count) when Count =:= 40 ->
95+
io:format("Never got SNTP.~n"),
96+
tests(),
97+
network:stop();
98+
loop(Count) ->
99+
timer:sleep(500),
100+
{{Year, Month, Day}, {Hour, Minute, Second}} = erlang:universaltime(),
101+
io:format("Date: ~p/~p/~p ~p:~p:~p (~pms)~n", [
102+
Year, Month, Day, Hour, Minute, Second, erlang:system_time(millisecond)
103+
]),
104+
case Year of
105+
1970 ->
106+
loop(Count + 1);
107+
_ ->
108+
io:format("Got SNTP.~n"),
109+
tests(),
110+
network:stop()
111+
end.
112+
tests() ->
113+
ok = test_net:start(),
114+
io:format("test_net OK.~n").

0 commit comments

Comments
 (0)