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
43 changes: 43 additions & 0 deletions .github/workflows/test-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
--cov=pytest_embedded_idf \
--cov=pytest_embedded_jtag \
--cov=pytest_embedded_qemu \
--cov=pytest_embedded_espemu \
--cov=pytest_embedded_serial \
--cov=pytest_embedded_serial_esp \
--cov=pytest_embedded_wokwi \
Expand All @@ -68,6 +69,48 @@ jobs:
pytest-qemu.xml
coverage-qemu.xml

test-espemu:
timeout-minutes: 40
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: "3.14"
- name: Install dependencies
run: |
pip install -U pip
export PIP_EXTRA_INDEX_URL="https://dl.espressif.com/pypi"
pip install cryptography --prefer-binary
pip install -r requirements.txt
bash foreach.sh install
- name: Download and install esp-emu
run: |
curl -fsSL https://raw.githubusercontent.com/espressif/esp-emulator/main/install.sh | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH

- name: Save PR number
run: echo ${{ github.event.number }} > pr_number.txt

- name: Run esp-emu tests
run: |
pytest pytest-embedded-espemu/tests/test_espemu.py \
--junitxml pytest-espemu.xml \
--cov-report=xml:coverage-espemu.xml \
--cov=pytest_embedded \
--cov=pytest_embedded_espemu \
--cov=pytest_embedded_idf \
--cov=pytest_embedded_serial
- name: Upload test results
uses: actions/upload-artifact@v7
if: always()
with:
name: test-results-espemu
path: |
pr_number.txt
pytest-espemu.xml
coverage-espemu.xml

test-python:
timeout-minutes: 40
strategy:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ A pytest plugin that has multiple services available for various functionalities
[![pytest-embedded-serial-esp](https://img.shields.io/pypi/v/pytest-embedded-serial-esp?color=green&label=pytest-embedded-serial-esp)](https://pypi.org/project/pytest-embedded-serial-esp/)
[![pytest-embedded-idf](https://img.shields.io/pypi/v/pytest-embedded-idf?color=green&label=pytest-embedded-idf)](https://pypi.org/project/pytest-embedded-idf/)
[![pytest-embedded-qemu](https://img.shields.io/pypi/v/pytest-embedded-qemu?color=green&label=pytest-embedded-qemu)](https://pypi.org/project/pytest-embedded-qemu/)
[![pytest-embedded-espemu](https://img.shields.io/pypi/v/pytest-embedded-espemu?color=green&label=pytest-embedded-espemu)](https://pypi.org/project/pytest-embedded-espemu/)
[![pytest-embedded-arduino](https://img.shields.io/pypi/v/pytest-embedded-arduino?color=green&label=pytest-embedded-arduino)](https://pypi.org/project/pytest-embedded-arduino/)
[![pytest-embedded-wokwi](https://img.shields.io/pypi/v/pytest-embedded-wokwi?color=green&label=pytest-embedded-wokwi)](https://pypi.org/project/pytest-embedded-wokwi/)
[![pytest-embedded-nuttx](https://img.shields.io/pypi/v/pytest-embedded-nuttx?color=green&label=pytest-embedded-nuttx)](https://pypi.org/project/pytest-embedded-nuttx/)
Expand Down Expand Up @@ -76,6 +77,7 @@ Available services:
- `idf`: auto-detect more app info with [ESP-IDF](https://github.com/espressif/esp-idf) specific rules, auto-flash the binary into the target.
- `jtag`: openocd/gdb utilities
- `qemu`: running test cases on QEMU instead of the real target.
- `espemu`: running test cases on [esp-emu](https://github.com/espressif/esp-emulator) instead of the real target.
- `arduino`: auto-detect more app info with [arduino](https://github.com/arduino/Arduino) specific rules, auto-flash the binary into the target.
- `wokwi`: running test cases with [Wokwi](https://wokwi.com/) instead of the real target.
- `nuttx`: service for [nuttx](https://nuttx.apache.org/) project, optionally with espressif devices.
Expand Down
1 change: 1 addition & 0 deletions all_local_packages.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
-e ./pytest-embedded-idf/
-e ./pytest-embedded-jtag/
-e ./pytest-embedded-qemu/
-e ./pytest-embedded-espemu/
-e ./pytest-embedded-arduino/
-e ./pytest-embedded-nuttx/
-e ./pytest-embedded-wokwi/
18 changes: 18 additions & 0 deletions docs/apis/pytest-embedded-espemu.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
########################
pytest-embedded-espemu
########################

.. automodule:: pytest_embedded_espemu.app
:members:
:undoc-members:
:show-inheritance:

.. automodule:: pytest_embedded_espemu.dut
:members:
:undoc-members:
:show-inheritance:

.. automodule:: pytest_embedded_espemu.espemu
:members:
:undoc-members:
:show-inheritance:
4 changes: 4 additions & 0 deletions docs/concepts/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ graph LR

pytest-embedded-idf -->|optional, support test on espressif chips| pytest-embedded-serial-esp
pytest-embedded-idf -->|optional, support test on qemu| pytest-embedded-qemu
pytest-embedded-idf -->|optional, support test on esp-emu| pytest-embedded-espemu
pytest-embedded-idf -->|optional, support test on wokwi| pytest-embedded-wokwi

pytest-embedded-arduino -->|optional, support test on espressif chips| pytest-embedded-serial-esp
Expand All @@ -52,6 +53,9 @@ Activate a service would enable a set of fixtures or add some extra functionalit
```{include} ../../pytest-embedded-qemu/README.md
```

```{include} ../../pytest-embedded-espemu/README.md
```

```{include} ../../pytest-embedded-arduino/README.md
```

Expand Down
1 change: 1 addition & 0 deletions foreach.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ DEFAULT_PACKAGES=" \
pytest-embedded-idf \
pytest-embedded-jtag \
pytest-embedded-qemu \
pytest-embedded-espemu \
pytest-embedded-arduino \
pytest-embedded-wokwi \
pytest-embedded-nuttx \
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ version_files = [
"pytest-embedded-jtag/pyproject.toml",
"pytest-embedded-qemu/pytest_embedded_qemu/__init__.py",
"pytest-embedded-qemu/pyproject.toml",
"pytest-embedded-espemu/pytest_embedded_espemu/__init__.py",
"pytest-embedded-espemu/pyproject.toml",
"pytest-embedded-serial/pytest_embedded_serial/__init__.py",
"pytest-embedded-serial/pyproject.toml",
"pytest-embedded-serial-esp/pytest_embedded_serial_esp/__init__.py",
Expand Down
21 changes: 21 additions & 0 deletions pytest-embedded-espemu/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Espressif Systems (Shanghai) Co. Ltd.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
19 changes: 19 additions & 0 deletions pytest-embedded-espemu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### pytest-embedded-espemu

pytest-embedded service for running tests on [esp-emu](https://github.com/espressif/esp-emulator), Espressif's lightweight emulator for ESP RISC-V series SoCs, instead of real targets.

Supported targets: `esp32c3`, `esp32c6`, `esp32h2`, `esp32p4`, `esp32s31`.

#### Usage

```shell
pytest --embedded-services idf,espemu --target esp32c3
```

The service builds a merged flash binary from the app's build directory (via `esptool merge-bin`), launches `esp-emu` with UART0 on stdio, and drives it like any other DUT — including the Unity test menu machinery from `pytest-embedded-idf`.

Extra CLI options:

- `--espemu-image-path`: use an existing merged flash binary instead of generating one
- `--espemu-prog-path`: path to the `esp-emu` binary (default: `esp-emu` from `PATH`)
- `--espemu-cli-args` / `--espemu-extra-args`: forwarded to the `esp-emu` command line, e.g. `--espemu-extra-args "--net user,restrict=yes"`
44 changes: 44 additions & 0 deletions pytest-embedded-espemu/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "pytest-embedded-espemu"
authors = [
{name = "Espressif Systems"},
]
readme = "README.md"
license = {file = "LICENSE"}
classifiers = [
"Development Status :: 4 - Beta",
"Framework :: Pytest",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python",
"Topic :: Software Development :: Testing",
]
dynamic = ["version", "description"]
requires-python = ">=3.10"

dependencies = [
"pytest-embedded~=2.8.1",
]

[project.optional-dependencies]
idf = [
"pytest-embedded-idf~=2.8.1",
]

[project.urls]
homepage = "https://github.com/espressif/pytest-embedded"
repository = "https://github.com/espressif/pytest-embedded"
documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
31 changes: 31 additions & 0 deletions pytest-embedded-espemu/pytest_embedded_espemu/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Make pytest-embedded plugin work with esp-emu."""

import importlib

from pytest_embedded.utils import lazy_load

DEFAULT_IMAGE_FN = 'espemu_image.bin'

from .dut import EspEmuDut # noqa
from .espemu import EspEmu # noqa

__getattr__ = lazy_load(
importlib.import_module(__name__),
{
'EspEmu': EspEmu,
'EspEmuDut': EspEmuDut,
},
{
'EspEmuApp': '.app', # requires idf
},
)


__all__ = [
'DEFAULT_IMAGE_FN',
'EspEmu',
'EspEmuApp',
'EspEmuDut',
]

__version__ = '2.8.1'
67 changes: 67 additions & 0 deletions pytest-embedded-espemu/pytest_embedded_espemu/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import contextlib
import logging
import os
import sys

from pytest_embedded.log import MessageQueue, live_print_call
from pytest_embedded_idf.app import IdfApp

from . import DEFAULT_IMAGE_FN


class EspEmuApp(IdfApp):
"""
esp-emu App class

Attributes:
image_path (str): esp-emu flash-able bin path
"""

def __init__(
self,
msg_queue: MessageQueue,
espemu_image_path: str | None = None,
skip_regenerate_image: bool | None = False,
**kwargs,
):
self._q = msg_queue

super().__init__(**kwargs)

self.image_path = espemu_image_path or os.path.join(self.binary_path, DEFAULT_IMAGE_FN)
self.skip_regenerate_image = skip_regenerate_image

self.create_image()

def create_image(self) -> None:
"""
Create the image, if it doesn't exist.
"""
if os.path.exists(self.image_path) and self.skip_regenerate_image:
logging.info(f'Using existing image: {self.image_path}')
return

try:
import esptool # noqa
except ImportError:
raise ImportError(
'esptool is required for creating esp-emu images. '
'Please install esptool with "pip install -U esptool" or use an existing image.'
)

# esp-emu accepts a plain merged flash binary, no flash-size padding needed
with contextlib.redirect_stdout(self._q):
live_print_call(
[
sys.executable,
'-m',
'esptool',
'--chip',
self.target,
'merge-bin',
'-o',
self.image_path,
*self.write_flash_args,
],
cwd=self.binary_path,
)
25 changes: 25 additions & 0 deletions pytest-embedded-espemu/pytest_embedded_espemu/dut.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import AnyStr

from pytest_embedded.dut import Dut

from .espemu import EspEmu


class EspEmuDut(Dut):
"""
esp-emu dut class
"""

def __init__(
self,
espemu: EspEmu,
**kwargs,
) -> None:
self.espemu = espemu

super().__init__(**kwargs)

self._hard_reset_func = self.espemu._hard_reset

def write(self, s: AnyStr) -> None:
self.espemu.write(s)
Loading