diff --git a/.github/scripts/tests_run.sh b/.github/scripts/tests_run.sh index ef56fcf2d0a..501fc69d3f0 100755 --- a/.github/scripts/tests_run.sh +++ b/.github/scripts/tests_run.sh @@ -8,6 +8,11 @@ function run_test() { local sketchdir=$(dirname $sketch) local sketchname=$(basename $sketchdir) + if [[ -f "$sketchdir/.skip.$platform" ]]; then + echo "Skipping $sketchname test in $target" + exit 0 + fi + if [ $options -eq 0 ] && [ -f $sketchdir/cfg.json ]; then len=`jq -r --arg chip $target '.targets[] | select(.name==$chip) | .fqbn | length' $sketchdir/cfg.json` else @@ -33,7 +38,16 @@ function run_test() { report_file="tests/$sketchname/$sketchname$i.xml" fi - pytest tests --build-dir $build_dir -k test_$sketchname --junit-xml=$report_file + if [ $platform == "wokwi" ]; then + extra_args="--target $target --embedded-services arduino,wokwi --wokwi-timeout=$wokwi_timeout" + if [[ -f "$sketchdir/scenario.yaml" ]]; then + extra_args+=" --wokwi-scenario $sketchdir/scenario.yaml" + fi + else + extra_args="--embedded-services esp,arduino" + fi + + pytest tests --build-dir $build_dir -k test_$sketchname --junit-xml=$report_file $extra_args result=$? if [ $result -ne 0 ]; then return $result @@ -44,6 +58,8 @@ function run_test() { SCRIPTS_DIR="./.github/scripts" COUNT_SKETCHES="${SCRIPTS_DIR}/sketch_utils.sh count" +platform="hardware" +wokwi_timeout=60000 chunk_run=0 options=0 erase=0 @@ -53,6 +69,11 @@ while [ ! -z "$1" ]; do -c ) chunk_run=1 ;; + -w ) + shift + wokwi_timeout=$1 + platform="wokwi" + ;; -o ) options=1 ;; diff --git a/.github/workflows/hil.yml b/.github/workflows/hil.yml index bc3afe4193a..699c9a3e91f 100644 --- a/.github/workflows/hil.yml +++ b/.github/workflows/hil.yml @@ -1,4 +1,4 @@ -name: Run tests in hardware +name: Run tests on: pull_request: @@ -7,8 +7,13 @@ on: schedule: - cron: '0 2 * * *' + push: + branches: + - master + env: MAX_CHUNKS: 15 + WOKWI_TIMEOUT: 120000 # Milliseconds concurrency: group: hil-${{github.event.pull_request.number || github.ref}} @@ -16,9 +21,7 @@ concurrency: jobs: gen_chunks: - if: | - contains(github.event.pull_request.labels.*.name, 'hil_test') || - (github.event_name == 'schedule' && github.repository == 'espressif/arduino-esp32') + if: github.repository == 'espressif/arduino-esp32' name: Generate Chunks matrix runs-on: ubuntu-latest outputs: @@ -26,6 +29,8 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} # Check out the code of the PR - name: Generate Chunks matrix id: gen-chunks @@ -41,7 +46,7 @@ jobs: CHUNKS=$(jq -c -n '$ARGS.positional' --args `seq 0 1 $((sketches - 1))`) echo "chunks=${CHUNKS}" >>$GITHUB_OUTPUT - Build: + build: needs: gen_chunks name: ${{matrix.chip}}-Build#${{matrix.chunks}} runs-on: ubuntu-latest @@ -52,6 +57,9 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} # Check out the code of the PR + - name: Build sketches run: | bash .github/scripts/tests_build.sh -c -t ${{matrix.chip}} -i ${{matrix.chunks}} -m ${{env.MAX_CHUNKS}} @@ -63,9 +71,60 @@ jobs: ~/.arduino/tests/*/build*.tmp/*.bin ~/.arduino/tests/*/build*.tmp/*.json if-no-files-found: error - Test: - needs: [gen_chunks, Build] - name: ${{matrix.chip}}-Test#${{matrix.chunks}} + + wokwi-test: + needs: [gen_chunks, build] + name: ${{matrix.chip}}-Wokwi_Test#${{matrix.chunks}} + if: | + github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'pull_request' + strategy: + fail-fast: false + matrix: + chip: ['esp32', 'esp32s2', 'esp32s3', 'esp32c3', 'esp32c6', 'esp32h2'] + chunks: ${{fromJson(needs.gen_chunks.outputs.chunks)}} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download ${{matrix.chip}}-${{matrix.chunks}} artifacts + uses: actions/download-artifact@v4 + with: + name: ${{matrix.chip}}-${{matrix.chunks}}.artifacts + path: ~/.arduino/tests/ + + - name: Install Wokwi CLI + run: curl -L https://wokwi.com/ci/install.sh | sh + + - name: Install dependencies + run: | + pip install -U pip + pip install -r tests/requirements.txt --extra-index-url https://dl.espressif.com/pypi + git clone https://github.com/P-R-O-C-H-Y/pytest-embedded.git + cd pytest-embedded + git checkout feature/wokwi-scenario-support + pip install -e pytest-embedded-wokwi + cd .. + sudo apt update && sudo apt install -y -qq jq + + - name: Run Tests + env: + WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }} + run: | + bash .github/scripts/tests_run.sh -c -t ${{matrix.chip}} -i ${{matrix.chunks}} -m ${{env.MAX_CHUNKS}} -w ${{env.WOKWI_TIMEOUT}} + + - name: Upload test result artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: wokwi_results-${{matrix.chip}}-${{matrix.chunks}} + path: tests/*/*.xml + + hardware-test: + needs: [gen_chunks, build] + name: ${{matrix.chip}}-Hardware_Test#${{matrix.chunks}} + if: | + contains(github.event.pull_request.labels.*.name, 'hil_test') || github.event_name == 'schedule' strategy: fail-fast: false matrix: @@ -100,7 +159,7 @@ jobs: uses: actions/upload-artifact@v4 if: always() with: - name: test_results-${{matrix.chip}}-${{matrix.chunks}} + name: hw_results-${{matrix.chip}}-${{matrix.chunks}} path: tests/*/*.xml event_file: @@ -108,7 +167,7 @@ jobs: if: | contains(github.event.pull_request.labels.*.name, 'hil_test') || github.event_name == 'schedule' - needs: Test + needs: hardware-test runs-on: ubuntu-latest steps: - name: Upload diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 34d3564c4a8..026c9d59094 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,7 +2,7 @@ name: Unit Test Results on: workflow_run: - workflows: [Run tests in hardware] + workflows: [Run tests] branches-ignore: [master] types: diff --git a/tests/gpio/.skip.hardware b/tests/gpio/.skip.hardware new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/gpio/.skip.qemu b/tests/gpio/.skip.qemu new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/gpio/esp32.diagram.json b/tests/gpio/esp32.diagram.json new file mode 100644 index 00000000000..ef4574bffa0 --- /dev/null +++ b/tests/gpio/esp32.diagram.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "author": "P-R-O-C-H-Y", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-devkit-c-v4", + "id": "esp32", + "top": -57.6, + "left": -177.56, + "attrs": {} + }, + { + "type": "wokwi-pushbutton", + "id": "btn1", + "top": -13, + "left": -19.2, + "attrs": { "color": "green" } + } + ], + "connections": [ + [ "esp32:RX", "$serialMonitor:TX", "", [] ], + [ "esp32:TX", "$serialMonitor:RX", "", [] ], + [ "btn1:1.l", "esp32:0", "blue", [ "h-19.2", "v48", "h-38.4" ] ], + [ "btn1:2.r", "esp32:GND.1", "black", [ "h19.4", "v173", "h-269.2", "v-98.23" ] ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/tests/gpio/esp32c3.diagram.json b/tests/gpio/esp32c3.diagram.json new file mode 100644 index 00000000000..3404e289c67 --- /dev/null +++ b/tests/gpio/esp32c3.diagram.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "author": "P-R-O-C-H-Y", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-c3-devkitm-1", + "id": "esp32", + "top": -57.6, + "left": -177.56, + "attrs": {} + }, + { + "type": "wokwi-pushbutton", + "id": "btn1", + "top": -22.6, + "left": -19.2, + "attrs": { "color": "green" } + } + ], + "connections": [ + [ "esp32:RX", "$serialMonitor:TX", "", [] ], + [ "esp32:TX", "$serialMonitor:RX", "", [] ], + [ "btn1:1.l", "esp32:0", "blue", [ "h-28.8", "v144", "h-144", "v-95.7" ] ], + [ "btn1:2.r", "esp32:GND.1", "black", [ "h19.4", "v173", "h-269.2", "v-98.23" ] ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/tests/gpio/esp32c6.diagram.json b/tests/gpio/esp32c6.diagram.json new file mode 100644 index 00000000000..f4156b092ed --- /dev/null +++ b/tests/gpio/esp32c6.diagram.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "author": "P-R-O-C-H-Y", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-c6-devkitc-1", + "id": "esp32", + "top": -57.6, + "left": -177.56, + "attrs": {} + }, + { + "type": "wokwi-pushbutton", + "id": "btn1", + "top": -22.6, + "left": -19.2, + "attrs": { "color": "green" } + } + ], + "connections": [ + [ "esp32:RX", "$serialMonitor:TX", "", [] ], + [ "esp32:TX", "$serialMonitor:RX", "", [] ], + [ "btn1:1.l", "esp32:0", "blue", [ "h-19.2", "v-96", "h-163.2", "v93.77" ] ], + [ "btn1:2.r", "esp32:GND.1", "black", [ "h19.4", "v173", "h-269.2", "v-98.23" ] ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/tests/gpio/esp32h2.diagram.json b/tests/gpio/esp32h2.diagram.json new file mode 100644 index 00000000000..46309019003 --- /dev/null +++ b/tests/gpio/esp32h2.diagram.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "author": "P-R-O-C-H-Y", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-h2-devkitm-1", + "id": "esp32", + "top": -57.6, + "left": -177.56, + "attrs": {} + }, + { + "type": "wokwi-pushbutton", + "id": "btn1", + "top": -22.6, + "left": -19.2, + "attrs": { "color": "green" } + } + ], + "connections": [ + [ "esp32:RX", "$serialMonitor:TX", "", [] ], + [ "esp32:TX", "$serialMonitor:RX", "", [] ], + [ "btn1:1.l", "esp32:0", "blue", [ "h-19.2", "v-96", "h-163.2", "v93.77" ] ], + [ "btn1:2.r", "esp32:GND.1", "black", [ "h19.4", "v173", "h-269.2", "v-98.23" ] ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/tests/gpio/esp32s2.diagram.json b/tests/gpio/esp32s2.diagram.json new file mode 100644 index 00000000000..9244824fddb --- /dev/null +++ b/tests/gpio/esp32s2.diagram.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "author": "P-R-O-C-H-Y", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-s2-devkitm-1", + "id": "esp32", + "top": -57.6, + "left": -177.56, + "attrs": {} + }, + { + "type": "wokwi-pushbutton", + "id": "btn1", + "top": -22.6, + "left": -19.2, + "attrs": { "color": "green" } + } + ], + "connections": [ + [ "esp32:RX", "$serialMonitor:TX", "", [] ], + [ "esp32:TX", "$serialMonitor:RX", "", [] ], + [ "btn1:1.l", "esp32:0", "blue", [ "h-28.8", "v-57.6", "h-144", "v42.71" ] ], + [ "btn1:2.r", "esp32:GND.1", "black", [ "h19.4", "v173", "h-269.2", "v-98.23" ] ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/tests/gpio/esp32s3.diagram.json b/tests/gpio/esp32s3.diagram.json new file mode 100644 index 00000000000..0709ac1752d --- /dev/null +++ b/tests/gpio/esp32s3.diagram.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "author": "P-R-O-C-H-Y", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-s3-devkitc-1", + "id": "esp32", + "top": -57.6, + "left": -177.56, + "attrs": {} + }, + { + "type": "wokwi-pushbutton", + "id": "btn1", + "top": -22.6, + "left": -19.2, + "attrs": { "color": "green" } + } + ], + "connections": [ + [ "esp32:RX", "$serialMonitor:TX", "", [] ], + [ "esp32:TX", "$serialMonitor:RX", "", [] ], + [ "btn1:1.l", "esp32:0", "blue", [ "h-38.4", "v105.78" ] ], + [ "btn1:2.r", "esp32:GND.1", "black", [ "h19.4", "v221", "h-269.2", "v-57.42" ] ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/tests/gpio/gpio.ino b/tests/gpio/gpio.ino new file mode 100644 index 00000000000..006e545b0b3 --- /dev/null +++ b/tests/gpio/gpio.ino @@ -0,0 +1,41 @@ +#include +#include + +#define BTN 0 + +void test_button() +{ + Serial.println("Button test"); + static int count = 0; + static int lastState = HIGH; + while(count < 3) + { + int state = digitalRead(BTN); + if (state != lastState) + { + if (state == LOW) + { + count++; + Serial.print("Button pressed "); + Serial.print(count); + Serial.println(" times"); + } + lastState = state; + } + delay(10); + } + TEST_ASSERT_EQUAL(3, count); +} + +void setup() +{ + Serial.begin(115200); + UNITY_BEGIN(); + pinMode(BTN, INPUT_PULLUP); + RUN_TEST(test_button); + UNITY_END(); +} + +void loop() +{ +} \ No newline at end of file diff --git a/tests/gpio/scenario.yaml b/tests/gpio/scenario.yaml new file mode 100644 index 00000000000..aaaf8952b6c --- /dev/null +++ b/tests/gpio/scenario.yaml @@ -0,0 +1,39 @@ +name: Pushbutton counter test +version: 1 +author: Jan Prochazka (jan.prochazka@espressif.com) + +steps: + - wait-serial: 'Button test' + + # Delay 1s (to make sure the test is ready to start) + - delay: 1000ms + # Press once + - set-control: + part-id: btn1 + control: pressed + value: 1 + - delay: 200ms + - set-control: + part-id: btn1 + control: pressed + value: 0 + - delay: 300ms + + # Press 2nd time + - set-control: + part-id: btn1 + control: pressed + value: 1 + - delay: 200ms + - set-control: + part-id: btn1 + control: pressed + value: 0 + - delay: 300ms + + # Press for the 3rd time + - set-control: + part-id: btn1 + control: pressed + value: 1 + - wait-serial: 'Button pressed 3 times' \ No newline at end of file diff --git a/tests/gpio/test_gpio.py b/tests/gpio/test_gpio.py new file mode 100644 index 00000000000..cebe719889d --- /dev/null +++ b/tests/gpio/test_gpio.py @@ -0,0 +1,2 @@ +def test_gpio(dut): + dut.expect_unity_test_output(timeout=240) diff --git a/tests/pytest.ini b/tests/pytest.ini index ef7e6d7c5ae..530533b12bf 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,5 +1,5 @@ [pytest] -addopts = --embedded-services esp,arduino +addopts = --embedded-services esp,arduino,wokwi # log related log_cli = True diff --git a/tests/requirements.txt b/tests/requirements.txt index 896699b5752..e00d80e6ae3 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,5 +1,6 @@ cryptography>=2.1.4 --only-binary cryptography pytest-cov -pytest-embedded-serial-esp>=1.3.4 -pytest-embedded-arduino>=1.3.4 +pytest-embedded-serial-esp>=1.10.0 +pytest-embedded-arduino>=1.10.0 +pytest-embedded-wokwi>=1.10.0 diff --git a/tests/wifi/.skip.hardware b/tests/wifi/.skip.hardware new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/wifi/test_wifi.py b/tests/wifi/test_wifi.py new file mode 100644 index 00000000000..dabb1e2d2ab --- /dev/null +++ b/tests/wifi/test_wifi.py @@ -0,0 +1,2 @@ +def test_wifi(dut): + dut.expect_unity_test_output(timeout=240) diff --git a/tests/wifi/wifi.ino b/tests/wifi/wifi.ino new file mode 100644 index 00000000000..49ce2d2fb92 --- /dev/null +++ b/tests/wifi/wifi.ino @@ -0,0 +1,132 @@ +/* HW Timer test */ +#include + +#define TIMER_FREQUENCY 4000000 +#define TIMER_FREQUENCY_XTAL_CLK 1000 + +/* + * ESP32 - APB clk only (1kHz not possible) + * C3 - APB + XTAL clk + * S2 - APB + XTAL clk + * S3 - APB + XTAL clk + */ + +static hw_timer_t *timer = NULL; +static volatile bool alarm_flag; + +/* setUp / tearDown functions are intended to be called before / after each test. */ +void setUp(void) { + timer = timerBegin(TIMER_FREQUENCY); + if (timer == NULL) { + TEST_FAIL_MESSAGE("Timer init failed in setUp()"); + } + timerStop(timer); + timerRestart(timer); +} + +void tearDown(void) { + timerEnd(timer); +} + +void ARDUINO_ISR_ATTR onTimer() { + alarm_flag = true; +} + +void timer_interrupt_test(void) { + + alarm_flag = false; + timerAttachInterrupt(timer, &onTimer); + timerAlarm(timer, (1.2 * TIMER_FREQUENCY), true, 0); + timerStart(timer); + + delay(2000); + + TEST_ASSERT_EQUAL(true, alarm_flag); + + timerStop(timer); + timerRestart(timer); + alarm_flag = false; + timerDetachInterrupt(timer); + timerStart(timer); + + delay(2000); + TEST_ASSERT_EQUAL(false, alarm_flag); +} + +void timer_divider_test(void) { + + uint64_t time_val; + uint64_t comp_time_val; + + timerStart(timer); + + delay(1000); + time_val = timerRead(timer); + + // compare divider 16 and 8, value should be double + timerEnd(timer); + + timer = timerBegin(2 * TIMER_FREQUENCY); + if (timer == NULL) { + TEST_FAIL_MESSAGE("Timer init failed!"); + } + timerRestart(timer); + delay(1000); + comp_time_val = timerRead(timer); + + TEST_ASSERT_INT_WITHIN(4000, 4000000, time_val); + TEST_ASSERT_INT_WITHIN(8000, 8000000, comp_time_val); + + // divider is 256, value should be 2^4 + timerEnd(timer); + + timer = timerBegin(TIMER_FREQUENCY / 16); + if (timer == NULL) { + TEST_FAIL_MESSAGE("Timer init failed!"); + } + timerRestart(timer); + delay(1000); + comp_time_val = timerRead(timer); + + TEST_ASSERT_INT_WITHIN(4000, 4000000, time_val); + TEST_ASSERT_INT_WITHIN(2500, 250000, comp_time_val); +} + +void timer_read_test(void) { + + uint64_t set_timer_val = 0xFF; + uint64_t get_timer_val = 0; + + timerWrite(timer, set_timer_val); + get_timer_val = timerRead(timer); + + TEST_ASSERT_EQUAL(set_timer_val, get_timer_val); +} + +void timer_clock_select_test(void) { + // Set timer frequency that can be achieved using XTAL clock source (autoselected) + timer = timerBegin(TIMER_FREQUENCY_XTAL_CLK); + + uint32_t resolution = timerGetFrequency(timer); + TEST_ASSERT_EQUAL(TIMER_FREQUENCY_XTAL_CLK, resolution); +} + +void setup() { + + // Open serial communications and wait for port to open: + Serial.begin(115200); + while (!Serial) { + ; + } + + UNITY_BEGIN(); + RUN_TEST(timer_read_test); + RUN_TEST(timer_interrupt_test); + RUN_TEST(timer_divider_test); +#if !CONFIG_IDF_TARGET_ESP32 + RUN_TEST(timer_clock_select_test); +#endif + UNITY_END(); +} + +void loop() {}