Skip to content

Commit

Permalink
feat(wokwi): initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
urish committed Sep 25, 2023
1 parent 57cfd98 commit 5094417
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 2 deletions.
21 changes: 21 additions & 0 deletions pytest-embedded-wokwi/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 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.
1 change: 1 addition & 0 deletions pytest-embedded-wokwi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
### pytest-embedded-wokwi
105 changes: 105 additions & 0 deletions pytest-embedded-wokwi/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "pytest-embedded-wokwi"
authors = [
{name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
]
readme = "README.md"
license = {file = "LICENSE"}
classifiers = [
"Development Status :: 5 - Production/Stable",
"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.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python",
"Topic :: Software Development :: Testing",
]
dynamic = ["version", "description"]
requires-python = ">=3.7"

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

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

[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"

[tool.isort]
profile = 'black'

[tool.black]
line-length = 120
target-version = ['py37']
force-exclude = '/tests/'
skip-string-normalization = true

[tool.ruff]
select = [
'F', # Pyflakes
'E', # pycodestyle
'W', # pycodestyle
# 'C90', # mccabe
# 'I', # isort
# 'N', # pep8-naming
# 'D', # pydocstyle
# 'UP', # pyupgrade
# 'YTT', # flake8-2020
# 'ANN', # flake8-annotations
# 'S', # flake8-bandit
# 'BLE', # flake8-blind-except
# 'FBT', # flake8-boolean-trap
# 'B', # flake8-bugbear
# 'A', # flake8-builtins
# 'COM', # flake8-commas
# 'C4', # flake8-comprehensions
# 'DTZ', # flake8-datetimez
# 'T10', # flake8-debugger
# 'DJ', # flake8-django
# 'EM', # flake8-errmsg
# 'EXE', # flake8-executable
# 'ISC', # flake8-implicit-str-concat
# 'ICN', # flake8-import-conventions
# 'G', # flake8-logging-format
# 'INP', # flake8-no-pep420
# 'PIE', # flake8-pie
# 'T20', # flake8-print
# 'PYI', # flake8-pyi
# 'PT', # flake8-pytest-style
# 'Q', # flake8-quotes
# 'RSE', # flake8-raise
# 'RET', # flake8-return
# 'SLF', # flake8-self
# 'SIM', # flake8-simplify
# 'TID', # flake8-tidy-imports
# 'TCH', # flake8-type-checking
# 'ARG', # flake8-unused-arguments
# 'PTH', # flake8-use-pathlib
# 'ERA', # eradicate
# 'PD', # pandas-vet
# 'PGH', # pygrep-hooks
# 'PL', # Pylint
# 'TRY', # tryceratops
# 'NPY', # NumPy-specific rules
# 'RUF', # Ruff-specific rules
]
line-length = 120
target-version = "py37"
11 changes: 11 additions & 0 deletions pytest-embedded-wokwi/pytest_embedded_wokwi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Make pytest-embedded plugin work with the Wokwi CLI."""

from .dut import WokwiDut # noqa
from .wokwi_cli import WokwiCLI # noqa

__all__ = [
'WokwiCLI',
'WokwiDut',
]

__version__ = '1.3.5'
25 changes: 25 additions & 0 deletions pytest-embedded-wokwi/pytest_embedded_wokwi/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 .wokwi_cli import WokwiCLI


class WokwiDut(Dut):
"""
Wokwi DUT class
"""

def __init__(
self,
wokwi: WokwiCLI,
**kwargs,
) -> None:
self.wokwi = wokwi

super().__init__(**kwargs)

self._hard_reset_func = self.wokwi._hard_reset

def write(self, s: AnyStr) -> None:
self.wokwi.write(s)
100 changes: 100 additions & 0 deletions pytest-embedded-wokwi/pytest_embedded_wokwi/wokwi_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import json
import os
import typing as t

from pytest_embedded import __version__
from pytest_embedded.log import DuplicateStdoutPopen

if t.TYPE_CHECKING:
from pytest_embedded_idf.app import IdfApp


target_to_board = {
'esp32': 'board-esp32-devkit-c-v4',
'esp32c3': 'board-esp32-c3-devkitm-1',
'esp32c6': 'board-esp32-c6-devkitc-1',
'esp32h2': 'board-esp32-h2-devkitm-1',
'esp32s2': 'board-esp32-s2-devkitm-1',
'esp32s3': 'board-esp32-s3-devkitc-1',
}


class WokwiCLI(DuplicateStdoutPopen):
"""
WokwiCLI class
"""

SOURCE = 'Wokwi'

WOKWI_CLI_PATH = 'wokwi-cli'

def __init__(
self,
wokwi_cli_path: t.Optional[str] = None,
app: t.Optional['IdfApp'] = None,
**kwargs,
):
"""
Args:
wokwi_cli_path: Wokwi CLI arguments
"""
self.app = app

bin_list = []
app_binary = None
for file in app.flash_files:
if not os.path.exists(file.file_path):
raise ValueError(f'Firmware binary file doesn\'t exist: {file.file_path}')
file_rel_path = os.path.relpath(app.elf_file, app.app_path)
bin_list.append(f"'{file.offset:#x} {file_rel_path}',")
# The following is a workaround until wokwi-cli supports multiple binaries
if file.offset == 0x10000:
app_binary = file_rel_path

bin_list_toml = '\n '.join(bin_list)
with open(os.path.join(app.app_path, 'wokwi.toml'), 'wt') as f:
f.write(
f"""
[wokwi]
version = 1
generatedBy = 'pytest-embedded-wokwi {__version__}'
firmware = '{app_binary}'
elf = '{os.path.relpath(app.elf_file, app.app_path)}'
# We don't support multiple binaries yet, so this will be ignored for now:
flash = [
{bin_list_toml}
]
"""
)

# TODO: Check if diagram already exist and update it?
diagram = {
'version': 1,
'author': 'Uri Shaked',
'editor': 'wokwi',
'parts': [{'type': target_to_board[app.target], 'id': 'esp'}],
'connections': [
['esp:TX', '$serialMonitor:RX', '', []],
['esp:RX', '$serialMonitor:TX', '', []],
],
}
with open(os.path.join(app.app_path, 'diagram.json'), 'wt') as f:
f.write(json.dumps(diagram))

wokwi_cli = wokwi_cli_path or self.wokwi_cli_executable

super().__init__(
cmd=[wokwi_cli, app.app_path],
**kwargs,
)

@property
def wokwi_cli_executable(self):
return self.WOKWI_CLI_PATH

def _hard_reset(self):
"""
This is a fake hard_reset. Keep this API to keep the consistency.
"""
raise NotImplementedError
Loading

0 comments on commit 5094417

Please sign in to comment.