├── .editorconfig
├── .flake8
├── .git-blame-ignore-revs
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
└── workflows
│ ├── codeql.yml
│ ├── publish_pypi.yml
│ ├── sync-jira.yml
│ ├── test-build-docs.yml
│ ├── test-build-package.yml
│ └── test-python.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── all_local_packages.txt
├── conftest.py
├── docs
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Makefile
├── _static
│ ├── espressif-logo.svg
│ └── theme_overrides.css
├── _templates
│ └── layout.html
├── apis
│ ├── pytest-embedded-arduino.rst
│ ├── pytest-embedded-idf.rst
│ ├── pytest-embedded-jtag.rst
│ ├── pytest-embedded-nuttx.rst
│ ├── pytest-embedded-qemu.rst
│ ├── pytest-embedded-serial-esp.rst
│ ├── pytest-embedded-serial.rst
│ ├── pytest-embedded-wokwi.rst
│ └── pytest-embedded.rst
├── concepts
│ ├── key-concepts.md
│ └── services.md
├── conf.py
├── index.rst
├── requirements.txt
└── usages
│ ├── expecting.rst
│ └── markers.rst
├── examples
├── .gitignore
├── arduino
│ ├── README.md
│ ├── hello_world
│ │ ├── hello_world.ino
│ │ └── test_hello_world.py
│ └── pytest.ini
├── echo
│ ├── README.md
│ ├── conftest.py
│ ├── pytest.ini
│ └── test_echo.py
├── esp-idf-qemu
│ ├── README.md
│ ├── hello_world
│ │ ├── CMakeLists.txt
│ │ ├── main
│ │ │ ├── CMakeLists.txt
│ │ │ └── hello_world_main.c
│ │ └── test_hello_world.py
│ └── pytest.ini
├── esp-idf
│ ├── README.md
│ ├── hello_world
│ │ ├── CMakeLists.txt
│ │ ├── main
│ │ │ ├── CMakeLists.txt
│ │ │ └── hello_world_main.c
│ │ └── test_hello_world.py
│ └── pytest.ini
└── nuttx-esp
│ ├── README.md
│ ├── pytest.ini
│ └── test_nuttx_esp.py
├── foreach.sh
├── pyproject.toml
├── pytest-embedded-arduino
├── LICENSE
├── README.md
├── pyproject.toml
├── pytest_embedded_arduino
│ ├── __init__.py
│ ├── app.py
│ └── serial.py
└── tests
│ └── test_arduino.py
├── pytest-embedded-idf
├── LICENSE
├── README.md
├── pyproject.toml
├── pytest_embedded_idf
│ ├── __init__.py
│ ├── app.py
│ ├── dut.py
│ ├── linux.py
│ ├── serial.py
│ ├── unity_tester.py
│ └── utils.py
└── tests
│ └── test_idf.py
├── pytest-embedded-jtag
├── LICENSE
├── README.md
├── __init__.py
├── pyproject.toml
├── pytest_embedded_jtag
│ ├── __init__.py
│ ├── _telnetlib
│ │ ├── LICENSE
│ │ ├── __init__.py
│ │ └── telnetlib.py
│ ├── gdb.py
│ └── openocd.py
└── tests
│ └── test_jtag.py
├── pytest-embedded-nuttx
├── LICENSE
├── README.md
├── pyproject.toml
├── pytest_embedded_nuttx
│ ├── __init__.py
│ ├── app.py
│ ├── dut.py
│ ├── qemu.py
│ └── serial.py
└── tests
│ └── test_nuttx.py
├── pytest-embedded-qemu
├── LICENSE
├── README.md
├── pyproject.toml
├── pytest_embedded_qemu
│ ├── __init__.py
│ ├── app.py
│ ├── dut.py
│ └── qemu.py
└── tests
│ └── test_qemu.py
├── pytest-embedded-serial-esp
├── LICENSE
├── README.md
├── pyproject.toml
├── pytest_embedded_serial_esp
│ ├── __init__.py
│ └── serial.py
└── tests
│ └── test_esp.py
├── pytest-embedded-serial
├── LICENSE
├── README.md
├── pyproject.toml
├── pytest_embedded_serial
│ ├── __init__.py
│ ├── dut.py
│ └── serial.py
└── tests
│ └── test_serial.py
├── pytest-embedded-wokwi
├── LICENSE
├── README.md
├── pyproject.toml
├── pytest_embedded_wokwi
│ ├── __init__.py
│ ├── arduino.py
│ ├── dut.py
│ ├── idf.py
│ └── wokwi_cli.py
└── tests
│ └── test_wokwi.py
├── pytest-embedded
├── LICENSE
├── README.md
├── pyproject.toml
├── pytest_embedded
│ ├── __init__.py
│ ├── app.py
│ ├── dut.py
│ ├── dut_factory.py
│ ├── log.py
│ ├── plugin.py
│ ├── unity.py
│ └── utils.py
└── tests
│ └── test_base.py
├── requirements.txt
├── ruff.toml
└── tests
├── esp-idf
├── components
│ └── soc
│ │ ├── esp32
│ │ └── include
│ │ │ └── soc
│ │ │ └── soc_caps.h
│ │ ├── esp32c2
│ │ └── include
│ │ │ └── soc
│ │ │ └── soc_caps.h
│ │ ├── esp32c3
│ │ └── include
│ │ │ └── soc
│ │ │ └── soc_caps.h
│ │ ├── esp32c5
│ │ └── include
│ │ │ └── soc
│ │ │ └── soc_caps.h
│ │ ├── esp32c6
│ │ └── include
│ │ │ └── soc
│ │ │ └── soc_caps.h
│ │ ├── esp32c61
│ │ └── include
│ │ │ └── soc
│ │ │ └── soc_caps.h
│ │ ├── esp32h2
│ │ └── include
│ │ │ └── soc
│ │ │ └── soc_caps.h
│ │ ├── esp32h21
│ │ └── include
│ │ │ └── soc
│ │ │ └── soc_caps.h
│ │ ├── esp32p4
│ │ └── include
│ │ │ └── soc
│ │ │ └── soc_caps.h
│ │ ├── esp32s2
│ │ └── include
│ │ │ └── soc
│ │ │ └── soc_caps.h
│ │ └── esp32s3
│ │ └── include
│ │ └── soc
│ │ └── soc_caps.h
└── tools
│ ├── cmake
│ └── version.cmake
│ └── idf_py_actions
│ └── constants.py
└── fixtures
├── esp32_qemu.bin
├── gdb_panic_server.py
├── gen_esp32part.py
├── hello_world_arduino
├── build
│ ├── build.options.json
│ ├── hello_world_arduino.ino.bin
│ ├── hello_world_arduino.ino.bootloader.bin
│ ├── hello_world_arduino.ino.elf
│ └── hello_world_arduino.ino.partitions.bin
└── esp32.diagram.json
├── hello_world_esp32
├── build
│ ├── bootloader
│ │ └── bootloader.bin
│ ├── config
│ │ └── sdkconfig.json
│ ├── flasher_args.json
│ ├── hello_world.bin
│ ├── hello_world.elf
│ └── partition_table
│ │ └── partition-table.bin
└── gdbinit
├── hello_world_esp32_flash_enc
├── build
│ ├── bootloader
│ │ └── bootloader.bin
│ ├── config
│ │ └── sdkconfig.json
│ ├── flasher_args.json
│ ├── hello_world.bin
│ └── partition_table
│ │ └── partition-table.bin
├── pre_encryption_efuses.bin
└── pre_encryption_key.bin
├── hello_world_esp32c3
└── build
│ ├── bootloader
│ └── bootloader.bin
│ ├── config
│ └── sdkconfig.json
│ ├── flasher_args.json
│ ├── hello_world.bin
│ ├── hello_world.elf
│ └── partition_table
│ └── partition-table.bin
├── hello_world_esp32c3_panic
├── build
│ ├── bootloader
│ │ └── bootloader.bin
│ ├── config
│ │ └── sdkconfig.json
│ ├── flasher_args.json
│ ├── hello_world.bin
│ ├── hello_world.elf
│ ├── hello_world.map
│ ├── partition_table
│ │ └── partition-table.bin
│ └── prefix_map_gdbinit
└── main
│ └── hello_world_main.c
├── hello_world_linux
└── build
│ ├── config
│ └── sdkconfig.json
│ ├── hello_world.elf
│ └── project_description.json
├── hello_world_nuttx
└── nuttx.bin
├── hello_world_nuttx_mcuboot
├── mcuboot-esp32.bin
└── nuttx.bin
├── hello_world_nuttx_qemu
├── nuttx.merged.bin
└── qemu_efuse.bin
├── unit_test_app_esp32
├── CMakeLists.txt
├── build
│ ├── bootloader
│ │ └── bootloader.bin
│ ├── case_tester_example.bin
│ ├── config
│ │ └── sdkconfig.json
│ ├── flasher_args.json
│ └── partition_table
│ │ └── partition-table.bin
└── main
│ ├── CMakeLists.txt
│ ├── case_tester_example.c
│ └── test_app_main.c
├── unit_test_app_esp32c3
├── CMakeLists.txt
├── build
│ ├── bootloader
│ │ └── bootloader.bin
│ ├── case_tester_example.bin
│ ├── config
│ │ └── sdkconfig.json
│ ├── flasher_args.json
│ └── partition_table
│ │ └── partition-table.bin
└── main
│ ├── CMakeLists.txt
│ ├── case_tester_example.c
│ └── test_app_main.c
└── unit_test_app_linux
└── build
├── config
└── sdkconfig.json
├── project_description.json
└── test_esp_event.elf
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # http://editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | indent_style = space
9 | indent_size = 4
10 | end_of_line = lf
11 | charset = utf-8
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 |
15 | [{*.md,*.rst}]
16 | trim_trailing_whitespace = false
17 | indent_size = 2
18 |
19 | [*.py]
20 | max_line_length = 119
21 |
22 | [{*.sh,*.yml,*.yaml}]
23 | indent_size = 2
24 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 120
3 | extend-ignore = E203
4 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # ruff, ruff-format
2 | 3f0008207d272b7fa4329d86a157f4533f1a441f
3 | a90b0d0c35a8e1c6896130e23c46cedee48b4158
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Configuration Files**
14 | - [ ] `pytest.ini`
15 | - [ ] `conftest.py`
16 | - [ ] `pyproject.toml`
17 |
18 | **To Reproduce**
19 | Steps to reproduce the behavior:
20 | 1. Run `pytest ...`
21 | 2. Got error message "..."
22 |
23 | **Expected behavior**
24 | A clear and concise description of what you expected to happen.
25 |
26 | **Dev Environment (please complete the following information):**
27 | - OS: [e.g. Windows]
28 | - Python Version [e.g. 3.8.5]
29 | - Pytest Version [e.g. 6.1.2]
30 | - pytest-embedded Version [e.g. 1.0.0]
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "github-actions" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 |
7 | jobs:
8 | analyze:
9 | name: Analyze
10 | runs-on: ubuntu-latest
11 | permissions:
12 | security-events: write
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | language: [ 'python' ]
17 | steps:
18 | - name: Checkout repository
19 | uses: actions/checkout@v4
20 | - name: Initialize CodeQL
21 | uses: github/codeql-action/init@v3
22 | with:
23 | languages: ${{ matrix.language }}
24 | - name: Perform CodeQL Analysis
25 | uses: github/codeql-action/analyze@v3
26 | with:
27 | category: "/language:${{matrix.language}}"
28 |
--------------------------------------------------------------------------------
/.github/workflows/publish_pypi.yml:
--------------------------------------------------------------------------------
1 | name: Upload Python Package
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-22.04
11 | steps:
12 | - uses: actions/checkout@v4
13 | - name: Set up Python
14 | uses: actions/setup-python@v5
15 | with:
16 | python-version: "3.7"
17 | - name: Build packages
18 | run: bash foreach.sh build
19 | - name: Publish packages
20 | env:
21 | FLIT_USERNAME: __token__
22 | FLIT_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
23 | run: bash foreach.sh publish
24 |
--------------------------------------------------------------------------------
/.github/workflows/sync-jira.yml:
--------------------------------------------------------------------------------
1 | # FILE: .github/workflows/sync-jira.yml
2 | ---
3 | # This GitHub Actions workflow synchronizes GitHub issues, comments, and pull requests with Jira.
4 | # It triggers on new issues, issue comments, and on a scheduled basis.
5 | # The workflow uses a custom action to perform the synchronization with Jira (espressif/sync-jira-actions).
6 |
7 | name: 🔷 Sync to Jira
8 |
9 | run-name: >
10 | Sync to Jira -
11 | ${{ github.event_name == 'issue_comment' && 'Issue Comment' ||
12 | github.event_name == 'schedule' && 'New Pull Requests' ||
13 | github.event_name == 'issues' && 'New Issue' ||
14 | github.event_name == 'workflow_dispatch' && 'Manual Sync' }}
15 |
16 | on:
17 | issues: { types: [opened] }
18 | issue_comment: { types: [created, edited, deleted] }
19 | schedule: [cron: "0 * * * *"]
20 | workflow_dispatch:
21 | inputs:
22 | action:
23 | {
24 | description: "Action to be performed",
25 | required: true,
26 | default: "mirror-issues",
27 | }
28 | issue-numbers:
29 | {
30 | description: "Issue numbers to sync (comma-separated)",
31 | required: true,
32 | }
33 |
34 | jobs:
35 | sync-to-jira:
36 | name: >
37 | Sync to Jira -
38 | ${{ github.event_name == 'issue_comment' && 'Issue Comment' ||
39 | github.event_name == 'schedule' && 'New Pull Requests' ||
40 | github.event_name == 'issues' && 'New Issue' ||
41 | github.event_name == 'workflow_dispatch' && 'Manual Sync' }}
42 | runs-on: ubuntu-latest
43 | permissions:
44 | contents: read
45 | issues: write
46 | pull-requests: write
47 | steps:
48 | - name: Check out
49 | uses: actions/checkout@v4
50 |
51 | - name: Run synchronization to Jira
52 | uses: espressif/sync-jira-actions@v1
53 | with:
54 | cron_job: ${{ github.event_name == 'schedule' && 'true' || '' }}
55 | env:
56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57 | JIRA_PASS: ${{ secrets.JIRA_PASS }}
58 | JIRA_PROJECT: RDT
59 | JIRA_COMPONENT: pytest-embedded
60 | JIRA_URL: ${{ secrets.JIRA_URL }}
61 | JIRA_USER: ${{ secrets.JIRA_USER }}
62 |
--------------------------------------------------------------------------------
/.github/workflows/test-build-docs.yml:
--------------------------------------------------------------------------------
1 | name: Test Build Docs
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - "**.py"
7 | - "docs/**"
8 | - "*.md"
9 |
10 | jobs:
11 | test-build-docs:
12 | runs-on: ubuntu-22.04
13 | steps:
14 | - uses: actions/checkout@v4
15 | - name: Set up Python
16 | uses: actions/setup-python@v5
17 | with:
18 | python-version: "3.7"
19 | - name: Install dependencies
20 | run: |
21 | bash foreach.sh install
22 | pip install -r docs/requirements.txt
23 | - name: Test build docs
24 | run: |
25 | cd docs && make html SPHINXOPTS="-W"
26 |
--------------------------------------------------------------------------------
/.github/workflows/test-build-package.yml:
--------------------------------------------------------------------------------
1 | name: Test Build Python Packages
2 |
3 | on:
4 | pull_request:
5 |
6 | jobs:
7 | test-build-packages:
8 | runs-on: ubuntu-22.04
9 | steps:
10 | - uses: actions/checkout@v4
11 | - name: Set up Python
12 | uses: actions/setup-python@v5
13 | with:
14 | python-version: "3.7"
15 | - name: Build packages
16 | run: |
17 | bash foreach.sh build
18 |
--------------------------------------------------------------------------------
/.github/workflows/test-python.yml:
--------------------------------------------------------------------------------
1 | name: Test Python Packages
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - "**.py"
7 | - .github/workflows/test-python.yml
8 | - "!docs/**"
9 | push:
10 | branches: [main]
11 |
12 | # cancel jobs if new commit pushed
13 | concurrency:
14 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
15 | cancel-in-progress: true
16 |
17 | jobs:
18 | test-python:
19 | timeout-minutes: 40
20 | strategy:
21 | matrix:
22 | include:
23 | - python-version: "3.7"
24 | arch: "ARM64"
25 | - python-version: "3.13"
26 | arch: "X64"
27 | fail-fast: false
28 | runs-on:
29 | - self-hosted
30 | - multiboard
31 | - ${{ matrix.arch }}
32 | container:
33 | image: python:${{ matrix.python-version }}
34 | options: --privileged
35 | steps:
36 | - uses: actions/checkout@v4
37 | - name: Install dependencies
38 | run: |
39 | apt update && apt install -y socat zip
40 | pip install -U pip
41 | export PIP_EXTRA_INDEX_URL="https://dl.espressif.com/pypi"
42 | pip install cryptography --prefer-binary
43 | pip install -r requirements.txt
44 | bash foreach.sh install
45 | - name: Check ports
46 | run: ls -la /dev/ttyUSB*
47 | - name: Run Tests with coverage
48 | run: |
49 | pytest \
50 | --junitxml=pytest.xml \
51 | --cov-report=term-missing \
52 | --cov=pytest_embedded \
53 | --cov=pytest_embedded_arduino \
54 | --cov=pytest_embedded_idf \
55 | --cov=pytest_embedded_jtag \
56 | --cov=pytest_embedded_qemu \
57 | --cov=pytest_embedded_serial \
58 | --cov=pytest_embedded_serial_esp
59 | - name: Zip log files
60 | if: failure()
61 | run: |
62 | zip -r logs.zip /tmp/pytest-embedded
63 | - uses: actions/upload-artifact@v4
64 | if: failure()
65 | with:
66 | name: logs-${{ matrix.python-version }}
67 | path: logs.zip
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .coverage.*
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 | .hypothesis/
46 | .pytest_cache
47 | report.xml
48 | *.log
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # sphinx documentation
55 | docs/_build
56 |
57 | # PyBuilder
58 | target/
59 |
60 | # IPython Notebook
61 | .ipynb_checkpoints
62 |
63 | # pyenv
64 | .python-version
65 |
66 | # venv
67 | venv/
68 | .venv/
69 |
70 | # IDE
71 | .idea/
72 |
73 | # built binaries
74 | tests/fixtures/unit_test_app*/build/
75 | !tests/fixtures/**/bootloader.bin
76 | !tests/fixtures/**/partition-table.bin
77 | !tests/fixtures/**/sdkconfig.json
78 | !tests/fixtures/**/flasher_args.json
79 | !tests/fixtures/unit_test_app_*/build/**/case_tester_example.bin
80 |
81 | sdkconfig
82 | sdkconfig.old
83 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | # See https://pre-commit.com for more information
2 | # See https://pre-commit.com/hooks.html for more hooks
3 |
4 | exclude: |
5 | (?x)^(
6 | .*tests/fixtures/.+|
7 | )$
8 |
9 | repos:
10 | - repo: https://github.com/pre-commit/pre-commit-hooks
11 | rev: v5.0.0
12 | hooks:
13 | - id: trailing-whitespace
14 | - id: end-of-file-fixer
15 | - id: mixed-line-ending
16 | args: ["-f=lf"]
17 | - repo: https://github.com/astral-sh/ruff-pre-commit
18 | rev: "v0.11.6"
19 | hooks:
20 | - id: ruff
21 | args: ["--fix"]
22 | - id: ruff-format
23 | args: ["--preview"]
24 |
25 | # documentation
26 | - repo: https://github.com/sphinx-contrib/sphinx-lint
27 | rev: v1.0.0
28 | hooks:
29 | - id: sphinx-lint
30 | args: [--enable=default-role]
31 | - repo: https://github.com/hfudev/rstfmt
32 | rev: v0.1.4
33 | hooks:
34 | - id: rstfmt
35 | args: ["-w", "-1"]
36 | files: \.rst$
37 |
38 | # git commit
39 | - repo: https://github.com/espressif/conventional-precommit-linter
40 | rev: v1.10.0
41 | hooks:
42 | - id: conventional-precommit-linter
43 | stages: [commit-msg]
44 | args:
45 | ["--types=chore,change,ci,docs,feat,fix,refactor,remove,revert,bump"]
46 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yaml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | version: 2
6 |
7 | sphinx:
8 | # Path to your Sphinx configuration file.
9 | configuration: docs/conf.py
10 |
11 | build:
12 | os: ubuntu-22.04
13 | tools:
14 | python: "3.7"
15 |
16 | python:
17 | install:
18 | - requirements: docs/requirements.txt
19 | - requirements: all_local_packages.txt
20 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Workflow
4 |
5 | We use [pre-commit](https://pre-commit.com) for code formatting, and some linter checks. You can install it by
6 |
7 | ```shell
8 | $ pre-commit install -t pre-commit -t commit-msg
9 | ```
10 |
11 | We use [commitizen](https://github.com/commitizen-tools/commitizen) to auto generate the [CHANGELOG.md](./CHANGELOG.md).
12 | You don't need to install it or know anything about the tool itself, but please follow
13 | the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) rule when writing commit messages.
14 |
15 | ## Install Virtual Environment
16 |
17 | We recommend using virtual environment for development. You can create one by
18 |
19 | ```shell
20 | $ python -m venv .venv
21 | $ source .venv/bin/activate
22 | ```
23 |
24 | ## Install Dependencies
25 |
26 | We recommend to install the local subpackages in editable mode.
27 |
28 | ```shell
29 | $ pip install -r requirements.txt
30 | $ bash foreach.sh install-editable
31 | ```
32 |
33 | ## Running Tests
34 |
35 | By default, all tests under all plugins would be run.
36 |
37 | ```shell
38 | $ # export DONT_SKIP_JTAG_TESTS=1 (when you have a jtag connection)
39 | $ pytest
40 | ```
41 |
42 | ## Writing Tests
43 |
44 | Basically we're following
45 | the [official documentation](https://docs.pytest.org/en/stable/writing_plugins.html#testing-plugins). You could also
46 | refer to the tests under each plugin.
47 |
48 | ## Building Docs
49 |
50 | We use `sphinx` with espressif theme to build the docs. The docstring is written in `Google` style. Here's an [example](https://www.sphinx-doc.org/en/master/usage/extensions/example_google.html).
51 |
52 | For building locally, you need to install the dependencies first.
53 |
54 | ```shell
55 | $ pip install -r docs/requirements.txt
56 | ```
57 |
58 | Then you can build the docs by
59 |
60 | ```shell
61 | $ cd docs
62 | $ make html
63 | ```
64 |
65 | For documentation preview, you may use any browser you prefer. The executable has to be searchable in `PATH`. For example we're using firefox here.
66 |
67 | ```shell
68 | $ firefox _build/html/index.html
69 | ```
70 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This repository contains files licensed under multiple licenses. The licensing terms are as follows:
2 |
3 | 1. Files in the examples/ folder
4 | The files within the examples/ folder are licensed under the Creative Commons Zero v1.0 Universal (CC0-1.0) License: https://creativecommons.org/publicdomain/zero/1.0/
5 |
6 | 2. Files in Python packages
7 | Each Python package in this repository is licensed separately. Please refer to the LICENSE file located in each package for specific licensing terms.
8 |
9 | 3. All other files
10 | Unless otherwise specified, all other files in this repository are licensed under the MIT License:
11 |
12 | MIT License
13 |
14 | Copyright (c) 2024 Espressif Systems (Shanghai) Co. Ltd.
15 |
16 | Permission is hereby granted, free of charge, to any person obtaining a copy
17 | of this software and associated documentation files (the "Software"), to deal
18 | in the Software without restriction, including without limitation the rights
19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20 | copies of the Software, and to permit persons to whom the Software is
21 | furnished to do so, subject to the following conditions:
22 |
23 | The above copyright notice and this permission notice shall be included in all
24 | copies or substantial portions of the Software.
25 |
26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32 | SOFTWARE.
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pytest-embedded
2 |
3 | [](https://docs.espressif.com/projects/pytest-embedded/en/latest/?badge=latest) 
4 |
5 | A pytest plugin that has multiple services available for various functionalities. Designed for embedded testing.
6 |
7 | ## Installation
8 |
9 | [](https://pypi.org/project/pytest-embedded/)
10 | [](https://pypi.org/project/pytest-embedded-serial/)
11 | [](https://pypi.org/project/pytest-embedded-serial-esp/)
12 | [](https://pypi.org/project/pytest-embedded-idf/)
13 | [](https://pypi.org/project/pytest-embedded-qemu/)
14 | [](https://pypi.org/project/pytest-embedded-arduino/)
15 | [](https://pypi.org/project/pytest-embedded-wokwi/)
16 | [](https://pypi.org/project/pytest-embedded-nuttx/)
17 |
18 | Packages under this repo mainly use semantic versioning. Sometimes a bug fix version may contain some non-breaking new features as well.
19 |
20 | It is recommended to use `~=1.0` to get rid of breaking changes, and use the latest new features. For example,
21 |
22 | ```shell
23 | pip install -U pytest-embedded~=1.0
24 | ```
25 |
26 | ## Quickstart
27 |
28 | - `pip install -U pytest-embedded~=1.0`
29 | - Create a file `test_basic.py`
30 |
31 | ```python
32 | from pytest_embedded import Dut
33 |
34 |
35 | def test_basic_expect(redirect, dut: Dut):
36 | with redirect():
37 | print('this would be redirected')
38 |
39 | dut.expect('this')
40 | dut.expect_exact('would')
41 | dut.expect('[be]{2}')
42 | dut.expect_exact('redirected')
43 | ```
44 |
45 | - Run the test with `pytest`, the result would be like:
46 |
47 | ```shell
48 | collected 1 item
49 |
50 | test_basic.py . [100%]
51 |
52 | ============================= 1 passed in 0.01s =============================
53 | ```
54 |
55 | - if run with `pytest -s`, the output would be as follows:
56 |
57 | ```shell
58 | collected 1 item
59 |
60 | test_basic.py 2022-01-01 12:34:56 this would be redirected
61 | .
62 |
63 | ============================= 1 passed in 0.01s =============================
64 | ```
65 |
66 | The `print` line is also duplicated to console output.
67 |
68 | ## Extra Services
69 |
70 | You can activate more services with `pytest --embedded-services service[,service]` to enable extra fixtures and functionalities. These services are provided by several optional dependencies. You can install them via `pip` as well.
71 |
72 | Available services:
73 |
74 | - `serial`: serial port utilities.
75 | - `esp`: auto-detect target/port by [esptool](https://github.com/espressif/esptool).
76 | - `idf`: auto-detect more app info with [ESP-IDF](https://github.com/espressif/esp-idf) specific rules, auto-flash the binary into the target.
77 | - `jtag`: openocd/gdb utilities
78 | - `qemu`: running test cases on QEMU instead of the real target.
79 | - `arduino`: auto-detect more app info with [arduino](https://github.com/arduino/Arduino) specific rules, auto-flash the binary into the target.
80 | - `wokwi`: running test cases with [Wokwi](https://wokwi.com/) instead of the real target.
81 | - `nuttx`: service for [nuttx](https://nuttx.apache.org/) project, optionally with espressif devices.
82 |
83 | ## Resources
84 |
85 | - Documentation is hosted at [https://docs.espressif.com/projects/pytest-embedded/en/latest/](https://docs.espressif.com/projects/pytest-embedded/en/latest/)
86 | - More examples under [examples](https://github.com/espressif/pytest-embedded/tree/main/examples)
87 |
--------------------------------------------------------------------------------
/all_local_packages.txt:
--------------------------------------------------------------------------------
1 | -e ./pytest-embedded/
2 | -e ./pytest-embedded-serial/
3 | -e ./pytest-embedded-serial-esp/
4 | -e ./pytest-embedded-idf/
5 | -e ./pytest-embedded-jtag/
6 | -e ./pytest-embedded-qemu/
7 | -e ./pytest-embedded-arduino/
8 |
--------------------------------------------------------------------------------
/conftest.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | import sys
4 | import textwrap
5 | from typing import List, Pattern
6 |
7 | import pytest
8 | from _pytest.config import Config
9 | from _pytest.fixtures import FixtureRequest
10 | from _pytest.legacypath import Testdir
11 |
12 | pytest_plugins = [
13 | 'pytester',
14 | ]
15 |
16 |
17 | @pytest.fixture(autouse=True)
18 | def copy_fixtures(testdir: Testdir):
19 | fixture_dir = os.path.join(os.path.dirname(__file__), 'tests', 'fixtures')
20 | for item in os.listdir(fixture_dir):
21 | if item == '__pycache__':
22 | continue
23 |
24 | if os.path.isfile(os.path.join(fixture_dir, item)):
25 | shutil.copy(os.path.join(fixture_dir, item), os.path.join(str(testdir.tmpdir), item))
26 | else:
27 | shutil.copytree(os.path.join(fixture_dir, item), os.path.join(str(testdir.tmpdir), item))
28 |
29 | testdir.makepyprojecttoml(
30 | textwrap.dedent("""
31 | [tool.pytest.ini_options]
32 | filterwarnings = "ignore:FutureWarning"
33 | junit_family = "xunit1"
34 | """)
35 | )
36 |
37 | yield
38 |
39 |
40 | @pytest.fixture()
41 | def copy_mock_esp_idf(testdir: Testdir):
42 | esp_idf = os.path.join(os.path.dirname(__file__), 'tests', 'esp-idf')
43 | for item in os.listdir(esp_idf):
44 | if os.path.isfile(os.path.join(esp_idf, item)):
45 | shutil.copy(os.path.join(esp_idf, item), os.path.join(str(testdir.tmpdir), item))
46 | else:
47 | shutil.copytree(os.path.join(esp_idf, item), os.path.join(str(testdir.tmpdir), item))
48 |
49 | yield
50 |
51 |
52 | @pytest.fixture(autouse=True)
53 | def cache_file_remove(cache_dir):
54 | yield
55 | _cache_file_path = os.path.join(cache_dir, 'port_target_cache')
56 | if os.path.exists(_cache_file_path):
57 | os.remove(_cache_file_path)
58 |
59 |
60 | @pytest.fixture
61 | def first_index_of_messages():
62 | def _fake(_pattern: Pattern, _messages: List[str], _start: int = 0) -> int:
63 | for i, _message in enumerate(_messages):
64 | if _pattern.match(_message) and i >= _start:
65 | return i
66 |
67 | raise AssertionError(f'Not found {_pattern.pattern}')
68 |
69 | return _fake
70 |
71 |
72 | @pytest.fixture(autouse=True)
73 | def temp_disable_packages(monkeypatch, request: FixtureRequest):
74 | temp_marker = request.node.get_closest_marker('temp_disable_packages')
75 | if not temp_marker:
76 | return
77 |
78 | packages = temp_marker.args
79 | for name in list(sys.modules):
80 | if name in packages or name.split('.')[0] in packages:
81 | monkeypatch.setitem(sys.modules, name, None)
82 |
83 |
84 | def pytest_configure(config: Config) -> None:
85 | for name, description in {'temp_disable_packages': 'disable packages in function scope'}.items():
86 | config.addinivalue_line('markers', f'{name}: {description}')
87 |
--------------------------------------------------------------------------------
/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ```{include} ../CHANGELOG.md
2 | ```
3 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ```{include} ../CONTRIBUTING.md
2 | ```
3 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/_static/espressif-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
78 |
--------------------------------------------------------------------------------
/docs/_static/theme_overrides.css:
--------------------------------------------------------------------------------
1 | /* override table width restrictions */
2 | @media screen and (min-width: 767px) {
3 |
4 | .wy-table-responsive table td {
5 | /* !important prevents the common CSS stylesheets from overriding
6 | this as on RTD they are loaded after this stylesheet */
7 | white-space: normal !important;
8 | }
9 |
10 | .wy-table-responsive {
11 | overflow: visible !important;
12 | }
13 | }
14 |
15 | .wy-side-nav-search {
16 | background-color: #e3e3e3 !important;
17 | }
18 |
19 | .wy-side-nav-search input[type=text] {
20 | border-radius: 0px !important;
21 | border-color: #333333 !important;
22 | }
23 |
24 | .icon-home {
25 | color: #333333 !important;
26 | }
27 |
28 | .icon-home:hover {
29 | background-color: #d6d6d6 !important;
30 | }
31 |
32 | .version {
33 | color: #000000 !important;
34 | }
35 |
36 | a:hover {
37 | color: #bd2c2a !important;
38 | }
39 |
40 | .logo {
41 | width: 240px !important;
42 | }
43 |
44 | .highlight .c1 {
45 | color: #008080;
46 | }
47 |
48 | .bolditalics {
49 | font-weight: bold;
50 | font-style: italic;
51 | }
52 |
--------------------------------------------------------------------------------
/docs/_templates/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "!layout.html" %}
2 |
3 | {% block document %}
4 |
5 | {{ super() }}
6 |
7 | {% endblock %}
8 |
--------------------------------------------------------------------------------
/docs/apis/pytest-embedded-arduino.rst:
--------------------------------------------------------------------------------
1 | #########################
2 | pytest-embedded-arduino
3 | #########################
4 |
5 | .. automodule:: pytest_embedded_arduino.app
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 |
10 | .. automodule:: pytest_embedded_arduino.serial
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
--------------------------------------------------------------------------------
/docs/apis/pytest-embedded-idf.rst:
--------------------------------------------------------------------------------
1 | #####################
2 | pytest-embedded-idf
3 | #####################
4 |
5 | .. automodule:: pytest_embedded_idf.app
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 |
10 | .. automodule:: pytest_embedded_idf.dut
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | .. automodule:: pytest_embedded_idf.linux
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | .. automodule:: pytest_embedded_idf.serial
21 | :members:
22 | :undoc-members:
23 | :show-inheritance:
24 |
25 | .. automodule:: pytest_embedded_idf.unity_tester
26 | :members:
27 | :undoc-members:
28 | :show-inheritance:
29 |
--------------------------------------------------------------------------------
/docs/apis/pytest-embedded-jtag.rst:
--------------------------------------------------------------------------------
1 | ######################
2 | pytest-embedded-jtag
3 | ######################
4 |
5 | .. automodule:: pytest_embedded_jtag.gdb
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 |
10 | .. automodule:: pytest_embedded_jtag.openocd
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
--------------------------------------------------------------------------------
/docs/apis/pytest-embedded-nuttx.rst:
--------------------------------------------------------------------------------
1 | #######################
2 | pytest-embedded-nuttx
3 | #######################
4 |
5 | .. automodule:: pytest_embedded_nuttx.app
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 |
10 | .. automodule:: pytest_embedded_nuttx.dut
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | .. automodule:: pytest_embedded_nuttx.serial
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/apis/pytest-embedded-qemu.rst:
--------------------------------------------------------------------------------
1 | ######################
2 | pytest-embedded-qemu
3 | ######################
4 |
5 | .. automodule:: pytest_embedded_qemu.app
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 |
10 | .. automodule:: pytest_embedded_qemu.dut
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | .. automodule:: pytest_embedded_qemu.qemu
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
--------------------------------------------------------------------------------
/docs/apis/pytest-embedded-serial-esp.rst:
--------------------------------------------------------------------------------
1 | ############################
2 | pytest-embedded-serial-esp
3 | ############################
4 |
5 | .. automodule:: pytest_embedded_serial_esp.serial
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 |
--------------------------------------------------------------------------------
/docs/apis/pytest-embedded-serial.rst:
--------------------------------------------------------------------------------
1 | ########################
2 | pytest-embedded-serial
3 | ########################
4 |
5 | .. automodule:: pytest_embedded_serial.dut
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 |
10 | .. automodule:: pytest_embedded_serial.serial
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
--------------------------------------------------------------------------------
/docs/apis/pytest-embedded-wokwi.rst:
--------------------------------------------------------------------------------
1 | #######################
2 | pytest-embedded-wokwi
3 | #######################
4 |
5 | .. automodule:: pytest_embedded_wokwi.arduino
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 |
10 | .. automodule:: pytest_embedded_wokwi.dut
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | .. automodule:: pytest_embedded_wokwi.idf
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | .. automodule:: pytest_embedded_wokwi.wokwi_cli
21 | :members:
22 | :undoc-members:
23 | :show-inheritance:
24 |
--------------------------------------------------------------------------------
/docs/apis/pytest-embedded.rst:
--------------------------------------------------------------------------------
1 | #################
2 | pytest-embedded
3 | #################
4 |
5 | .. automodule:: pytest_embedded.app
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 |
10 | .. automodule:: pytest_embedded.dut
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | .. automodule:: pytest_embedded.dut_factory
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | .. automodule:: pytest_embedded.log
21 | :members:
22 | :undoc-members:
23 | :show-inheritance:
24 |
25 | .. automodule:: pytest_embedded.plugin
26 | :members:
27 | :undoc-members:
28 | :show-inheritance:
29 |
30 | .. automodule:: pytest_embedded.unity
31 | :members:
32 | :undoc-members:
33 | :show-inheritance:
34 |
35 | .. automodule:: pytest_embedded.utils
36 | :members:
37 | :undoc-members:
38 | :show-inheritance:
39 |
--------------------------------------------------------------------------------
/docs/concepts/key-concepts.md:
--------------------------------------------------------------------------------
1 | # Key Concepts
2 |
3 | ## Fixtures
4 |
5 | Each test case would initialize a few fixtures. The most important fixtures are:
6 |
7 | - `msg_queue`, a message queue. There's a background listener process to read all the messages from this queue, log them to the terminal with an optional timestamp, and record them to the pexpect process.
8 | - `pexpect_proc`, a pexpect process, that could run `pexpect.expect()` for testing purposes.
9 | - `app`, the built binary
10 | - `dut`, Device Under Test (DUT)
11 |
12 | A DUT would contain several daemon processes/threads. The output of each of them would be redirected to the `msg_queue` fixture.
13 |
14 | ```{eval-rst}
15 | .. note::
16 |
17 | You may redirect any output to the ``msg_queue`` fixture by ``contextlib.redirect_stdout``.
18 |
19 | .. code-block:: python
20 |
21 | import contextlib
22 |
23 | def test_redirect(dut, msg_queue):
24 | with contextlib.redirect_stdout(msg_queue):
25 | print('will be redirected')
26 |
27 | dut.expect_exact('redirected')
28 |
29 | Or you may redirect the output from a fixture `redirect`
30 |
31 | .. code-block:: python
32 |
33 | def test_redirect(dut, msg_queue, redirect):
34 | with redirect():
35 | print('will also be redirected')
36 |
37 | dut.expect_exact('redirected')
38 |
39 | ```
40 |
41 | You can run `pytest --fixtures` to get all the fixtures defined with `pytest-embedded`. They are under the section `fixtures defined from pytest_embedded.plugin`.
42 |
43 | ## Parametrization
44 |
45 | All the CLI options support parametrization via `indirect=True`. Parametrization is a feature provided by `pytest`, please refer to [Parametrizing tests](https://docs.pytest.org/en/latest/example/parametrize.html) for its documentation.
46 |
47 | For example, running shell command `pytest` with the test script:
48 |
49 | ```python
50 | @pytest.mark.parametrize(
51 | 'embedded_services, app_path',
52 | [
53 | ('idf', app_path_1),
54 | ('idf', app_path_2),
55 | ],
56 | indirect=True,
57 | )
58 | def test_serial_tcp(dut):
59 | assert dut.app.target == 'esp32'
60 | dut.expect('Restart now')
61 | ```
62 |
63 | is equivalent to running two shell commands `pytest --embedded-services idf --app-path ` and `pytest --embedded-services idf --app-path ` with the test script:
64 |
65 | ```python
66 | def test_serial_tcp(dut):
67 | assert dut.app.target == 'esp32'
68 | dut.expect('Restart now')
69 | ```
70 |
71 | ## Services
72 |
73 | You can activate more services with `pytest --embedded-services service[,service]` to enable extra fixtures and functionalities. These services are provided by several optional dependencies. You can install them via `pip` as well.
74 |
75 | Available services:
76 |
77 | - `serial`: serial port utilities.
78 | - `esp`: auto-detect target/port by [esptool](https://github.com/espressif/esptool).
79 | - `idf`: auto-detect more app info with [ESP-IDF](https://github.com/espressif/esp-idf) specific rules, auto-flash the binary into the target.
80 | - `jtag`: openocd/gdb utilities.
81 | - `qemu`: running test cases on QEMU instead of the real target.
82 | - `arduino`: auto-detect more app info with [arduino](https://github.com/arduino/Arduino) specific rules, auto-flash the binary into the target.
83 | - `wokwi`: running test cases with [Wokwi](https://wokwi.com/) instead of the real target.
84 |
85 | ## Multi DUTs
86 |
87 | Sometimes we need multi DUTs while testing, e.g., master-slave or mesh testing.
88 |
89 | Here are a few examples of how to enable this. For detailed information, please refer to the `embedded` group of the `pytest --help`.
90 |
91 | ### Enable multi DUTs by specifying `--count`
92 |
93 | After you enabled the multi-dut mode, all the fixtures would be a tuple with instances. Each instance inside the tuple would be independent. For parametrization, each configuration will use `|` as a separator for each instance.
94 |
95 | For example, running shell command:
96 |
97 | ```shell
98 | pytest \
99 | --embedded-services serial|serial \
100 | --count 2 \
101 | --app-path |
102 | ```
103 |
104 | would enable 2 DUTs with `serial` service. `app` would be a tuple of 2 `App` instances, and `dut` would be a tuple of 2 `Dut` Instances.
105 |
106 | You can test with:
107 |
108 | ```python
109 | def test(dut):
110 | master = dut[0]
111 | slave = dut[1]
112 |
113 | master.expect('sent')
114 | slave.expect('received')
115 | ```
116 |
117 | ### Specify once when applying to all DUTs
118 |
119 | If all DUTs share the same configuration value, you can specify only once.
120 |
121 | ```shell
122 | pytest \
123 | --embedded-services serial \
124 | --count 2 \
125 | --app-path |
126 | ```
127 |
128 | `--embedded-services serial` would apply to all DUTs
129 |
130 | ### Vacant Value if it's Useless
131 |
132 | Sometimes one option is only useful when enabling specific services. You can set a vacant value if this config is only useful for certain DUTs.
133 |
134 | ```shell
135 | pytest \
136 | --embedded-services qemu|serial \
137 | --count 2 \
138 | --app-path | \
139 | --qemu-cli-args "|" \
140 | --port "|"
141 | ```
142 |
143 | `--qemu-cli-args` would apply to the first DUT with `qemu` service and `--port` would apply to the second DUT with `serial` service.
144 |
145 | ## Logging
146 |
147 | `pytest-embedded` print all the DUT output with the timestamp. If you want to remove the timestamp, please run pytest with `pytest --with-timestamp n` to disable this feature.
148 |
149 | By default, `pytest` would swallow the stdout. If you want to check the live output, please run pytest with `pytest -s`.
150 |
--------------------------------------------------------------------------------
/docs/concepts/services.md:
--------------------------------------------------------------------------------
1 | # Services
2 |
3 | ## Fixtures
4 |
5 | Please refer to instructions under the "fixtures defined from pytest_embedded.plugin" chapter of the output of `pytest --fixtures`.
6 |
7 | ## CLI Options
8 |
9 | Please refer to the instructions under the "embedded" or "embedded-[SERVICE]" groups of the output of `pytest --help`.
10 |
11 | ## Dependency Graph for Services
12 |
13 | The arrow points from the dependent service to the service it depends on. For example, `pytest-embedded-serial-esp` depends on `pytest-embedded-serial`.
14 |
15 | ```{mermaid}
16 | graph LR
17 | pytest-embedded-serial
18 |
19 | pytest-embedded-nuttx --> pytest-embedded-serial
20 | pytest-embedded-nuttx -->|optional, support test on espressif chips| pytest-embedded-serial-esp
21 | pytest-embedded-nuttx -->|optional, support test on qemu| pytest-embedded-qemu
22 |
23 | pytest-embedded-serial-esp --> pytest-embedded-serial
24 |
25 | pytest-embedded-jtag --> pytest-embedded-serial
26 |
27 | pytest-embedded-idf -->|optional, support test on espressif chips| pytest-embedded-serial-esp
28 | pytest-embedded-idf -->|optional, support test on qemu| pytest-embedded-qemu
29 | pytest-embedded-idf -->|optional, support test on wokwi| pytest-embedded-wokwi
30 |
31 | pytest-embedded-arduino -->|optional, support test on espressif chips| pytest-embedded-serial-esp
32 | pytest-embedded-arduino -->|optional, support test on qemu| pytest-embedded-qemu
33 | pytest-embedded-arduino -->|optional, support test on wokwi| pytest-embedded-wokwi
34 | ```
35 |
36 | ## Supported Services
37 |
38 | Activate a service would enable a set of fixtures or add some extra functionalities to a few fixtures.
39 |
40 | ```{include} ../../pytest-embedded-serial/README.md
41 | ```
42 |
43 | ```{include} ../../pytest-embedded-serial-esp/README.md
44 | ```
45 |
46 | ```{include} ../../pytest-embedded-idf/README.md
47 | ```
48 |
49 | ```{include} ../../pytest-embedded-jtag/README.md
50 | ```
51 |
52 | ```{include} ../../pytest-embedded-qemu/README.md
53 | ```
54 |
55 | ```{include} ../../pytest-embedded-arduino/README.md
56 | ```
57 |
58 | ```{include} ../../pytest-embedded-wokwi/README.md
59 | ```
60 |
61 | ```{include} ../../pytest-embedded-nuttx/README.md
62 | ```
63 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # For the full list of built-in configuration values, see the documentation:
4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
5 |
6 | from datetime import datetime
7 |
8 | # -- Project information -----------------------------------------------------
9 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
10 |
11 | project = 'pytest-embedded'
12 | project_homepage = 'https://github.com/espressif/pytest-embedded'
13 | copyright = f'2023-{datetime.now().year}, Espressif Systems (Shanghai) Co., Ltd.' # noqa: A001
14 | author = 'Fu Hanxi'
15 | version = '1.x'
16 | release = '1.x'
17 |
18 | # -- General configuration ---------------------------------------------------
19 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
20 |
21 | extensions = [
22 | 'myst_parser',
23 | 'sphinx.ext.autodoc',
24 | 'sphinx.ext.napoleon',
25 | 'sphinx.ext.intersphinx',
26 | 'sphinx_copybutton',
27 | 'sphinx_tabs.tabs',
28 | 'sphinxcontrib.mermaid',
29 | ]
30 |
31 | templates_path = ['_templates']
32 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
33 |
34 | # -- Options for HTML output -------------------------------------------------
35 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
36 |
37 | html_css_files = ['theme_overrides.css']
38 | html_logo = '_static/espressif-logo.svg'
39 | html_static_path = ['_static']
40 | html_theme = 'sphinx_rtd_theme'
41 |
42 | # -- Options for intersphinx extension ---------------------------------------
43 |
44 | intersphinx_mapping = {
45 | 'python': ('https://docs.python.org/3', None),
46 | 'pytest': ('https://docs.pytest.org/en/stable', None),
47 | 'pexpect': ('https://pexpect.readthedocs.io/en/stable', None),
48 | }
49 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | #########################################
2 | pytest-embedded |version| Documentation
3 | #########################################
4 |
5 | .. include:: ../README.md
6 | :parser: myst_parser.sphinx_
7 |
8 | .. toctree::
9 | :caption: Key Concepts
10 | :glob:
11 | :hidden:
12 |
13 | concepts/*
14 |
15 | .. toctree::
16 | :caption: Usages
17 | :glob:
18 | :hidden:
19 |
20 | usages/*
21 |
22 | .. toctree::
23 | :caption: API Reference
24 | :hidden:
25 | :glob:
26 |
27 | apis/*
28 |
29 | .. toctree::
30 | :caption: Other Information
31 | :hidden:
32 |
33 | CHANGELOG
34 | CONTRIBUTING
35 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | -r ../requirements.txt
2 |
3 | # theme
4 | sphinx-rtd-theme
5 |
6 | # extensions
7 | myst-parser # markdown support
8 | sphinx-tabs # tabs support
9 | sphinx_copybutton # copy button
10 | sphinxcontrib-mermaid # mermaid graph support
11 | sphinxcontrib-napoleon # google style docstring support
12 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 |
3 | # idf related
4 | sdkconfig
5 | sdkconfig.old
6 |
7 | # qemu image
8 | flash_image.bin
9 |
--------------------------------------------------------------------------------
/examples/arduino/README.md:
--------------------------------------------------------------------------------
1 | # Arduino Examples
2 |
3 | This folder contains basic examples that show how to use the Arduino service.
4 |
5 | ## Prerequisites
6 |
7 | 1. Connect to the board
8 | 2. Make sure the necessary packages are installed, at least:
9 | - `pytest_embedded`
10 | - `pytest_embedded_serial_esp`
11 | - `pytest_embedded_arduino`
12 | 3. Build an example. For this you can use `arduino-cli`.
13 | ```shell
14 | $ arduino-cli compile --build-path hello_world/build --fqbn espressif:esp32:esp32:PartitionScheme=huge_app hello_world`
15 | ```
16 | The above command is supposed to be run from the directory `examples/arduino`,
17 | adapt the build folder appropriately when run from a different location.
18 | On success, this will create a `build` directory under the `hello_world`
19 | example.
20 |
21 | ### Run the tests
22 |
23 | ```shell
24 | $ pytest
25 | ```
26 |
27 | This command can be run from the same location as earlier, i.e.
28 | `examples/arduino`. It can also be run from the top level directory, with:
29 |
30 | ```shell
31 | $ pytest examples/arduino -k test_hello_arduino
32 | ```
33 |
34 | This will parse the `build` directory created earlier, flash the chip and
35 | expect the `Hello Arduino!` text to be printed.
36 |
--------------------------------------------------------------------------------
/examples/arduino/hello_world/hello_world.ino:
--------------------------------------------------------------------------------
1 | void setup(){
2 | // Open serial communications and wait for port to open:
3 | Serial.begin(115200);
4 | while (!Serial) {
5 | ;
6 | }
7 |
8 | Serial.println("Hello Arduino!");
9 | }
10 |
11 | void loop(){
12 | }
13 |
--------------------------------------------------------------------------------
/examples/arduino/hello_world/test_hello_world.py:
--------------------------------------------------------------------------------
1 | def test_hello_arduino(dut):
2 | dut.expect('Hello Arduino!')
3 |
--------------------------------------------------------------------------------
/examples/arduino/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --embedded-services esp,arduino
3 |
4 | # log related
5 | log_cli = True
6 | log_cli_level = INFO
7 | log_cli_format = %(asctime)s %(levelname)s %(message)s
8 | log_cli_date_format = %Y-%m-%d %H:%M:%S
9 |
10 | log_file = test.log
11 | log_file_level = INFO
12 | log_file_format = %(asctime)s %(levelname)s %(message)s
13 | log_file_date_format = %Y-%m-%d %H:%M:%S
14 |
--------------------------------------------------------------------------------
/examples/echo/README.md:
--------------------------------------------------------------------------------
1 | # Basic DUT echo example
2 |
3 | This example shows the basics of working with a DUT.
4 |
5 | `dut` fixture in this example is provided by `pytest-embedded-serial` plugin. This fixture wraps a port implemented by `pyserial`. This is normally a serial port, however `pyserial` also supports TCP, RFC2217, loopback ports via [URL handlers](https://pyserial.readthedocs.io/en/latest/url_handlers.html).
6 |
7 | In this example, `loopback://` port is used, which means that any data sent to the DUT will be received back. The port is specified in [pytest.ini](pytest.ini).
8 |
9 | You could also try this example with a real serial port (`--port /dev/ttyUSB0`), connecting RX and TX lines. The `--port` argument specified on the command line overrides the same argument from pytest.ini.
10 |
11 | ## Prerequisites
12 |
13 | 1. Install following packages
14 | - `pytest_embedded`
15 | - `pytest_embedded_serial`
16 |
17 | ## Test Steps
18 |
19 | ```shell
20 | $ pytest
21 | ```
22 |
--------------------------------------------------------------------------------
/examples/echo/conftest.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(autouse=True)
7 | def open_tcp_port():
8 | proc = subprocess.Popen('socat TCP4-LISTEN:9876,fork EXEC:cat', shell=True)
9 | yield
10 | proc.terminate()
11 |
--------------------------------------------------------------------------------
/examples/echo/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --embedded-services serial -s
3 |
4 | # log related
5 | log_cli = True
6 | log_cli_level = INFO
7 | log_cli_format = %(asctime)s %(levelname)s %(message)s
8 | log_cli_date_format = %Y-%m-%d %H:%M:%S
9 |
--------------------------------------------------------------------------------
/examples/echo/test_echo.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.mark.parametrize(
5 | 'port',
6 | ['socket://localhost:9876'],
7 | indirect=True,
8 | )
9 | def test_echo_tcp(dut):
10 | dut.write(b'Hello, DUT!')
11 | dut.expect('Hello, DUT!') # will decode automatically
12 |
--------------------------------------------------------------------------------
/examples/esp-idf-qemu/README.md:
--------------------------------------------------------------------------------
1 | # ESP-IDF QEMU Examples
2 |
3 | The example shows how to create idf app qemu flash image automatically and run this with qemu.
4 |
5 | The example code comes from ESP-IDF v5.0
6 |
7 | ## Prerequisites
8 |
9 | 1. Prepare QEMU program which supports xtensa, name it `qemu-system-xtensa` and add its parent directory into `$PATH`
10 | 2. Install following packages
11 | - `pytest_embedded`
12 | - `pytest_embedded_idf`
13 | - `pytest_embedded_qemu`
14 | - `esptool` (for sending commands to QEMU via socket)
15 | 3. cd into the app folder
16 | 4. run `idf.py build` under the apps you want to test
17 |
18 | ## Test Steps
19 |
20 | ```shell
21 | $ pytest
22 | ```
23 |
24 | QEMU flash image would be created automatically if not exists
25 |
--------------------------------------------------------------------------------
/examples/esp-idf-qemu/hello_world/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # The following lines of boilerplate have to be in your project's
2 | # CMakeLists in this exact order for cmake to work correctly
3 | cmake_minimum_required(VERSION 3.16)
4 |
5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6 | project(hello_world)
7 |
--------------------------------------------------------------------------------
/examples/esp-idf-qemu/hello_world/main/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | idf_component_register(SRCS "hello_world_main.c"
2 | INCLUDE_DIRS "")
3 |
4 | target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
5 |
--------------------------------------------------------------------------------
/examples/esp-idf-qemu/hello_world/main/hello_world_main.c:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
3 | *
4 | * SPDX-License-Identifier: CC0-1.0
5 | */
6 |
7 | #include
8 | #include "sdkconfig.h"
9 | #include "freertos/FreeRTOS.h"
10 | #include "freertos/task.h"
11 | #include "esp_chip_info.h"
12 | #include "esp_flash.h"
13 |
14 | void app_main(void)
15 | {
16 | printf("Hello world!\n");
17 |
18 | /* Print chip information */
19 | esp_chip_info_t chip_info;
20 | uint32_t flash_size;
21 | esp_chip_info(&chip_info);
22 | printf("This is %s chip with %d CPU core(s), WiFi%s%s, ",
23 | CONFIG_IDF_TARGET,
24 | chip_info.cores,
25 | (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
26 | (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
27 |
28 | printf("silicon revision %d, ", chip_info.revision);
29 | if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
30 | printf("Get flash size failed");
31 | return;
32 | }
33 |
34 | printf("%uMB %s flash\n", flash_size / (1024 * 1024),
35 | (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
36 |
37 | printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());
38 |
39 | for (int i = 10; i >= 0; i--) {
40 | printf("Restarting in %d seconds...\n", i);
41 | vTaskDelay(1000 / portTICK_PERIOD_MS);
42 | }
43 | printf("Restarting now.\n");
44 | fflush(stdout);
45 | esp_restart();
46 | }
47 |
--------------------------------------------------------------------------------
/examples/esp-idf-qemu/hello_world/test_hello_world.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from pytest_embedded.log import live_print_call
3 |
4 |
5 | def test_hello_world(dut):
6 | dut.expect('Hello world!')
7 | dut.expect('Restarting')
8 |
9 |
10 | @pytest.mark.parametrize(
11 | 'qemu_extra_args',
12 | [
13 | '"-serial tcp::5555,server,nowait -global driver=esp32.gpio,property=strap_mode,value=0x0f"',
14 | ],
15 | indirect=True,
16 | )
17 | def test_serial_tcp(dut, redirect):
18 | with redirect():
19 | live_print_call('esptool.py --port socket://localhost:5555 --no-stub --chip esp32 read_mac', shell=True)
20 |
21 | dut.expect('Serial port socket://localhost:5555') # expect from what esptool.py printed to sys.stdout
22 | dut.expect('MAC:')
23 | dut.expect('Hard resetting via RTS pin...')
24 |
--------------------------------------------------------------------------------
/examples/esp-idf-qemu/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --embedded-services idf,qemu -s
3 |
4 | # log related
5 | log_cli = True
6 | log_cli_level = INFO
7 | log_cli_format = %(asctime)s %(levelname)s %(message)s
8 | log_cli_date_format = %Y-%m-%d %H:%M:%S
9 |
10 | log_file = test.log
11 | log_file_level = INFO
12 | log_file_format = %(asctime)s %(levelname)s %(message)s
13 | log_file_date_format = %Y-%m-%d %H:%M:%S
14 |
--------------------------------------------------------------------------------
/examples/esp-idf/README.md:
--------------------------------------------------------------------------------
1 | # ESP-IDF Examples
2 |
3 | This example shows:
4 | 1. how `pytest_embedded_serial_esp` auto-detect chip target and port
5 | 2. how `pytest_embedded_idf` auto flash the app into the target chip
6 |
7 | The example code comes from ESP-IDF v5.0
8 |
9 | ## Prerequisites
10 |
11 | 1. Connect to the target chips
12 | 2. Install following packages
13 | - `pytest_embedded`
14 | - `pytest_embedded_serial_esp`
15 | - `pytest_embedded_idf`
16 | 3. cd into the app folder
17 | 4. run `idf.py build` under the apps you want to test
18 |
19 | ## Test Steps
20 |
21 | ```shell
22 | $ pytest
23 | ```
24 |
--------------------------------------------------------------------------------
/examples/esp-idf/hello_world/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # The following lines of boilerplate have to be in your project's
2 | # CMakeLists in this exact order for cmake to work correctly
3 | cmake_minimum_required(VERSION 3.16)
4 |
5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6 | project(hello_world)
7 |
--------------------------------------------------------------------------------
/examples/esp-idf/hello_world/main/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | idf_component_register(SRCS "hello_world_main.c"
2 | INCLUDE_DIRS "")
3 |
4 | target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
5 |
--------------------------------------------------------------------------------
/examples/esp-idf/hello_world/main/hello_world_main.c:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
3 | *
4 | * SPDX-License-Identifier: CC0-1.0
5 | */
6 |
7 | #include
8 | #include "sdkconfig.h"
9 | #include "freertos/FreeRTOS.h"
10 | #include "freertos/task.h"
11 | #include "esp_chip_info.h"
12 | #include "esp_flash.h"
13 |
14 | void app_main(void)
15 | {
16 | printf("Hello world!\n");
17 |
18 | /* Print chip information */
19 | esp_chip_info_t chip_info;
20 | uint32_t flash_size;
21 | esp_chip_info(&chip_info);
22 | printf("This is %s chip with %d CPU core(s), WiFi%s%s, ",
23 | CONFIG_IDF_TARGET,
24 | chip_info.cores,
25 | (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
26 | (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
27 |
28 | printf("silicon revision %d, ", chip_info.revision);
29 | if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
30 | printf("Get flash size failed");
31 | return;
32 | }
33 |
34 | printf("%uMB %s flash\n", flash_size / (1024 * 1024),
35 | (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
36 |
37 | printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());
38 |
39 | for (int i = 10; i >= 0; i--) {
40 | printf("Restarting in %d seconds...\n", i);
41 | vTaskDelay(1000 / portTICK_PERIOD_MS);
42 | }
43 | printf("Restarting now.\n");
44 | fflush(stdout);
45 | esp_restart();
46 | }
47 |
--------------------------------------------------------------------------------
/examples/esp-idf/hello_world/test_hello_world.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 |
4 | def test_hello_world(dut):
5 | # expect from what esptool.py printed to sys.stdout
6 | dut.expect('Hash of data verified.')
7 |
8 | # now the `dut.expect` would return a `re.Match` object if succeeded
9 | res = dut.expect(r'Hello (\w+)!')
10 |
11 | # don't forget to decode, since the serial outputs are all bytes
12 | logging.info(f'hello to {res.group(1).decode("utf8")}')
13 |
14 | # of course you can just don't care about the return value, do an assert only :)
15 | dut.expect('Restarting')
16 |
--------------------------------------------------------------------------------
/examples/esp-idf/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --embedded-services esp,idf -s
3 |
4 | # log related
5 | log_cli = True
6 | log_cli_level = INFO
7 | log_cli_format = %(asctime)s %(levelname)s %(message)s
8 | log_cli_date_format = %Y-%m-%d %H:%M:%S
9 |
10 | log_file = test.log
11 | log_file_level = INFO
12 | log_file_format = %(asctime)s %(levelname)s %(message)s
13 | log_file_date_format = %Y-%m-%d %H:%M:%S
14 |
--------------------------------------------------------------------------------
/examples/nuttx-esp/README.md:
--------------------------------------------------------------------------------
1 | # NuttX with Espressif Device Example
2 |
3 | The example shows run pytest-embedded using NuttX and Espressif devices.
4 | Simple uses such as serial port data exchange can be done with pytest-embedded only.
5 | Using pytest-embedded-nuttx allows for the of `esptool` along Pytest.
6 |
7 | ## Prerequisites
8 |
9 | 1. Install following packages
10 | - `pytest_embedded`
11 | - `pytest_embedded_nuttx`
12 | - `esptool`
13 |
14 | ## Test Steps
15 |
16 | ```shell
17 | $ pytest
18 | ```
19 |
--------------------------------------------------------------------------------
/examples/nuttx-esp/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --embedded-services nuttx,esp -s --port=/dev/ttyUSB0
3 |
4 | # log related
5 | log_cli = True
6 | log_cli_level = INFO
7 | log_cli_format = %(asctime)s %(levelname)s %(message)s
8 | log_cli_date_format = %Y-%m-%d %H:%M:%S
9 |
--------------------------------------------------------------------------------
/examples/nuttx-esp/test_nuttx_esp.py:
--------------------------------------------------------------------------------
1 | """Test example for Espressif devices"""
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(autouse=True)
7 | def reset_to_nsh(dut, serial):
8 | """Resets the device and waits until NSH prompt is ready."""
9 | serial.hard_reset()
10 | dut.expect('nsh>', timeout=5)
11 |
12 |
13 | def test_nuttx_esp_ps(dut):
14 | dut.write('ps')
15 | dut.expect('Idle_Task', timeout=2)
16 |
--------------------------------------------------------------------------------
/foreach.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -euo pipefail
4 |
5 | DEFAULT_PACKAGES=" \
6 | pytest-embedded \
7 | pytest-embedded-serial \
8 | pytest-embedded-serial-esp \
9 | pytest-embedded-idf \
10 | pytest-embedded-jtag \
11 | pytest-embedded-qemu \
12 | pytest-embedded-arduino \
13 | pytest-embedded-wokwi \
14 | pytest-embedded-nuttx \
15 | "
16 |
17 | action=${1:-"install"}
18 | res=0
19 |
20 | # one-time command
21 | pip install -U pip
22 | if [ "$action" = "build" ]; then
23 | pip install -U flit
24 | elif [ "$action" = "publish" ]; then
25 | pip install -U flit
26 | fi
27 |
28 | # for-loop each package
29 | for pkg in $DEFAULT_PACKAGES; do
30 | pushd "$pkg"
31 | if [ "$action" = "install-editable" ]; then
32 | pip install -e .
33 | elif [ "$action" = "install" ]; then
34 | pip install .
35 | elif [ "$action" = "uninstall" ]; then
36 | pip uninstall -y $pkg
37 | elif [ "$action" = "build" ]; then
38 | flit build
39 | elif [ "$action" = "publish" ]; then
40 | flit publish
41 | else
42 | echo "invalid argument. valid choices: install-editable/install/uninstall/build/publish"
43 | exit 1
44 | fi
45 | popd
46 | done
47 |
48 | exit $res
49 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.commitizen]
2 | name = "cz_conventional_commits"
3 | version = "1.16.1"
4 | tag_format = "v$version"
5 | version_files = [
6 | "pytest-embedded/pytest_embedded/__init__.py",
7 | "pytest-embedded/pyproject.toml",
8 | "pytest-embedded-arduino/pytest_embedded_arduino/__init__.py",
9 | "pytest-embedded-arduino/pyproject.toml",
10 | "pytest-embedded-idf/pytest_embedded_idf/__init__.py",
11 | "pytest-embedded-idf/pyproject.toml",
12 | "pytest-embedded-jtag/pytest_embedded_jtag/__init__.py",
13 | "pytest-embedded-jtag/pyproject.toml",
14 | "pytest-embedded-qemu/pytest_embedded_qemu/__init__.py",
15 | "pytest-embedded-qemu/pyproject.toml",
16 | "pytest-embedded-serial/pytest_embedded_serial/__init__.py",
17 | "pytest-embedded-serial/pyproject.toml",
18 | "pytest-embedded-serial-esp/pytest_embedded_serial_esp/__init__.py",
19 | "pytest-embedded-serial-esp/pyproject.toml",
20 | "pytest-embedded-wokwi/pytest_embedded_wokwi/__init__.py",
21 | "pytest-embedded-wokwi/pyproject.toml",
22 | "pytest-embedded-nuttx/pytest_embedded_nuttx/__init__.py",
23 | "pytest-embedded-nuttx/pyproject.toml",
24 | ]
25 |
26 | [tool.pytest.ini_options]
27 | norecursedirs = 'examples/*'
28 | addopts = "-s"
29 |
--------------------------------------------------------------------------------
/pytest-embedded-arduino/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pytest-embedded-arduino/README.md:
--------------------------------------------------------------------------------
1 | ### pytest-embedded-arduino
2 |
3 | pytest embedded service for Arduino project
4 |
5 | Extra Functionalities:
6 |
7 | ```{eval-rst}
8 | .. tabs::
9 |
10 | .. group-tab:: `pytest-embedded-serial-esp` activated
11 |
12 | - `app`: Parse Arduino's build directory and gather more information.
13 | - `serial`: Auto flash the built binary into the target board at the beginning when running test cases.
14 |
15 | .. group-tab:: `pytest-embedded-serial-esp` NOT activated
16 |
17 | - `app`: Parse Arduino's build directory and gather more information.
18 | ```
19 |
--------------------------------------------------------------------------------
/pytest-embedded-arduino/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "pytest-embedded-arduino"
7 | authors = [
8 | {name = "Abdelatif Guettouche", email = "abdelatif.guettouche@espressif.com"},
9 | {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
10 | ]
11 | readme = "README.md"
12 | license = {file = "LICENSE"}
13 | classifiers = [
14 | "Development Status :: 5 - Production/Stable",
15 | "Framework :: Pytest",
16 | "Intended Audience :: Developers",
17 | "License :: OSI Approved :: MIT License",
18 | "Operating System :: OS Independent",
19 | "Programming Language :: Python :: 3 :: Only",
20 | "Programming Language :: Python :: 3",
21 | "Programming Language :: Python :: 3.7",
22 | "Programming Language :: Python :: 3.8",
23 | "Programming Language :: Python :: 3.9",
24 | "Programming Language :: Python :: 3.10",
25 | "Programming Language :: Python :: 3.11",
26 | "Programming Language :: Python :: 3.12",
27 | "Programming Language :: Python :: 3.13",
28 | "Programming Language :: Python",
29 | "Topic :: Software Development :: Testing",
30 | ]
31 | dynamic = ["version", "description"]
32 | requires-python = ">=3.7"
33 |
34 | dependencies = [
35 | "pytest-embedded~=1.16.1",
36 | ]
37 |
38 | [project.optional-dependencies]
39 | serial = [
40 | "pytest-embedded-serial-esp~=1.16.1"
41 | ]
42 |
43 | [project.urls]
44 | homepage = "https://github.com/espressif/pytest-embedded"
45 | repository = "https://github.com/espressif/pytest-embedded"
46 | documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
47 | changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
48 |
--------------------------------------------------------------------------------
/pytest-embedded-arduino/pytest_embedded_arduino/__init__.py:
--------------------------------------------------------------------------------
1 | """Make pytest-embedded plugin work with Arduino."""
2 |
3 | import importlib
4 |
5 | from pytest_embedded.utils import lazy_load
6 |
7 | from .app import ArduinoApp
8 |
9 | __getattr__ = lazy_load(
10 | importlib.import_module(__name__),
11 | {
12 | 'ArduinoApp': ArduinoApp,
13 | },
14 | {
15 | 'ArduinoSerial': '.serial', # requires esp
16 | },
17 | )
18 |
19 | __all__ = ['ArduinoApp', 'ArduinoSerial']
20 |
21 |
22 | __version__ = '1.16.1'
23 |
--------------------------------------------------------------------------------
/pytest-embedded-arduino/pytest_embedded_arduino/app.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | from typing import ClassVar, Dict, List, Tuple
4 |
5 | from pytest_embedded.app import App
6 |
7 |
8 | class ArduinoApp(App):
9 | """
10 | Arduino App class
11 |
12 | Attributes:
13 | sketch (str): Sketch name.
14 | fqbn (str): Fully Qualified Board Name.
15 | target (str) : ESPxx chip.
16 | flash_files (List[Tuple[int, str, str]]): List of (offset, file path, encrypted) of files need to be flashed in.
17 | """
18 |
19 | #: dict of flash settings
20 | flash_settings: ClassVar[Dict[str, Dict[str, str]]] = {
21 | 'esp32': {'flash_mode': 'dio', 'flash_size': 'detect', 'flash_freq': '80m'},
22 | 'esp32s2': {'flash_mode': 'dio', 'flash_size': 'detect', 'flash_freq': '80m'},
23 | 'esp32c3': {'flash_mode': 'dio', 'flash_size': 'detect', 'flash_freq': '80m'},
24 | 'esp32s3': {'flash_mode': 'dio', 'flash_size': 'detect', 'flash_freq': '80m'},
25 | 'esp32c6': {'flash_mode': 'dio', 'flash_size': 'detect', 'flash_freq': '80m'},
26 | 'esp32h2': {'flash_mode': 'dio', 'flash_size': 'detect', 'flash_freq': '48m'},
27 | 'esp32p4': {'flash_mode': 'dio', 'flash_size': 'detect', 'flash_freq': '80m'},
28 | }
29 |
30 | #: dict of binaries' offset.
31 | binary_offsets: ClassVar[Dict[str, List[int]]] = {
32 | 'esp32': [0x1000, 0x8000, 0x10000],
33 | 'esp32s2': [0x1000, 0x8000, 0x10000],
34 | 'esp32c3': [0x0, 0x8000, 0x10000],
35 | 'esp32s3': [0x0, 0x8000, 0x10000],
36 | 'esp32c6': [0x0, 0x8000, 0x10000],
37 | 'esp32h2': [0x0, 0x8000, 0x10000],
38 | 'esp32p4': [0x2000, 0x8000, 0x10000],
39 | }
40 |
41 | def __init__(
42 | self,
43 | **kwargs,
44 | ):
45 | super().__init__(**kwargs)
46 |
47 | self.sketch = os.path.basename(self.app_path)
48 | self.fqbn = self._get_fqbn(self.binary_path)
49 | self.target = self.fqbn.split(':')[2]
50 | self.flash_files = self._get_bin_files(self.binary_path, self.sketch, self.target)
51 | self.elf_file = os.path.realpath(os.path.join(self.binary_path, self.sketch + '.ino.elf'))
52 |
53 | def _get_fqbn(self, build_path) -> str:
54 | options_file = os.path.realpath(os.path.join(build_path, 'build.options.json'))
55 | with open(options_file) as f:
56 | options = json.load(f)
57 | fqbn = options['fqbn']
58 | return fqbn
59 |
60 | def _get_bin_files(self, build_path, sketch, target) -> List[Tuple[int, str, bool]]:
61 | bootloader = os.path.realpath(os.path.join(build_path, sketch + '.ino.bootloader.bin'))
62 | partitions = os.path.realpath(os.path.join(build_path, sketch + '.ino.partitions.bin'))
63 | app = os.path.realpath(os.path.join(build_path, sketch + '.ino.bin'))
64 | files = [bootloader, partitions, app]
65 | offsets = self.binary_offsets[target]
66 | return [(offsets[i], files[i], False) for i in range(3)]
67 |
--------------------------------------------------------------------------------
/pytest-embedded-arduino/pytest_embedded_arduino/serial.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from typing import Optional
3 |
4 | import esptool
5 | from pytest_embedded_serial_esp.serial import EspSerial
6 |
7 | from .app import ArduinoApp
8 |
9 |
10 | class ArduinoSerial(EspSerial):
11 | """
12 | Arduino serial Dut class
13 |
14 | Auto flash the app while starting test.
15 | """
16 |
17 | SUGGEST_FLASH_BAUDRATE = 921600
18 |
19 | def __init__(
20 | self,
21 | app: ArduinoApp,
22 | target: Optional[str] = None,
23 | **kwargs,
24 | ) -> None:
25 | self.app = app
26 | super().__init__(
27 | target=target or self.app.target,
28 | **kwargs,
29 | )
30 |
31 | def _start(self):
32 | if self.skip_autoflash:
33 | logging.info('Skipping auto flash...')
34 | super()._start()
35 | else:
36 | self.flash()
37 |
38 | @EspSerial.use_esptool()
39 | def flash(self) -> None:
40 | """
41 | Flash the binary files to the board.
42 | """
43 | flash_files = []
44 | for offset, path, encrypted in self.app.flash_files:
45 | if encrypted:
46 | continue
47 | flash_files.extend((str(offset), path))
48 |
49 | flash_settings = []
50 | for k, v in self.app.flash_settings[self.app.target].items():
51 | flash_settings.append(f'--{k}')
52 | flash_settings.append(v)
53 |
54 | if self.esp_flash_force:
55 | flash_settings.append('--force')
56 |
57 | try:
58 | esptool.main(
59 | ['--chip', self.app.target, 'write_flash', *flash_files, *flash_settings],
60 | esp=self.esp,
61 | )
62 | except Exception:
63 | raise
64 |
--------------------------------------------------------------------------------
/pytest-embedded-arduino/tests/test_arduino.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 |
4 | def test_arduino_serial_flash(testdir):
5 | testdir.makepyfile("""
6 | import pexpect
7 | import pytest
8 |
9 | def test_arduino_app(app, dut):
10 | assert len(app.flash_files) == 3
11 | assert app.target == 'esp32'
12 | assert app.fqbn == 'espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app'
13 | dut.expect('Hello Arduino!')
14 | with pytest.raises(pexpect.TIMEOUT):
15 | dut.expect('foo bar not found', timeout=1)
16 | """)
17 |
18 | result = testdir.runpytest(
19 | '-s',
20 | '--embedded-services',
21 | 'arduino,esp',
22 | '--app-path',
23 | os.path.join(testdir.tmpdir, 'hello_world_arduino'),
24 | '--build-dir',
25 | 'build',
26 | )
27 |
28 | result.assert_outcomes(passed=1)
29 |
--------------------------------------------------------------------------------
/pytest-embedded-idf/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pytest-embedded-idf/README.md:
--------------------------------------------------------------------------------
1 | ### pytest-embedded-idf
2 |
3 | pytest embedded service for esp-idf project
4 |
5 | Extra Functionalities:
6 |
7 | ```{eval-rst}
8 | .. tabs::
9 |
10 | .. group-tab:: `pytest-embedded-serial-esp` activated
11 |
12 | - `app`: parse the built binary by idf rules and gather more information.
13 | - `serial`: auto flash the built binary into the target board at the beginning when running test cases.
14 |
15 | .. group-tab:: `pytest-embedded-serial-esp` NOT activated
16 |
17 | - `app`: parse the built binary by idf rules and gather more information.
18 | ```
19 |
--------------------------------------------------------------------------------
/pytest-embedded-idf/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "pytest-embedded-idf"
7 | authors = [
8 | {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
9 | ]
10 | readme = "README.md"
11 | license = {file = "LICENSE"}
12 | classifiers = [
13 | "Development Status :: 5 - Production/Stable",
14 | "Framework :: Pytest",
15 | "Intended Audience :: Developers",
16 | "License :: OSI Approved :: MIT License",
17 | "Operating System :: OS Independent",
18 | "Programming Language :: Python :: 3 :: Only",
19 | "Programming Language :: Python :: 3",
20 | "Programming Language :: Python :: 3.7",
21 | "Programming Language :: Python :: 3.8",
22 | "Programming Language :: Python :: 3.9",
23 | "Programming Language :: Python :: 3.10",
24 | "Programming Language :: Python :: 3.11",
25 | "Programming Language :: Python :: 3.12",
26 | "Programming Language :: Python :: 3.13",
27 | "Programming Language :: Python",
28 | "Topic :: Software Development :: Testing",
29 | ]
30 | dynamic = ["version", "description"]
31 | requires-python = ">=3.7"
32 |
33 | dependencies = [
34 | "pytest-embedded~=1.16.1",
35 | "esp-idf-panic-decoder",
36 | "esp-bool-parser>=0.1.2,<1"
37 | ]
38 |
39 | [project.optional-dependencies]
40 | serial = [
41 | "pytest-embedded-serial-esp~=1.16.1",
42 | "esp-coredump~=1.0",
43 | ]
44 |
45 | [project.urls]
46 | homepage = "https://github.com/espressif/pytest-embedded"
47 | repository = "https://github.com/espressif/pytest-embedded"
48 | documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
49 | changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
50 |
--------------------------------------------------------------------------------
/pytest-embedded-idf/pytest_embedded_idf/__init__.py:
--------------------------------------------------------------------------------
1 | """Make pytest-embedded plugin work with ESP-IDF."""
2 |
3 | import importlib
4 |
5 | from pytest_embedded.utils import lazy_load
6 |
7 | from .app import IdfApp
8 | from .linux import LinuxDut, LinuxSerial
9 | from .unity_tester import CaseTester, UnittestMenuCase
10 |
11 | __getattr__ = lazy_load(
12 | importlib.import_module(__name__),
13 | {
14 | 'IdfApp': IdfApp,
15 | 'LinuxDut': LinuxDut,
16 | 'LinuxSerial': LinuxSerial,
17 | 'CaseTester': CaseTester,
18 | },
19 | {
20 | 'IdfSerial': '.serial', # requires esp
21 | 'IdfDut': '.dut', # requires esp
22 | },
23 | )
24 |
25 |
26 | __all__ = [
27 | 'CaseTester',
28 | 'IdfApp',
29 | 'IdfDut',
30 | 'IdfSerial',
31 | 'LinuxDut',
32 | 'LinuxSerial',
33 | 'UnittestMenuCase',
34 | ]
35 |
36 | __version__ = '1.16.1'
37 |
--------------------------------------------------------------------------------
/pytest-embedded-idf/pytest_embedded_idf/linux.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 |
3 | from pytest_embedded.dut import Dut
4 | from pytest_embedded.log import DuplicateStdoutPopen
5 |
6 | from .app import IdfApp
7 |
8 |
9 | class LinuxDut(Dut):
10 | """
11 | Dut class for Linux targets
12 |
13 | Attributes:
14 | serial (LinuxSerial): `LinuxSerial` instance
15 | """
16 |
17 | def __init__(
18 | self,
19 | serial,
20 | **kwargs,
21 | ) -> None:
22 | self.serial = serial
23 | super().__init__(**kwargs)
24 |
25 | self._hard_reset_func = self.serial.hard_reset
26 |
27 | def write(self, data: t.AnyStr) -> None:
28 | self.serial.write(data)
29 |
30 |
31 | class LinuxSerial(DuplicateStdoutPopen):
32 | """
33 | Linux serial Dut class
34 | """
35 |
36 | def __init__(
37 | self,
38 | app: IdfApp,
39 | **kwargs,
40 | ) -> None:
41 | self.app = app
42 |
43 | if not hasattr(self.app, 'target'):
44 | raise ValueError(f"Idf app not parsable. Please check if it's valid: {self.app.binary_path}")
45 |
46 | if self.app.target != 'linux':
47 | raise ValueError(f'Targets do not match. App target: {self.app.target}, Cmd target: "linux".')
48 |
49 | super().__init__(cmd=[self.app.elf_file], **kwargs)
50 |
51 | def hard_reset(self) -> None:
52 | """
53 | Perform a fake hardware reset
54 | """
55 | self.write('\n')
56 |
--------------------------------------------------------------------------------
/pytest-embedded-idf/pytest_embedded_idf/utils.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import typing as t
3 | from contextvars import ContextVar
4 |
5 | import pytest
6 | from esp_bool_parser import PREVIEW_TARGETS, SUPPORTED_TARGETS
7 | from esp_bool_parser.bool_parser import parse_bool_expr
8 |
9 | supported_targets = ContextVar('supported_targets', default=SUPPORTED_TARGETS)
10 | preview_targets = ContextVar('preview_targets', default=PREVIEW_TARGETS)
11 |
12 |
13 | if sys.version_info < (3, 8):
14 | from typing_extensions import Literal
15 | else:
16 | from typing import Literal
17 |
18 |
19 | def _expand_target_values(values: t.List[t.List[t.Any]], target_index: int) -> t.List[t.List[t.Any]]:
20 | """
21 | Expands target-specific values into individual test cases.
22 | """
23 | expanded_values = []
24 | for value in values:
25 | target = value[target_index]
26 | if target == 'supported_targets':
27 | expanded_values.extend([
28 | value[:target_index] + [target] + value[target_index + 1 :] for target in supported_targets.get()
29 | ])
30 | elif target == 'preview_targets':
31 | expanded_values.extend([
32 | value[:target_index] + [target] + value[target_index + 1 :] for target in preview_targets.get()
33 | ])
34 | else:
35 | expanded_values.append(value)
36 | return expanded_values
37 |
38 |
39 | def _process_pytest_value(value: t.Union[t.List[t.Any], t.Any], param_count: int) -> t.Any:
40 | """
41 | Processes a single parameter value, converting it to pytest.param if needed.
42 | """
43 | if not isinstance(value, (list, tuple)):
44 | return value
45 |
46 | if len(value) > param_count + 1:
47 | raise ValueError(f'Expected at most {param_count + 1} elements (params + marks), got {len(value)}')
48 |
49 | params, marks = [], []
50 | if len(value) > param_count:
51 | mark_values = value[-1]
52 | marks.extend(mark_values if isinstance(mark_values, (tuple, list)) else (mark_values,))
53 |
54 | params.extend(value[:param_count])
55 |
56 | return pytest.param(*params, marks=tuple(marks))
57 |
58 |
59 | def idf_parametrize(
60 | param_names: str,
61 | values: t.List[t.Union[t.Any, t.Tuple[t.Any, ...]]],
62 | indirect: (t.Union[bool, t.Sequence[str]]) = False,
63 | ) -> t.Callable[..., None]:
64 | """
65 | A decorator to unify pytest.mark.parametrize usage in esp-idf.
66 |
67 | Args:
68 | param_names: A comma-separated string of parameter names that will be passed to
69 | the test function.
70 | values: A list of parameter values where each value corresponds to the parameters
71 | defined in param_names.
72 | indirect: A list of arguments names (subset of argnames) or a boolean. If True
73 | the list contains all names from the argnames. Each argvalue corresponding to an
74 | argname in this list will be passed as request.param to its respective argname
75 | fixture function so that it can perform more expensive setups during the setup
76 | phase of a test rather than at collection time.
77 |
78 | Returns:
79 | Decorated test function with parametrization applied
80 | """
81 | param_list = [name.strip() for name in param_names.split(',')]
82 | for param in param_list:
83 | if not param:
84 | raise ValueError(f'One of the provided parameters name is empty: {param_list}')
85 |
86 | param_count = len(param_list)
87 | param_list[:] = [_p for _p in param_list if _p not in ('markers',)]
88 | target_index = param_list.index('target') if 'target' in param_list else -1
89 | normalized_values = [[value] if param_count == 1 else list(value) for value in values]
90 | param_count = len(param_list)
91 |
92 | if target_index != -1:
93 | normalized_values = _expand_target_values(normalized_values, target_index)
94 |
95 | processed_values = [_process_pytest_value(value, param_count) for value in normalized_values]
96 |
97 | def decorator(func):
98 | return pytest.mark.parametrize(','.join(param_list), processed_values, indirect=indirect)(func)
99 |
100 | return decorator
101 |
102 |
103 | ValidTargets = Literal['supported_targets', 'preview_targets', 'all']
104 |
105 |
106 | def soc_filtered_targets(soc_statement: str, targets: ValidTargets = 'all') -> t.List[str]:
107 | """Filters targets based on a given SOC (System on Chip) statement.
108 |
109 | Args:
110 | soc_statement (str): A boolean expression used to filter targets.
111 | targets (ValidTargets, optional): Specifies which target set to filter.
112 | - "supported_targets": Filters only supported targets.
113 | - "preview_targets": Filters only preview targets.
114 | - "all": Filters both supported and preview targets.
115 | Defaults to "all".
116 |
117 | Returns:
118 | List[str]: A list of targets that satisfy the given SOC statement.
119 | """
120 | target_list = []
121 | target_list.extend(supported_targets.get()) if targets in ['all', 'supported_targets'] else []
122 | target_list.extend(preview_targets.get()) if targets in ['all', 'preview_targets'] else []
123 |
124 | stm = parse_bool_expr(soc_statement)
125 |
126 | result = []
127 | for target in target_list:
128 | if stm.get_value(target, ''):
129 | result.append(target)
130 | return result
131 |
--------------------------------------------------------------------------------
/pytest-embedded-jtag/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pytest-embedded-jtag/README.md:
--------------------------------------------------------------------------------
1 | ### pytest-embedded-jtag
2 |
3 | pytest embedded service for openocd/gdb utilities
4 |
5 | Extra Functionalities:
6 |
7 | - `openocd`: enable the fixture
8 | - `gdb`: enable the fixture
9 | - `dut`: duplicate the `openocd` output and the `gdb` output to `dut.pexpect_proc`.
10 |
--------------------------------------------------------------------------------
/pytest-embedded-jtag/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/pytest-embedded-jtag/__init__.py
--------------------------------------------------------------------------------
/pytest-embedded-jtag/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "pytest-embedded-jtag"
7 | authors = [
8 | {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
9 | ]
10 | readme = "README.md"
11 | license = {file = "LICENSE"}
12 | classifiers = [
13 | "Development Status :: 5 - Production/Stable",
14 | "Framework :: Pytest",
15 | "Intended Audience :: Developers",
16 | "License :: OSI Approved :: MIT License",
17 | "Operating System :: OS Independent",
18 | "Programming Language :: Python :: 3 :: Only",
19 | "Programming Language :: Python :: 3",
20 | "Programming Language :: Python :: 3.7",
21 | "Programming Language :: Python :: 3.8",
22 | "Programming Language :: Python :: 3.9",
23 | "Programming Language :: Python :: 3.10",
24 | "Programming Language :: Python :: 3.11",
25 | "Programming Language :: Python :: 3.12",
26 | "Programming Language :: Python :: 3.13",
27 | "Programming Language :: Python",
28 | "Topic :: Software Development :: Testing",
29 | ]
30 | dynamic = ["version", "description"]
31 | requires-python = ">=3.7"
32 |
33 | dependencies = [
34 | "pytest-embedded-serial~=1.16.1",
35 | ]
36 |
37 | [project.urls]
38 | homepage = "https://github.com/espressif/pytest-embedded"
39 | repository = "https://github.com/espressif/pytest-embedded"
40 | documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
41 | changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
42 |
--------------------------------------------------------------------------------
/pytest-embedded-jtag/pytest_embedded_jtag/__init__.py:
--------------------------------------------------------------------------------
1 | """Make pytest-embedded plugin work with JTAG."""
2 |
3 | from ._telnetlib.telnetlib import Telnet
4 | from .gdb import Gdb
5 | from .openocd import OpenOcd
6 |
7 | __all__ = [
8 | 'Gdb',
9 | 'OpenOcd',
10 | 'Telnet',
11 | ]
12 |
13 | __version__ = '1.16.1'
14 |
--------------------------------------------------------------------------------
/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/__init__.py
--------------------------------------------------------------------------------
/pytest-embedded-jtag/pytest_embedded_jtag/gdb.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import re
3 | import shlex
4 | import time
5 | from typing import AnyStr, Optional
6 |
7 | from pytest_embedded.log import DuplicateStdoutPopen
8 |
9 |
10 | class Gdb(DuplicateStdoutPopen):
11 | SOURCE = 'GDB'
12 | REDIRECT_CLS = None
13 |
14 | GDB_PROG_PATH = 'xtensa-esp32-elf-gdb'
15 | GDB_DEFAULT_ARGS = '--quiet'
16 |
17 | _GDB_RESPONSE_FINISHED_RE = re.compile(r'^\(gdb\)\s*$')
18 |
19 | def __init__(self, gdb_prog_path: Optional[str] = None, gdb_cli_args: Optional[str] = None, **kwargs):
20 | gdb_prog_path = gdb_prog_path or self.GDB_PROG_PATH
21 | gdb_cli_args = shlex.split(gdb_cli_args or self.GDB_DEFAULT_ARGS)
22 |
23 | self._gdb_first_write = True
24 |
25 | super().__init__(cmd=[gdb_prog_path, *gdb_cli_args], **kwargs)
26 |
27 | def write(self, s: AnyStr, non_blocking: bool = False, timeout: float = 30) -> Optional[str]:
28 | with open(self._logfile) as fr:
29 | if self._gdb_first_write:
30 | # Discard all queued responses before the first write
31 | _ = fr.readlines()
32 | self._logfile_offset = fr.tell()
33 | self._gdb_first_write = False
34 |
35 | super().write(s)
36 |
37 | if non_blocking:
38 | logging.debug('non-blocking write...')
39 | return None
40 |
41 | _buffer = ''
42 | _t_start = time.time()
43 | fr.seek(self._logfile_offset)
44 | while True:
45 | line = fr.readline()
46 | if line:
47 | _buffer += line
48 | if self._GDB_RESPONSE_FINISHED_RE.match(line):
49 | break
50 |
51 | _t_now = time.time()
52 | if (_t_now - _t_start) >= timeout:
53 | logging.debug(f'current buffer: {_buffer}')
54 | raise ValueError(f'gdb no response after {timeout} seconds')
55 |
56 | self._logfile_offset = fr.tell()
57 |
58 | logging.debug(f'{self.SOURCE} <-: {_buffer}')
59 | return _buffer
60 |
--------------------------------------------------------------------------------
/pytest-embedded-jtag/pytest_embedded_jtag/openocd.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import shlex
4 | import time
5 | from typing import AnyStr, Optional
6 |
7 | from pytest_embedded.log import DuplicateStdoutPopen
8 | from pytest_embedded.utils import to_bytes, to_str
9 |
10 | from ._telnetlib.telnetlib import Telnet
11 |
12 |
13 | class OpenOcd(DuplicateStdoutPopen):
14 | """
15 | Class to communicate to OpenOCD
16 | """
17 |
18 | SOURCE = 'OPENOCD'
19 | REDIRECT_CLS = None
20 |
21 | OPENOCD_PROG_PATH = 'openocd'
22 | OPENOCD_DEFAULT_ARGS = '-f board/esp32-wrover-kit-3.3v.cfg'
23 |
24 | TCL_BASE_PORT = 6666
25 | TELNET_BASE_PORT = 4444
26 | GDB_BASE_PORT = 3333
27 |
28 | def __init__(
29 | self,
30 | openocd_prog_path: Optional[str] = None,
31 | openocd_cli_args: Optional[str] = None,
32 | port_offset: int = 0,
33 | **kwargs,
34 | ):
35 | openocd_prog_path = openocd_prog_path or os.getenv('OPENOCD_BIN', self.OPENOCD_PROG_PATH)
36 | openocd_cli_args = shlex.split(openocd_cli_args or self.OPENOCD_DEFAULT_ARGS)
37 |
38 | openocd_scripts_path = os.getenv('OPENOCD_SCRIPTS')
39 | if openocd_scripts_path:
40 | openocd_cli_args.extend(['-s', openocd_scripts_path])
41 |
42 | self.tcl_port = self.TCL_BASE_PORT + port_offset
43 | self.telnet_port = self.TELNET_BASE_PORT + port_offset
44 | self.gdb_port = self.GDB_BASE_PORT + port_offset
45 |
46 | openocd_cli_args.extend([
47 | '-c',
48 | f'tcl_port {self.tcl_port}',
49 | '-c',
50 | f'telnet_port {self.telnet_port}',
51 | '-c',
52 | f'gdb_port {self.gdb_port}',
53 | ])
54 |
55 | super().__init__(cmd=[openocd_prog_path, *openocd_cli_args], **kwargs)
56 |
57 | # open telnet port to interact with openocd
58 | for i in range(30):
59 | try:
60 | self.telnet = Telnet('127.0.0.1', self.telnet_port, 5)
61 | break
62 | except ConnectionRefusedError:
63 | time.sleep(1)
64 | else:
65 | raise ConnectionRefusedError
66 |
67 | def write(self, s: AnyStr) -> str:
68 | # read all output already sent
69 | resp = self.telnet.read_very_eager()
70 | if resp:
71 | logging.debug(f'{self.SOURCE} <-: {to_str(resp)}')
72 |
73 | logging.debug(f'{self.SOURCE} ->: {to_str(s)}')
74 | self.telnet.write(to_bytes(s, '\n'))
75 |
76 | resp = self.telnet.read_until(b'>')
77 |
78 | logging.debug(f'{self.SOURCE} <-: {to_str(resp)}')
79 | return to_str(resp)
80 |
--------------------------------------------------------------------------------
/pytest-embedded-jtag/tests/test_jtag.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import pytest
4 |
5 | jtag_connection_required = pytest.mark.skipif(
6 | os.getenv('DONT_SKIP_JTAG_TESTS', False) is False,
7 | reason='Connect the board to a JTAG adapter then use "DONT_SKIP_JTAG_TESTS" to run this test.',
8 | )
9 |
10 |
11 | @jtag_connection_required
12 | def test_pexpect_by_jtag(testdir):
13 | testdir.makepyfile(r"""
14 | import os
15 | import time
16 |
17 | def test_pexpect_by_jtag(dut):
18 | dut.gdb.write('mon reset halt')
19 | dut.gdb.write('thb app_main')
20 | dut.gdb.write('c')
21 | dut.gdb.write('c', non_blocking=True)
22 | dut.expect('Hello world')
23 | """)
24 |
25 | result = testdir.runpytest(
26 | '-s',
27 | '--embedded-services',
28 | 'jtag,idf',
29 | '--app-path',
30 | os.path.join(testdir.tmpdir, 'hello_world_esp32'),
31 | '--port',
32 | '/dev/ttyUSB1',
33 | '--part-tool',
34 | os.path.join(testdir.tmpdir, 'gen_esp32part.py'),
35 | )
36 |
37 | result.assert_outcomes(passed=1)
38 |
--------------------------------------------------------------------------------
/pytest-embedded-nuttx/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pytest-embedded-nuttx/README.md:
--------------------------------------------------------------------------------
1 | ### pytest-embedded-nuttx
2 |
3 | Pytest embedded service for the NuttX project, compatible with Espressif devices.
4 |
5 | Using the 'nuttx' service alongside 'serial' enables reading from and writing to the serial port, taking NuttShell into account when running programs and retrieving return codes.
6 |
7 | The `nuttx` service provides basic serial communication and testing. Adding the 'esp' service enables further capabilities for Espressif devices, including flashing and device rebooting. Alternatively, using the 'qemu' service is also supported with NuttX
8 | binaries.
9 |
10 | Additional Features:
11 |
12 | - `app`: Scans the NuttX binary directory to locate firmware and bootloader files.
13 | - `serial`: Parses binary information and flashes the board. Requires the 'esp' service.
14 | - `dut`: Sends commands to the device through the serial port. Requires the 'serial' service, 'esp' service for Espressif devices or 'qemu' service for emulation.
15 |
--------------------------------------------------------------------------------
/pytest-embedded-nuttx/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "pytest-embedded-nuttx"
7 | authors = [
8 | {name = "Filipe Cavalcanti", email = "filipe.cavalcanti@espressif.com"},
9 | {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
10 | ]
11 | readme = "README.md"
12 | license = {file = "LICENSE"}
13 | classifiers = [
14 | "Development Status :: 5 - Production/Stable",
15 | "Framework :: Pytest",
16 | "Intended Audience :: Developers",
17 | "License :: OSI Approved :: MIT License",
18 | "Operating System :: OS Independent",
19 | "Programming Language :: Python :: 3 :: Only",
20 | "Programming Language :: Python :: 3",
21 | "Programming Language :: Python :: 3.7",
22 | "Programming Language :: Python :: 3.8",
23 | "Programming Language :: Python :: 3.9",
24 | "Programming Language :: Python :: 3.10",
25 | "Programming Language :: Python :: 3.11",
26 | "Programming Language :: Python :: 3.12",
27 | "Programming Language :: Python :: 3.13",
28 | "Programming Language :: Python",
29 | "Topic :: Software Development :: Testing",
30 | ]
31 | dynamic = ["version", "description"]
32 | requires-python = ">=3.7"
33 |
34 | dependencies = [
35 | "pytest-embedded-serial~=1.16.1",
36 | ]
37 |
38 | [project.optional-dependencies]
39 | esp = [
40 | "pytest-embedded-serial-esp~=1.16.1",
41 | ]
42 | qemu = [
43 | "pytest-embedded-qemu~=1.16.1",
44 | ]
45 |
46 | [project.urls]
47 | homepage = "https://github.com/espressif/pytest-embedded"
48 | repository = "https://github.com/espressif/pytest-embedded"
49 | documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
50 | changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
51 |
--------------------------------------------------------------------------------
/pytest-embedded-nuttx/pytest_embedded_nuttx/__init__.py:
--------------------------------------------------------------------------------
1 | """Make pytest-embedded plugin work with NuttX."""
2 |
3 | import importlib
4 |
5 | from pytest_embedded.utils import lazy_load
6 |
7 | from .app import NuttxApp
8 | from .dut import NuttxDut, NuttxSerialDut
9 |
10 | __getattr__ = lazy_load(
11 | importlib.import_module(__name__),
12 | {
13 | 'NuttxApp': NuttxApp,
14 | 'NuttxDut': NuttxDut,
15 | 'NuttxSerialDut': NuttxSerialDut,
16 | },
17 | {
18 | 'NuttxSerial': '.serial', # requires 'esp' service
19 | 'NuttxEspDut': '.serial', # requires 'esp' service
20 | 'NuttxQemuDut': '.qemu', # requires 'qemu' service
21 | },
22 | )
23 |
24 | __all__ = ['NuttxApp', 'NuttxDut', 'NuttxEspDut', 'NuttxQemuDut', 'NuttxSerial', 'NuttxSerialDut']
25 |
26 | __version__ = '1.16.1'
27 |
--------------------------------------------------------------------------------
/pytest-embedded-nuttx/pytest_embedded_nuttx/app.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import typing as t
3 | from pathlib import Path
4 |
5 | from pytest_embedded.app import App
6 |
7 |
8 | class NuttxApp(App):
9 | """
10 | NuttX App class for Espressif devices.
11 | Evaluates binary files (firmware and bootloader) and extract information
12 | required for flashing.
13 |
14 | Attributes:
15 | file_extension (str): app binary file extension.
16 | """
17 |
18 | def __init__(
19 | self,
20 | file_extension='.bin',
21 | **kwargs,
22 | ):
23 | super().__init__(**kwargs)
24 |
25 | self.file_extension = file_extension
26 | self.app_file, self.bootloader_file, self.merge_file = self._get_bin_files()
27 |
28 | def _get_bin_files(self) -> t.Tuple[t.Optional[Path], t.Optional[Path], t.Optional[Path]]:
29 | """
30 | Get path to binary files available in the app_path.
31 | If either the application image or bootloader is not found,
32 | None is returned.
33 |
34 | Returns:
35 | tuple: path to application binary file and bootloader file.
36 | """
37 | search_path = Path(self.app_path)
38 | search_pattern = '*' + self.file_extension
39 | bin_files = list(search_path.rglob(search_pattern))
40 | app_file, bootloader_file, merge_file = None, None, None
41 |
42 | logging.info('Searching %s', str(search_path))
43 | if not bin_files:
44 | logging.warning('No binary files found with pattern: %s', search_pattern)
45 |
46 | for file_path in bin_files:
47 | file_name = str(file_path.stem)
48 | if 'nuttx' in file_name:
49 | if 'merged' in file_name:
50 | merge_file = file_path
51 | logging.info('Merge file: %s', merge_file.as_posix())
52 | else:
53 | app_file = file_path
54 | logging.info('App file: %s', app_file.as_posix())
55 | if 'mcuboot-' in file_name:
56 | bootloader_file = file_path
57 | logging.info('Bootloader file: %s', bootloader_file.as_posix())
58 |
59 | if not app_file:
60 | logging.error('App file not found: %s', app_file)
61 | print(bin_files)
62 |
63 | return app_file, bootloader_file, merge_file
64 |
--------------------------------------------------------------------------------
/pytest-embedded-nuttx/pytest_embedded_nuttx/dut.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import re
3 | from time import sleep
4 | from typing import AnyStr
5 |
6 | import pexpect
7 | from pytest_embedded.dut import Dut
8 | from pytest_embedded_serial import SerialDut
9 |
10 |
11 | class NuttxDut(Dut):
12 | """
13 | Generic DUT class for use with NuttX RTOS.
14 | """
15 |
16 | PROMPT_NSH = 'nsh>'
17 | PROMPT_TIMEOUT_S = 30
18 |
19 | def write(self, data: str) -> None:
20 | """
21 | Write to NuttShell and sleep for a few hundred milliseconds to
22 | ensure there is time for Nuttshell prompt appear again.
23 |
24 | Args:
25 | data (str): data to be passed on to Nuttshell.
26 |
27 | Returns:
28 | None.
29 | """
30 | super().write('')
31 | self.expect(self.PROMPT_NSH, timeout=self.PROMPT_TIMEOUT_S)
32 | super().write(data)
33 | sleep(0.25)
34 |
35 | def return_code(self, timeout: int = PROMPT_TIMEOUT_S) -> int:
36 | """
37 | Matches the 'echo $?' response and extracts the integer value
38 | corresponding to the last program return code.
39 |
40 | The first regex option on expect is for serial interface,
41 | while the second will match QEMU.
42 |
43 | Returns:
44 | int: return code.
45 | """
46 | self.write('echo $?')
47 | echo_match = self.expect([r'echo \$\?\r\n(\d+)', r'echo \$\?\n*(\d+)\n'], timeout=timeout)
48 | ret_code = re.findall(r'\d+', echo_match.group().decode())
49 |
50 | if not ret_code:
51 | logging.error('Failed to retrieve return code')
52 |
53 | return int(ret_code[0])
54 |
55 | def write_and_return(self, data: str, timeout: int = 2) -> AnyStr:
56 | """
57 | Writes to Nuttshell and returns all available serial data until
58 | the timeout.
59 | This is useful when parsing and reusing the data is required, and
60 | pexect is not enough.
61 |
62 | Args:
63 | data (str): data to be passed on to Nuttshell.
64 | timeout (int): how long to wait for an answer in seconds.
65 |
66 | Returns:
67 | AnyStr
68 | """
69 | self.write(data)
70 | ans = self.expect(pexpect.TIMEOUT, timeout=timeout)
71 | return ans.rstrip().decode()
72 |
73 | def reset_to_nsh(self, ready_prompt: str = PROMPT_NSH) -> None:
74 | """
75 | Resets the board and waits until the Nuttshell prompt appears.
76 | Defaults to 'nsh>'.
77 |
78 | Args:
79 | ready_prompt (str): string on prompt that signals completion.
80 |
81 | Returns:
82 | None
83 | """
84 | if self.reset:
85 | logging.info('Resetting board')
86 | self.reset()
87 | else:
88 | logging.error('Resetting method not available')
89 | self.expect(ready_prompt, timeout=self.PROMPT_TIMEOUT_S)
90 |
91 |
92 | class NuttxSerialDut(SerialDut, NuttxDut):
93 | """
94 | DUT class for serial ports connected to generic boards running NuttX
95 | with NuttX RTOS.
96 | """
97 |
98 | def __init__(
99 | self,
100 | **kwargs,
101 | ) -> None:
102 | super().__init__(**kwargs)
103 |
104 | def reset(self) -> None:
105 | """Reset the DUT by toggling the DTR line."""
106 | self.serial.proc.dtr = False
107 | sleep(0.2)
108 | self.serial.proc.dtr = True
109 |
--------------------------------------------------------------------------------
/pytest-embedded-nuttx/pytest_embedded_nuttx/qemu.py:
--------------------------------------------------------------------------------
1 | from pytest_embedded_qemu import Qemu, QemuDut
2 |
3 | from .dut import NuttxDut
4 |
5 |
6 | class NuttxQemuDut(QemuDut, NuttxDut):
7 | """
8 | DUT class for QEMU usage of the NuttX RTOS.
9 | """
10 |
11 | def __init__(
12 | self,
13 | qemu: Qemu,
14 | **kwargs,
15 | ) -> None:
16 | self.qemu = qemu
17 |
18 | super().__init__(qemu=qemu, **kwargs)
19 |
20 | def reset(self) -> None:
21 | """Hard reset the DUT."""
22 | self.hard_reset()
23 |
--------------------------------------------------------------------------------
/pytest-embedded-nuttx/pytest_embedded_nuttx/serial.py:
--------------------------------------------------------------------------------
1 | from typing import ClassVar, Dict
2 |
3 | import esptool
4 | from esptool.cmds import FLASH_MODES, LoadFirmwareImage
5 | from pytest_embedded_serial_esp.serial import EspSerial
6 |
7 | from .app import NuttxApp
8 | from .dut import NuttxSerialDut
9 |
10 |
11 | class NuttxSerial(EspSerial):
12 | """
13 | NuttX serial DUT class.
14 | """
15 |
16 | # Default offset for the primary MCUBoot slot across
17 | # all Espressif devices on NuttX
18 | MCUBOOT_PRIMARY_SLOT_OFFSET = 0x10000
19 | SERIAL_BAUDRATE = 115200
20 |
21 | binary_offsets: ClassVar[Dict[str, int]] = {
22 | 'esp32': 0x1000,
23 | 'esp32s2': 0x1000,
24 | 'esp32c3': 0x0,
25 | 'esp32s3': 0x0,
26 | 'esp32c6': 0x0,
27 | 'esp32h2': 0x0,
28 | 'esp32p4': 0x2000,
29 | }
30 |
31 | def __init__(
32 | self,
33 | app: NuttxApp,
34 | **kwargs,
35 | ) -> None:
36 | self.app = app
37 | super().__init__(**kwargs)
38 | self.flash_size = None
39 | self.flash_freq = None
40 | self.flash_mode = None
41 | self._get_binary_target_info()
42 |
43 | def _get_binary_target_info(self):
44 | """Binary target should be in the format nuttx.merged.bin, where
45 | the 'merged.bin' extension can be modified by the file_extension
46 | argument.
47 |
48 | Important note regarding MCUBoot:
49 | If enabled, the magic number will be on the MCUBoot binary. In this
50 | case, image_info should run on the mcuboot binary, not the NuttX one.
51 | """
52 |
53 | def get_key_from_value(dictionary, val):
54 | """Get key from value in dictionary"""
55 | for key, value in dictionary.items():
56 | if value == val:
57 | return key
58 | return None
59 |
60 | binary_path = self.app.app_file
61 | if self.app.bootloader_file:
62 | binary_path = self.app.bootloader_file
63 |
64 | # Load app image and retrieve flash information
65 | image = LoadFirmwareImage(self.target, binary_path.as_posix())
66 |
67 | # Flash Size
68 | flash_s_bits = image.flash_size_freq & 0xF0
69 | self.flash_size = get_key_from_value(image.ROM_LOADER.FLASH_SIZES, flash_s_bits)
70 |
71 | # Flash Frequency
72 | flash_fr_bits = image.flash_size_freq & 0x0F # low four bits
73 | self.flash_freq = get_key_from_value(image.ROM_LOADER.FLASH_FREQUENCY, flash_fr_bits)
74 |
75 | # Flash Mode
76 | self.flash_mode = get_key_from_value(FLASH_MODES, image.flash_mode)
77 |
78 | # From esp-idf/components/esptool_py:
79 | # We use esptool.py to flash bootloader in dio mode for QIO/QOUT, bootloader then
80 | # upgrades itself to quad mode during initialization.
81 | if self.flash_mode in ('qio', 'qout'):
82 | self.flash_mode = 'dio'
83 |
84 | @EspSerial.use_esptool()
85 | def flash(self) -> None:
86 | """Flash the binary files to the board."""
87 |
88 | flash_files = []
89 | if self.app.bootloader_file:
90 | flash_files.extend((str(self.binary_offsets[self.target]), self.app.bootloader_file.as_posix()))
91 | flash_files.extend((str(self.MCUBOOT_PRIMARY_SLOT_OFFSET), self.app.app_file.as_posix()))
92 | else:
93 | flash_files.extend((str(self.binary_offsets[self.target]), self.app.app_file.as_posix()))
94 |
95 | flash_settings = [
96 | '--flash_mode',
97 | self.flash_mode,
98 | '--flash_size',
99 | self.flash_size,
100 | '--flash_freq',
101 | self.flash_freq,
102 | ]
103 |
104 | esptool.main(
105 | [
106 | '--chip',
107 | self.target,
108 | '--port',
109 | self.port,
110 | '--baud',
111 | str(self.esptool_baud),
112 | 'write_flash',
113 | *flash_files,
114 | *flash_settings,
115 | ],
116 | esp=self.esp,
117 | )
118 |
119 |
120 | class NuttxEspDut(NuttxSerialDut):
121 | """
122 | DUT class for serial ports connected to Espressif boards which are
123 | flashed with NuttX RTOS.
124 | """
125 |
126 | def __init__(
127 | self,
128 | app: NuttxApp,
129 | **kwargs,
130 | ) -> None:
131 | super().__init__(app=app, **kwargs)
132 |
133 | def reset(self) -> None:
134 | """Resets the board."""
135 | self.serial: NuttxSerial
136 | self.serial.hard_reset()
137 |
--------------------------------------------------------------------------------
/pytest-embedded-nuttx/tests/test_nuttx.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 |
4 | import pytest
5 |
6 |
7 | def test_nuttx_app(testdir):
8 | testdir.makepyfile("""
9 | import pytest
10 |
11 | def test_nuttx_app(dut, app, target):
12 | assert 'esp32' == target
13 | assert '40m' == dut.serial.flash_freq
14 | assert '4MB' == dut.serial.flash_size
15 | assert 'dio' == dut.serial.flash_mode
16 |
17 | def test_app_flash(serial, dut):
18 | serial.erase_flash()
19 | serial.flash()
20 | dut.reset_to_nsh()
21 |
22 | def test_hello(dut):
23 | dut.reset_to_nsh()
24 | dut.write("ls /dev")
25 | dut.expect("console")
26 | """)
27 |
28 | result = testdir.runpytest(
29 | '-s',
30 | '--embedded-services',
31 | 'nuttx,esp',
32 | '--target',
33 | 'esp32',
34 | '--app-path',
35 | os.path.join(testdir.tmpdir, 'hello_world_nuttx'),
36 | )
37 |
38 | result.assert_outcomes(passed=3)
39 |
40 |
41 | def test_nuttx_app_mcuboot(testdir):
42 | testdir.makepyfile("""
43 | import pytest
44 |
45 | def test_nuttx_app_mcuboot(dut, app, target):
46 | assert 'esp32' == target
47 | assert '40m' == dut.serial.flash_freq
48 | assert '4MB' == dut.serial.flash_size
49 | assert 'dio' == dut.serial.flash_mode
50 | assert None != app.bootloader_file
51 |
52 | def test_nuttx_app_mcuboot_flash(serial, dut):
53 | serial.erase_flash()
54 | serial.flash()
55 | dut.reset_to_nsh()
56 |
57 | def test_hello_mcuboot(dut):
58 | dut.reset_to_nsh()
59 | dut.write("ls /dev")
60 | dut.expect("console")
61 | """)
62 |
63 | result = testdir.runpytest(
64 | '-s',
65 | '--embedded-services',
66 | 'nuttx,esp',
67 | '--target',
68 | 'esp32',
69 | '--app-path',
70 | os.path.join(testdir.tmpdir, 'hello_world_nuttx_mcuboot'),
71 | )
72 |
73 | result.assert_outcomes(passed=3)
74 |
75 |
76 | qemu_bin_required = pytest.mark.skipif(
77 | shutil.which('qemu-system-xtensa') is None,
78 | reason='Please make sure that `qemu-system-xtensa` is in your PATH env var. Build QEMU for ESP32 locally and then '
79 | 'run `pytest` again',
80 | )
81 |
82 |
83 | @qemu_bin_required
84 | def test_nuttx_app_qemu(testdir):
85 | testdir.makepyfile("""
86 | import pytest
87 | from time import sleep
88 |
89 | def test_nuttx_app_qemu(dut):
90 | # Wait boot sequence
91 | sleep(1)
92 | dut.write("ls /dev")
93 | dut.expect("console")
94 | """)
95 |
96 | efuse_path = os.path.join(testdir.tmpdir, 'hello_world_nuttx_qemu', 'qemu_efuse.bin')
97 | qemu_extra = f'-drive file={efuse_path},if=none,format=raw,id=efuse '
98 | qemu_extra += '-global driver=nvram.esp32.efuse,property=drive,value=efuse'
99 |
100 | print(qemu_extra)
101 | result = testdir.runpytest(
102 | '-s',
103 | '--embedded-services',
104 | 'nuttx,qemu',
105 | '--target',
106 | 'esp32',
107 | '--app-path',
108 | os.path.join(testdir.tmpdir, 'hello_world_nuttx_qemu'),
109 | '--qemu-image-path',
110 | os.path.join(testdir.tmpdir, 'hello_world_nuttx_qemu', 'nuttx.merged.bin'),
111 | '--qemu-extra-args',
112 | qemu_extra,
113 | '--qemu-prog-path',
114 | 'qemu-system-xtensa',
115 | )
116 |
117 | result.assert_outcomes(passed=1)
118 |
--------------------------------------------------------------------------------
/pytest-embedded-qemu/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pytest-embedded-qemu/README.md:
--------------------------------------------------------------------------------
1 | ### pytest-embedded-qemu
2 |
3 | pytest embedded service for running tests on QEMU instead of the real target.
4 |
5 | Extra Functionalities:
6 |
7 | ```{eval-rst}
8 | .. tabs::
9 |
10 | .. group-tab:: `pytest-embedded-idf` activated
11 |
12 | - `app`: create the qemu bootable image automatically by the built binaries.
13 | - `qemu`: enable the fixture
14 | - `dut`: duplicate the `qemu` output to `pexpect_proc`.
15 |
16 | .. group-tab:: `pytest-embedded-idf` NOT activated
17 |
18 | - `qemu`: enable the fixture
19 | - `dut`: duplicate the `qemu` output to `pexpect_proc`.
20 | ```
21 |
--------------------------------------------------------------------------------
/pytest-embedded-qemu/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "pytest-embedded-qemu"
7 | authors = [
8 | {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
9 | ]
10 | readme = "README.md"
11 | license = {file = "LICENSE"}
12 | classifiers = [
13 | "Development Status :: 5 - Production/Stable",
14 | "Framework :: Pytest",
15 | "Intended Audience :: Developers",
16 | "License :: OSI Approved :: MIT License",
17 | "Operating System :: OS Independent",
18 | "Programming Language :: Python :: 3 :: Only",
19 | "Programming Language :: Python :: 3",
20 | "Programming Language :: Python :: 3.7",
21 | "Programming Language :: Python :: 3.8",
22 | "Programming Language :: Python :: 3.9",
23 | "Programming Language :: Python :: 3.10",
24 | "Programming Language :: Python :: 3.11",
25 | "Programming Language :: Python :: 3.12",
26 | "Programming Language :: Python :: 3.13",
27 | "Programming Language :: Python",
28 | "Topic :: Software Development :: Testing",
29 | ]
30 | dynamic = ["version", "description"]
31 | requires-python = ">=3.7"
32 |
33 | dependencies = [
34 | "pytest-embedded~=1.16.1",
35 | "qemu.qmp==0.0.3"
36 | ]
37 |
38 | [project.optional-dependencies]
39 | idf = [
40 | "pytest-embedded-idf~=1.16.1",
41 | ]
42 |
43 | [project.urls]
44 | homepage = "https://github.com/espressif/pytest-embedded"
45 | repository = "https://github.com/espressif/pytest-embedded"
46 | documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
47 | changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
48 |
--------------------------------------------------------------------------------
/pytest-embedded-qemu/pytest_embedded_qemu/__init__.py:
--------------------------------------------------------------------------------
1 | """Make pytest-embedded plugin work with QEMU."""
2 |
3 | import importlib
4 |
5 | from pytest_embedded.utils import lazy_load
6 |
7 | DEFAULT_IMAGE_FN = 'flash_image.bin'
8 | ENCRYPTED_IMAGE_FN = f'encrypted_{DEFAULT_IMAGE_FN}'
9 |
10 | from .dut import QemuDut # noqa
11 | from .qemu import Qemu # noqa
12 |
13 |
14 | __getattr__ = lazy_load(
15 | importlib.import_module(__name__),
16 | {
17 | 'Qemu': Qemu,
18 | 'QemuDut': QemuDut,
19 | },
20 | {
21 | 'QemuApp': '.app', # requires idf
22 | },
23 | )
24 |
25 |
26 | __all__ = [
27 | 'DEFAULT_IMAGE_FN',
28 | 'ENCRYPTED_IMAGE_FN',
29 | 'Qemu',
30 | 'QemuApp',
31 | 'QemuDut',
32 | ]
33 |
34 | __version__ = '1.16.1'
35 |
--------------------------------------------------------------------------------
/pytest-embedded-qemu/pytest_embedded_qemu/dut.py:
--------------------------------------------------------------------------------
1 | from typing import AnyStr
2 |
3 | from pytest_embedded.dut import Dut
4 |
5 | from .qemu import Qemu
6 |
7 |
8 | class QemuDut(Dut):
9 | """
10 | QEMU dut class
11 | """
12 |
13 | def __init__(
14 | self,
15 | qemu: Qemu,
16 | **kwargs,
17 | ) -> None:
18 | self.qemu = qemu
19 |
20 | super().__init__(**kwargs)
21 |
22 | self._hard_reset_func = self.qemu._hard_reset
23 |
24 | def write(self, s: AnyStr) -> None:
25 | self.qemu.write(s)
26 |
27 | def hard_reset(self):
28 | self._hard_reset_func()
29 |
--------------------------------------------------------------------------------
/pytest-embedded-qemu/pytest_embedded_qemu/qemu.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import logging
3 | import os
4 | import shlex
5 | import socket
6 | import typing as t
7 |
8 | from pytest_embedded.log import DuplicateStdoutPopen
9 | from qemu.qmp import QMPClient
10 |
11 | from . import DEFAULT_IMAGE_FN
12 |
13 | if t.TYPE_CHECKING:
14 | from .app import QemuApp
15 |
16 |
17 | class Qemu(DuplicateStdoutPopen):
18 | """
19 | QEMU class
20 | """
21 |
22 | SOURCE = 'QEMU'
23 |
24 | QEMU_PROG_PATH = 'qemu-system-xtensa'
25 |
26 | QEMU_DEFAULT_ARGS = '-nographic -machine esp32'
27 | QEMU_DEFAULT_FMT = '-nographic -machine {}'
28 |
29 | QEMU_STRAP_MODE_FMT = '-global driver=esp32.gpio,property=strap_mode,value={}'
30 | QEMU_SERIAL_TCP_FMT = '-serial tcp::{},server,nowait'
31 |
32 | QEMU_DEFAULT_QMP_FMT = '-qmp tcp:127.0.0.1:{},server,wait=off'
33 |
34 | def __init__(
35 | self,
36 | qemu_image_path: t.Optional[str] = None,
37 | qemu_prog_path: t.Optional[str] = None,
38 | qemu_cli_args: t.Optional[str] = None,
39 | qemu_extra_args: t.Optional[str] = None,
40 | app: t.Optional['QemuApp'] = None,
41 | **kwargs,
42 | ):
43 | """
44 | Args:
45 | qemu_image_path: QEMU image path
46 | qemu_prog_path: QEMU program path
47 | qemu_cli_args: QEMU CLI arguments
48 | qemu_extra_args: QEMU CLI extra arguments, will be appended to `qemu_cli_args`
49 | """
50 | self.app = app
51 |
52 | image_path = qemu_image_path or DEFAULT_IMAGE_FN
53 | if not os.path.exists(image_path):
54 | raise ValueError(f"QEMU image path doesn't exist: {image_path}")
55 |
56 | qemu_prog_path = qemu_prog_path or self.qemu_prog_name
57 |
58 | if qemu_cli_args:
59 | qemu_cli_args = qemu_cli_args.strip('"').strip("'")
60 | qemu_cli_args = shlex.split(qemu_cli_args or self.qemu_default_args)
61 | qemu_extra_args = shlex.split(qemu_extra_args or '')
62 |
63 | self.qmp_addr = None
64 | self.qmp_port = None
65 |
66 | dut_index = int(kwargs.pop('dut_index', 0))
67 | for i, v in enumerate(qemu_cli_args):
68 | if v == '-qmp':
69 | d = qemu_cli_args[i + 1]
70 | if not d.startswith('tcp'):
71 | raise ValueError('Please use TCP for qmp, example: -qmp tcp:localhost:4488,server,wait=off')
72 | cmd = d.split(',')
73 | _, self.qmp_addr, self.qmp_port = cmd[0].split(':')
74 | self.qmp_port = int(self.qmp_port) + dut_index
75 | cmd[0] = f'tcp:{self.qmp_addr}:{self.qmp_port}'
76 | qemu_cli_args[i + 1] = ','.join(cmd)
77 | break
78 | else:
79 | self.qmp_addr = '127.0.0.1'
80 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
81 | s.bind((self.qmp_addr, 0))
82 | _, self.qmp_port = s.getsockname()
83 | qemu_cli_args += shlex.split(self.QEMU_DEFAULT_QMP_FMT.format(self.qmp_port))
84 |
85 | super().__init__(
86 | cmd=[qemu_prog_path, *qemu_cli_args, *qemu_extra_args, '-drive', f'file={image_path},if=mtd,format=raw'],
87 | **kwargs,
88 | )
89 |
90 | @property
91 | def qemu_prog_name(self):
92 | if self.app:
93 | return self.app.qemu_prog_path
94 |
95 | logging.warning('App not set, use default qemu program name "%s"', self.QEMU_DEFAULT_PROG_PATH)
96 | return self.QEMU_PROG_PATH
97 |
98 | @property
99 | def qemu_default_args(self):
100 | if self.app:
101 | try:
102 | return self.QEMU_DEFAULT_FMT.format(self.app.target)
103 | except AttributeError:
104 | pass
105 |
106 | return self.QEMU_DEFAULT_ARGS
107 |
108 | def qmp_execute_cmd(self, execute, arguments=None):
109 | response = None
110 |
111 | async def h_r():
112 | nonlocal response
113 |
114 | qmp = QMPClient()
115 | try:
116 | await qmp.connect((str(self.qmp_addr), int(self.qmp_port)))
117 | response = await qmp.execute(execute, arguments=arguments)
118 | finally:
119 | await qmp.disconnect()
120 |
121 | asyncio.run(h_r())
122 | return response
123 |
124 | def _hard_reset(self):
125 | self.qmp_execute_cmd('system_reset')
126 |
127 | def take_screenshot(self, image_path):
128 | self.qmp_execute_cmd('screendump', arguments={'filename': image_path})
129 |
--------------------------------------------------------------------------------
/pytest-embedded-qemu/tests/test_qemu.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | import xml.etree.ElementTree as ET
4 |
5 | import pytest
6 |
7 | qemu_bin_required = pytest.mark.skipif(
8 | shutil.which('qemu-system-xtensa') is None,
9 | reason='Please make sure that `qemu-system-xtensa` is in your PATH env var. Build QEMU for ESP32 locally and then '
10 | 'run `pytest` again',
11 | )
12 |
13 |
14 | @qemu_bin_required
15 | def test_pexpect_by_qemu_xtensa(testdir):
16 | testdir.makepyfile("""
17 | import pexpect
18 | import pytest
19 |
20 | def test_pexpect_by_qemu(dut):
21 | dut.expect('Hello world!')
22 | dut.expect('Restarting')
23 | with pytest.raises(pexpect.TIMEOUT):
24 | dut.expect('foo bar not found', timeout=1)
25 | """)
26 |
27 | result = testdir.runpytest(
28 | '-s',
29 | '--embedded-services',
30 | 'idf,qemu',
31 | '--app-path',
32 | os.path.join(testdir.tmpdir, 'hello_world_esp32'),
33 | )
34 |
35 | result.assert_outcomes(passed=1)
36 |
37 |
38 | @qemu_bin_required
39 | def test_pexpect_make_restart_by_qemu_xtensa(testdir):
40 | testdir.makepyfile("""
41 | import pexpect
42 | import pytest
43 |
44 | def test_pexpect_by_qemu(dut):
45 | dut.expect('Hello world!')
46 | dut.hard_reset()
47 | dut.expect('cpu_start')
48 | dut.expect('Hello world!')
49 | dut.hard_reset()
50 | dut.expect('cpu_start')
51 | dut.expect('Hello world!')
52 | """)
53 |
54 | result = testdir.runpytest(
55 | '-s',
56 | '--embedded-services',
57 | 'idf,qemu',
58 | '--app-path',
59 | os.path.join(testdir.tmpdir, 'hello_world_esp32'),
60 | '--qemu-cli-args="-machine esp32 -nographic"',
61 | )
62 |
63 | result.assert_outcomes(passed=1)
64 |
65 |
66 | @qemu_bin_required
67 | def test_pexpect_by_qemu_riscv(testdir):
68 | testdir.makepyfile("""
69 | import pexpect
70 | import pytest
71 |
72 | def test_pexpect_by_qemu(dut):
73 | dut.expect('Hello world!')
74 | dut.expect('Restarting')
75 | with pytest.raises(pexpect.TIMEOUT):
76 | dut.expect('foo bar not found', timeout=1)
77 | """)
78 |
79 | result = testdir.runpytest(
80 | '-s',
81 | '--embedded-services',
82 | 'idf,qemu',
83 | '--app-path',
84 | os.path.join(testdir.tmpdir, 'hello_world_esp32c3'),
85 | )
86 |
87 | result.assert_outcomes(passed=1)
88 |
89 |
90 | @qemu_bin_required
91 | def test_multi_count_qemu(testdir):
92 | testdir.makepyfile("""
93 | import pexpect
94 | import pytest
95 |
96 | def test_pexpect_by_qemu(dut):
97 | dut[0].expect('Hello world!')
98 | dut[1].expect('Restarting')
99 | """)
100 |
101 | result = testdir.runpytest(
102 | '-s',
103 | '--count',
104 | 2,
105 | '--embedded-services',
106 | 'idf,qemu|qemu',
107 | '--app-path',
108 | f'{os.path.join(testdir.tmpdir, "hello_world_esp32")}|',
109 | '--qemu-image-path',
110 | f'|{os.path.join(testdir.tmpdir, "esp32_qemu.bin")}',
111 | )
112 |
113 | result.assert_outcomes(passed=1)
114 |
115 |
116 | @qemu_bin_required
117 | def test_pre_flash_enc_qemu(testdir):
118 | testdir.makepyfile("""
119 | import pexpect
120 | import pytest
121 |
122 | def test_pexpect_by_qemu(dut):
123 | dut.expect('Hello world!', timeout=120)
124 | dut.expect('Restarting')
125 | with pytest.raises(pexpect.TIMEOUT):
126 | dut.expect('foo bar not found', timeout=1)
127 | """)
128 |
129 | app_path = os.path.join(testdir.tmpdir, 'hello_world_esp32_flash_enc')
130 | keyfile_path = os.path.join(app_path, 'pre_encryption_key.bin')
131 | efuses_path = os.path.join(app_path, 'pre_encryption_efuses.bin')
132 |
133 | result = testdir.runpytest(
134 | '-s',
135 | '--embedded-services',
136 | 'idf,qemu',
137 | '--app-path',
138 | app_path,
139 | '--qemu-extra-args',
140 | f'-drive file={efuses_path},if=none,format=raw,id=efuse'
141 | ' -global driver=nvram.esp32.efuse,property=drive,value=efuse',
142 | '--encrypt',
143 | 'true',
144 | '--keyfile',
145 | keyfile_path,
146 | )
147 |
148 | result.assert_outcomes(passed=1)
149 |
150 |
151 | @qemu_bin_required
152 | def test_qemu_use_idf_mixin_methods(testdir):
153 | testdir.makepyfile("""
154 | import pexpect
155 | import pytest
156 |
157 | def test_qemu_use_idf_mixin_methods(dut):
158 | dut.run_all_single_board_cases()
159 | """)
160 |
161 | result = testdir.runpytest(
162 | '-s',
163 | '--embedded-services',
164 | 'idf,qemu',
165 | '--app-path',
166 | f'{os.path.join(testdir.tmpdir, "unit_test_app_esp32")}',
167 | '--junitxml',
168 | 'report.xml',
169 | )
170 |
171 | result.assert_outcomes(failed=1)
172 |
173 | junit_report = ET.parse('report.xml').getroot()[0]
174 |
175 | assert junit_report.attrib['errors'] == '0'
176 | assert junit_report.attrib['failures'] == '1'
177 | assert junit_report.attrib['skipped'] == '0'
178 | assert junit_report.attrib['tests'] == '2'
179 |
--------------------------------------------------------------------------------
/pytest-embedded-serial-esp/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pytest-embedded-serial-esp/README.md:
--------------------------------------------------------------------------------
1 | ### pytest-embedded-serial-esp
2 |
3 | pytest embedded service for testing espressif boards via serial ports
4 |
5 | Extra Functionalities:
6 |
7 | - `serial`: detect and confirm target and port by `esptool` automatically.
8 |
--------------------------------------------------------------------------------
/pytest-embedded-serial-esp/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "pytest-embedded-serial-esp"
7 | authors = [
8 | {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
9 | ]
10 | readme = "README.md"
11 | license = {file = "LICENSE"}
12 | classifiers = [
13 | "Development Status :: 5 - Production/Stable",
14 | "Framework :: Pytest",
15 | "Intended Audience :: Developers",
16 | "License :: OSI Approved :: MIT License",
17 | "Operating System :: OS Independent",
18 | "Programming Language :: Python :: 3 :: Only",
19 | "Programming Language :: Python :: 3",
20 | "Programming Language :: Python :: 3.7",
21 | "Programming Language :: Python :: 3.8",
22 | "Programming Language :: Python :: 3.9",
23 | "Programming Language :: Python :: 3.10",
24 | "Programming Language :: Python :: 3.11",
25 | "Programming Language :: Python :: 3.12",
26 | "Programming Language :: Python :: 3.13",
27 | "Programming Language :: Python",
28 | "Topic :: Software Development :: Testing",
29 | ]
30 | dynamic = ["version", "description"]
31 | requires-python = ">=3.7"
32 |
33 | dependencies = [
34 | "pytest-embedded-serial~=1.16.1",
35 | "esptool~=4.5",
36 | ]
37 |
38 | [project.urls]
39 | homepage = "https://github.com/espressif/pytest-embedded"
40 | repository = "https://github.com/espressif/pytest-embedded"
41 | documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
42 | changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
43 |
--------------------------------------------------------------------------------
/pytest-embedded-serial-esp/pytest_embedded_serial_esp/__init__.py:
--------------------------------------------------------------------------------
1 | """Make pytest-embedded plugin work with Espressif target boards."""
2 |
3 | from .serial import EspSerial
4 |
5 | __all__ = [
6 | 'EspSerial',
7 | ]
8 |
9 | __version__ = '1.16.1'
10 |
--------------------------------------------------------------------------------
/pytest-embedded-serial-esp/tests/test_esp.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | import pytest
4 |
5 |
6 | def test_detect_port(testdir):
7 | testdir.makepyfile(
8 | """
9 | def test_detect_port(dut):
10 | assert dut[0].serial.target == 'esp32s2'
11 | assert dut[1].serial.target == 'esp32'
12 | """
13 | )
14 |
15 | result = testdir.runpytest(
16 | '-s',
17 | '--count',
18 | 2,
19 | '--embedded-services',
20 | 'esp',
21 | '--target',
22 | 'esp32s2|esp32',
23 | )
24 |
25 | result.assert_outcomes(passed=1)
26 |
27 |
28 | def test_detect_port_with_cache(testdir, caplog, first_index_of_messages):
29 | testdir.makepyfile(
30 | """
31 | def test_detect_port(dut):
32 | assert dut[0].serial.target == 'esp32s2'
33 | assert dut[1].serial.target == 'esp32'
34 |
35 | def test_detect_port_again(dut):
36 | assert dut[0].serial.target == 'esp32s2'
37 | assert dut[1].serial.target == 'esp32'
38 | """
39 | )
40 |
41 | result = testdir.runpytest(
42 | '-s',
43 | '--count',
44 | 2,
45 | '--embedded-services',
46 | 'esp',
47 | '--target',
48 | 'esp32s2|esp32',
49 | '--log-cli-level',
50 | 'DEBUG',
51 | )
52 |
53 | result.assert_outcomes(passed=2)
54 |
55 | esp32s2_set_cache_index = first_index_of_messages(
56 | re.compile('^set port-target cache: .+ - esp32s2$', re.MULTILINE), caplog.messages
57 | )
58 | esp32_set_cache_index = first_index_of_messages(
59 | re.compile('^set port-target cache: .+ - esp32$', re.MULTILINE), caplog.messages, esp32s2_set_cache_index + 1
60 | )
61 | esp32s2_hit_cache_index = first_index_of_messages(
62 | re.compile('^hit port-target cache: .+ - esp32s2$', re.MULTILINE), caplog.messages, esp32_set_cache_index + 1
63 | )
64 | first_index_of_messages(
65 | re.compile('^hit port-target cache: .+ - esp32$', re.MULTILINE), caplog.messages, esp32s2_hit_cache_index + 1
66 | )
67 |
68 |
69 | def test_detect_port_with_local_cache(testdir):
70 | pytest.global_port_target_cache = {}
71 |
72 | testdir.makepyfile(r"""
73 | import pytest
74 |
75 | def test_empty_port_target_cache_before_init_devices(port_target_cache):
76 | assert isinstance(port_target_cache, dict)
77 | assert port_target_cache == pytest.global_port_target_cache
78 |
79 | def test_init_devices(dut, port_target_cache):
80 | pytest.global_port_target_cache = port_target_cache
81 |
82 | def test_empty_port_target_cache_after_init_devices(port_target_cache):
83 | assert port_target_cache == pytest.global_port_target_cache
84 | """)
85 | result = testdir.runpytest(
86 | '-s',
87 | '--embedded-services',
88 | 'esp',
89 | '--cache-dir',
90 | './cache-test',
91 | '--count',
92 | '2',
93 | )
94 | result.assert_outcomes(passed=3)
95 |
96 | testdir.makepyfile("""
97 | import pytest
98 |
99 | def test_load_local_saved_port_target_cache(port_target_cache):
100 | assert port_target_cache == pytest.global_port_target_cache
101 | """)
102 | result = testdir.runpytest(
103 | '-s',
104 | '--embedded-services',
105 | 'esp',
106 | '--cache-dir',
107 | './cache-test',
108 | '--count',
109 | '2',
110 | )
111 | result.assert_outcomes(passed=1)
112 |
--------------------------------------------------------------------------------
/pytest-embedded-serial/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pytest-embedded-serial/README.md:
--------------------------------------------------------------------------------
1 | ### pytest-embedded-serial
2 |
3 | pytest embedded service for testing via serial ports
4 |
5 | Extra Functionalities:
6 |
7 | - `serial`: enable the fixture
8 | - `dut`: duplicate the `serial` output to `pexpect_proc`.
9 |
--------------------------------------------------------------------------------
/pytest-embedded-serial/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "pytest-embedded-serial"
7 | authors = [
8 | {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
9 | ]
10 | readme = "README.md"
11 | license = {file = "LICENSE"}
12 | classifiers = [
13 | "Development Status :: 5 - Production/Stable",
14 | "Framework :: Pytest",
15 | "Intended Audience :: Developers",
16 | "License :: OSI Approved :: MIT License",
17 | "Operating System :: OS Independent",
18 | "Programming Language :: Python :: 3 :: Only",
19 | "Programming Language :: Python :: 3",
20 | "Programming Language :: Python :: 3.7",
21 | "Programming Language :: Python :: 3.8",
22 | "Programming Language :: Python :: 3.9",
23 | "Programming Language :: Python :: 3.10",
24 | "Programming Language :: Python :: 3.11",
25 | "Programming Language :: Python :: 3.12",
26 | "Programming Language :: Python :: 3.13",
27 | "Programming Language :: Python",
28 | "Topic :: Software Development :: Testing",
29 | ]
30 | dynamic = ["version", "description"]
31 | requires-python = ">=3.7"
32 |
33 | dependencies = [
34 | "pytest-embedded~=1.16.1",
35 | "pyserial~=3.0",
36 | ]
37 |
38 | [project.urls]
39 | homepage = "https://github.com/espressif/pytest-embedded"
40 | repository = "https://github.com/espressif/pytest-embedded"
41 | documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
42 | changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
43 |
--------------------------------------------------------------------------------
/pytest-embedded-serial/pytest_embedded_serial/__init__.py:
--------------------------------------------------------------------------------
1 | """Make pytest-embedded plugin work with Serial."""
2 |
3 | from .dut import SerialDut
4 | from .serial import Serial
5 |
6 | __all__ = [
7 | 'Serial',
8 | 'SerialDut',
9 | ]
10 |
11 | __version__ = '1.16.1'
12 |
--------------------------------------------------------------------------------
/pytest-embedded-serial/pytest_embedded_serial/dut.py:
--------------------------------------------------------------------------------
1 | from typing import TYPE_CHECKING, AnyStr, Optional
2 |
3 | from pytest_embedded.dut import Dut
4 | from pytest_embedded.utils import to_bytes
5 |
6 | from .serial import Serial
7 |
8 | if TYPE_CHECKING:
9 | from pytest_embedded_jtag import Gdb, OpenOcd, Telnet
10 |
11 |
12 | class SerialDut(Dut):
13 | """
14 | Dut class for serial ports
15 |
16 | Attributes:
17 | serial (Serial): `Serial` instance
18 | openocd (OpenOcd): `OpenOcd` instance, applied only when `jtag` service is activated
19 | gdb (Gdb): `Gdb` instance, applied only when `jtag` service is activated
20 | telnet (Telnet): `Telnet` instance, applied only when `jtag` service is activated
21 | """
22 |
23 | def __init__(
24 | self,
25 | serial: Serial,
26 | openocd: Optional['OpenOcd'] = None,
27 | gdb: Optional['Gdb'] = None,
28 | telnet: Optional['Telnet'] = None,
29 | **kwargs,
30 | ) -> None:
31 | super().__init__(**kwargs)
32 |
33 | self.serial = serial
34 | self.openocd = openocd
35 | self.gdb = gdb
36 | self.telnet = telnet
37 |
38 | self.setup_jtag()
39 |
40 | def write(self, data: AnyStr) -> None:
41 | self.serial.proc.write(to_bytes(data, '\n'))
42 |
43 | def setup_jtag(self):
44 | if self.gdb:
45 | self.gdb.write('set remotetimeout 10')
46 | self.gdb.write(f'target extended-remote :{self.openocd.gdb_port}')
47 |
--------------------------------------------------------------------------------
/pytest-embedded-serial/tests/test_serial.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | import pytest
4 |
5 |
6 | def test_custom_serial_device(testdir):
7 | testdir.makepyfile(r"""
8 | import pytest
9 |
10 | def test_serial_mixed(dut):
11 | from pytest_embedded.dut_factory import DutFactory
12 | assert len(dut)==2
13 | another_dut = DutFactory.create()
14 | st = set(
15 | (
16 | dut[0].serial.port,
17 | dut[1].serial.port,
18 | another_dut.serial.port
19 | )
20 | )
21 | assert len(st) == 3
22 |
23 | def test_custom_dut():
24 | from pytest_embedded.dut_factory import DutFactory
25 | another_dut = DutFactory.create(embedded_services='esp,serial')
26 | """)
27 |
28 | result = testdir.runpytest(
29 | '-s',
30 | '--embedded-services',
31 | 'esp,serial',
32 | '--count',
33 | 2,
34 | )
35 | result.assert_outcomes(passed=2, errors=0)
36 |
37 |
38 | def test_custom_serial_device_dut_count_1(testdir):
39 | testdir.makepyfile(r"""
40 | import pytest
41 |
42 | def test_serial_device_created_dut_count_1(dut):
43 | from pytest_embedded.dut_factory import DutFactory
44 | another_dut = DutFactory.create()
45 | another_dut2 = DutFactory.create()
46 | st = set(
47 | (
48 | dut.serial.port,
49 | another_dut.serial.port,
50 | another_dut2.serial.port
51 | )
52 | )
53 | assert len(st) == 3
54 |
55 |
56 | """)
57 |
58 | result = testdir.runpytest(
59 | '-s',
60 | '--embedded-services',
61 | 'esp,serial',
62 | '--count',
63 | 1,
64 | )
65 | result.assert_outcomes(passed=1, errors=0)
66 |
67 |
68 | @pytest.mark.skipif(sys.platform == 'win32', reason='No socat support on windows')
69 | @pytest.mark.flaky(reruns=3, reruns_delay=2)
70 | def test_serial_port(testdir):
71 | testdir.makepyfile(r"""
72 | import pytest
73 | import subprocess
74 |
75 | @pytest.fixture(autouse=True)
76 | def open_tcp_port():
77 | proc = subprocess.Popen('socat TCP4-LISTEN:9876,fork EXEC:cat', shell=True)
78 | yield
79 | proc.terminate()
80 |
81 | def test_serial_port(dut):
82 | dut.write(b'hello world\n')
83 | dut.expect('hello world')
84 | """)
85 |
86 | result = testdir.runpytest(
87 | '-s',
88 | '--embedded-services',
89 | 'serial',
90 | '--port',
91 | 'socket://localhost:9876',
92 | )
93 |
94 | result.assert_outcomes(passed=1)
95 |
96 |
97 | def test_teardown_called_for_multi_dut(testdir):
98 | testdir.makepyfile(r"""
99 | import pytest
100 |
101 | @pytest.mark.parametrize('count, embedded_services, port', [
102 | ('3', 'serial', '/dev/ttyUSB0|/dev/ttyUSB1|/dev/ttyUSB100'), # set up failure
103 | ], indirect=True)
104 | def test_teardown_called_for_multi_dut_fail(dut):
105 | assert len(dut) == 3
106 |
107 | @pytest.mark.parametrize('count, embedded_services, port', [
108 | ('3', 'serial', '/dev/ttyUSB0|/dev/ttyUSB1|/dev/ttyUSB2'), # set up succeeded
109 | ], indirect=True)
110 | def test_teardown_called_for_multi_dut_succeeded(dut):
111 | assert dut[0].serial.port == '/dev/ttyUSB0'
112 | assert dut[1].serial.port == '/dev/ttyUSB1'
113 | assert dut[2].serial.port == '/dev/ttyUSB2'
114 | """)
115 |
116 | result = testdir.runpytest()
117 | result.assert_outcomes(passed=1, errors=1)
118 |
--------------------------------------------------------------------------------
/pytest-embedded-wokwi/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pytest-embedded-wokwi/README.md:
--------------------------------------------------------------------------------
1 | ### pytest-embedded-wokwi
2 |
3 | pytest-embedded service for running tests on [Wokwi](https://wokwi.com/ci) instead of the real target.
4 |
5 | Wokwi supports most ESP32 targets, including: esp32, esp32s2, esp32s3, esp32c3, esp32c6, and esp32h2. In addition, it supports a [wide range of peripherals](https://docs.wokwi.com/getting-started/supported-hardware), including sensors, displays, motors, and debugging tools.
6 |
7 | Running the tests with Wokwi requires an internet connection. Your firmware is uploaded to the Wokwi server for the duration of the simulation, but it is not saved on the server. On-premises Wokwi installations are available for enterprise customers.
8 |
9 | #### Wokwi CLI installation
10 |
11 | The Wokwi plugin uses the [Wokwi CLI](https://github.com/wokwi/wokwi-cli) to interact with the wokwi simulation server. You can download the precompiled CLI binaries from the [releases page](https://github.com/wokwi/wokwi-cli/releases). Alternatively, on Linux or Mac OS, you can install the CLI using the following command:
12 |
13 | ```bash
14 | curl -L https://wokwi.com/ci/install.sh | sh
15 | ```
16 |
17 | And on Windows:
18 |
19 | ```powershell
20 | iwr https://wokwi.com/ci/install.ps1 -useb | iex
21 | ```
22 |
23 | #### Wokwi API Tokens
24 |
25 | Before using this plugin, you need to create a free Wokwi account and [generate an API key](https://wokwi.com/dashboard/ci). You can then set the `WOKWI_CLI_TOKEN` environment variable to the API key.
26 |
27 | Linux / Mac OS / WSL:
28 |
29 | ```bash
30 | export WOKWI_CLI_TOKEN="your-api-key"
31 | ```
32 |
33 | Windows PowerShell:
34 |
35 | ```powershell
36 | $env:WOKWI_CLI_TOKEN="your-api-key"
37 | ```
38 |
39 | #### Usage
40 |
41 | To run your tests with Wokwi, make sure to specify the `wokwi` service when running pytest, e.g.:
42 |
43 | ```
44 | pytest --embedded-services idf,wokwi
45 | ```
46 |
47 | To limit the amount of simulation time, use the `--wokwi-timeout` flag. For example, to set the simulation time limit to 60 seconds (60000 milliseconds):
48 |
49 | ```
50 | pytest --embedded-services idf,wokwi --wokwi-timeout=60000
51 | ```
52 |
--------------------------------------------------------------------------------
/pytest-embedded-wokwi/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "pytest-embedded-wokwi"
7 | authors = [
8 | {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
9 | {name = "Uri Shaked", email = "uri@wokwi.com"},
10 | ]
11 | readme = "README.md"
12 | license = {file = "LICENSE"}
13 | classifiers = [
14 | "Development Status :: 5 - Production/Stable",
15 | "Framework :: Pytest",
16 | "Intended Audience :: Developers",
17 | "License :: OSI Approved :: MIT License",
18 | "Operating System :: OS Independent",
19 | "Programming Language :: Python :: 3 :: Only",
20 | "Programming Language :: Python :: 3",
21 | "Programming Language :: Python :: 3.7",
22 | "Programming Language :: Python :: 3.8",
23 | "Programming Language :: Python :: 3.9",
24 | "Programming Language :: Python :: 3.10",
25 | "Programming Language :: Python :: 3.11",
26 | "Programming Language :: Python :: 3.12",
27 | "Programming Language :: Python :: 3.13",
28 | "Programming Language :: Python",
29 | "Topic :: Software Development :: Testing",
30 | ]
31 | dynamic = ["version", "description"]
32 | requires-python = ">=3.7"
33 |
34 | dependencies = [
35 | "pytest-embedded~=1.16.1",
36 | "toml~=0.10.2",
37 | ]
38 |
39 | [project.optional-dependencies]
40 | idf = [
41 | "pytest-embedded-idf~=1.16.1",
42 | ]
43 |
44 | [project.urls]
45 | homepage = "https://github.com/espressif/pytest-embedded"
46 | repository = "https://github.com/espressif/pytest-embedded"
47 | documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
48 | changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
49 |
--------------------------------------------------------------------------------
/pytest-embedded-wokwi/pytest_embedded_wokwi/__init__.py:
--------------------------------------------------------------------------------
1 | """Make pytest-embedded plugin work with the Wokwi CLI."""
2 |
3 | WOKWI_CLI_MINIMUM_VERSION = '0.10.1'
4 |
5 | from .dut import WokwiDut # noqa
6 | from .wokwi_cli import WokwiCLI # noqa
7 |
8 | __all__ = [
9 | 'WOKWI_CLI_MINIMUM_VERSION',
10 | 'WokwiCLI',
11 | 'WokwiDut',
12 | ]
13 |
14 | __version__ = '1.16.1'
15 |
--------------------------------------------------------------------------------
/pytest-embedded-wokwi/pytest_embedded_wokwi/arduino.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 | from pathlib import Path
3 |
4 | if t.TYPE_CHECKING:
5 | from pytest_embedded_arduino.app import ArduinoApp
6 |
7 |
8 | class ArduinoFirmwareResolver:
9 | """
10 | ArduinoFirmwareResolver class
11 | """
12 |
13 | def resolve_firmware(self, app: 'ArduinoApp'):
14 | # get path of ino.bin file
15 | return Path(app.binary_path, app.sketch + '.ino.merged.bin')
16 |
--------------------------------------------------------------------------------
/pytest-embedded-wokwi/pytest_embedded_wokwi/dut.py:
--------------------------------------------------------------------------------
1 | from typing import AnyStr
2 |
3 | from pytest_embedded.dut import Dut
4 |
5 | from .wokwi_cli import WokwiCLI
6 |
7 |
8 | class WokwiDut(Dut):
9 | """
10 | Wokwi DUT class
11 | """
12 |
13 | def __init__(
14 | self,
15 | wokwi: WokwiCLI,
16 | **kwargs,
17 | ) -> None:
18 | self.wokwi = wokwi
19 |
20 | super().__init__(**kwargs)
21 |
22 | self._hard_reset_func = self.wokwi._hard_reset
23 |
24 | def write(self, s: AnyStr) -> None:
25 | self.wokwi.write(s)
26 |
--------------------------------------------------------------------------------
/pytest-embedded-wokwi/pytest_embedded_wokwi/idf.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 | from pathlib import Path
3 |
4 | if t.TYPE_CHECKING:
5 | from pytest_embedded_idf.app import IdfApp
6 |
7 |
8 | class IDFFirmwareResolver:
9 | """
10 | IDFFirmwareResolver class
11 | """
12 |
13 | def resolve_firmware(self, app: 'IdfApp'):
14 | return Path(app.binary_path, 'flasher_args.json')
15 |
--------------------------------------------------------------------------------
/pytest-embedded-wokwi/pytest_embedded_wokwi/wokwi_cli.py:
--------------------------------------------------------------------------------
1 | import json
2 | import logging
3 | import os
4 | import re
5 | import shutil
6 | import subprocess
7 | import typing as t
8 | from pathlib import Path
9 |
10 | import toml
11 | from packaging.version import Version
12 | from pytest_embedded import __version__
13 | from pytest_embedded.log import DuplicateStdoutPopen
14 |
15 | from pytest_embedded_wokwi import WOKWI_CLI_MINIMUM_VERSION
16 |
17 | from .idf import IDFFirmwareResolver
18 |
19 | if t.TYPE_CHECKING:
20 | from pytest_embedded_idf.app import IdfApp
21 |
22 |
23 | target_to_board = {
24 | 'esp32': 'board-esp32-devkit-c-v4',
25 | 'esp32c3': 'board-esp32-c3-devkitm-1',
26 | 'esp32c6': 'board-esp32-c6-devkitc-1',
27 | 'esp32h2': 'board-esp32-h2-devkitm-1',
28 | 'esp32p4': 'board-esp32-p4-function-ev',
29 | 'esp32s2': 'board-esp32-s2-devkitm-1',
30 | 'esp32s3': 'board-esp32-s3-devkitc-1',
31 | }
32 |
33 |
34 | class WokwiCLI(DuplicateStdoutPopen):
35 | """
36 | WokwiCLI class
37 | """
38 |
39 | SOURCE = 'Wokwi'
40 | WOKWI_CLI_PATH = 'wokwi-cli'
41 |
42 | def __init__(
43 | self,
44 | firmware_resolver: IDFFirmwareResolver,
45 | wokwi_cli_path: t.Optional[str] = None,
46 | wokwi_timeout: t.Optional[int] = None,
47 | wokwi_scenario: t.Optional[str] = None,
48 | wokwi_diagram: t.Optional[str] = None,
49 | app: t.Optional['IdfApp'] = None,
50 | **kwargs,
51 | ):
52 | """
53 | Args:
54 | wokwi_cli_path: Wokwi CLI arguments
55 | """
56 | self.app = app
57 | self.firmware_resolver = firmware_resolver
58 |
59 | # first need to check if wokwi-cli exists in PATH
60 | if shutil.which('wokwi-cli') is None:
61 | raise RuntimeError('Please install wokwi-cli, by running: curl -L https://wokwi.com/ci/install.sh | sh')
62 |
63 | output = subprocess.check_output(['wokwi-cli', '--help'])
64 | try:
65 | wokwi_cli_version = re.match(r'Wokwi CLI v(\d+\.\d+\.\d+)', output.decode('utf-8')).group(1)
66 | except AttributeError:
67 | logging.warning('Failed to get wokwi-cli version, assume version requirements satisfied')
68 | else:
69 | if Version(wokwi_cli_version) < Version(WOKWI_CLI_MINIMUM_VERSION):
70 | raise ValueError(
71 | f'Wokwi CLI version {wokwi_cli_version} is not supported. '
72 | f'Minimum version required: {WOKWI_CLI_MINIMUM_VERSION}. '
73 | f'To update Wokwi CLI run: curl -L https://wokwi.com/ci/install.sh | sh'
74 | )
75 |
76 | self.create_wokwi_toml()
77 |
78 | if wokwi_diagram is None:
79 | self.create_diagram_json()
80 |
81 | wokwi_cli = wokwi_cli_path or self.wokwi_cli_executable
82 | cmd = [wokwi_cli, '--interactive', app.app_path]
83 | if (wokwi_timeout is not None) and (wokwi_timeout > 0):
84 | cmd.extend(['--timeout', str(wokwi_timeout)])
85 | if (wokwi_scenario is not None) and os.path.exists(wokwi_scenario):
86 | cmd.extend(['--scenario', wokwi_scenario])
87 | if (wokwi_diagram is not None) and os.path.exists(wokwi_diagram):
88 | cmd.extend(['--diagram-file', wokwi_diagram])
89 |
90 | super().__init__(
91 | cmd=cmd,
92 | **kwargs,
93 | )
94 |
95 | @property
96 | def wokwi_cli_executable(self):
97 | return self.WOKWI_CLI_PATH
98 |
99 | def create_wokwi_toml(self):
100 | app = self.app
101 | flasher_args = self.firmware_resolver.resolve_firmware(app)
102 | wokwi_toml_path = os.path.join(app.app_path, 'wokwi.toml')
103 | firmware_path = Path(os.path.relpath(flasher_args, app.app_path)).as_posix()
104 | elf_path = Path(os.path.relpath(app.elf_file, app.app_path)).as_posix()
105 |
106 | if os.path.exists(wokwi_toml_path):
107 | with open(wokwi_toml_path) as f:
108 | toml_data = toml.load(f)
109 |
110 | if 'wokwi' not in toml_data:
111 | toml_data['wokwi'] = {'version': 1}
112 |
113 | wokwi_table = toml_data['wokwi']
114 | if wokwi_table.get('firmware') == firmware_path and wokwi_table.get('elf') == elf_path:
115 | # No need to update
116 | return
117 |
118 | wokwi_table.update({'firmware': firmware_path, 'elf': elf_path})
119 | else:
120 | toml_data = {
121 | 'wokwi': {
122 | 'version': 1,
123 | 'generatedBy': f'pytest-embedded-wokwi {__version__}',
124 | 'firmware': firmware_path,
125 | 'elf': elf_path,
126 | }
127 | }
128 |
129 | with open(wokwi_toml_path, 'w') as f:
130 | toml.dump(toml_data, f)
131 |
132 | def create_diagram_json(self):
133 | app = self.app
134 | target_board = target_to_board[app.target]
135 |
136 | # Check for common diagram.json file
137 | diagram_json_path = os.path.join(app.app_path, 'diagram.json')
138 | if os.path.exists(diagram_json_path):
139 | with open(diagram_json_path) as f:
140 | json_data = json.load(f)
141 | if not any(part['type'] == target_board for part in json_data['parts']):
142 | logging.warning(
143 | f'diagram.json exists, no part with type "{target_board}" found. '
144 | + 'You may need to update the diagram.json file manually to match the target board.'
145 | )
146 | return
147 |
148 | if app.target == 'esp32p4':
149 | rx_pin = '38'
150 | tx_pin = '37'
151 | else:
152 | rx_pin = 'RX'
153 | tx_pin = 'TX'
154 |
155 | diagram = {
156 | 'version': 1,
157 | 'author': 'Uri Shaked',
158 | 'editor': 'wokwi',
159 | 'parts': [{'type': target_board, 'id': 'esp'}],
160 | 'connections': [
161 | ['esp:' + tx_pin, '$serialMonitor:RX', ''],
162 | ['esp:' + rx_pin, '$serialMonitor:TX', ''],
163 | ],
164 | }
165 | with open(diagram_json_path, 'w') as f:
166 | f.write(json.dumps(diagram, indent=2))
167 |
168 | def _hard_reset(self):
169 | """
170 | This is a fake hard_reset. Keep this API to keep the consistency.
171 | """
172 | raise NotImplementedError
173 |
--------------------------------------------------------------------------------
/pytest-embedded-wokwi/tests/test_wokwi.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 |
4 | import pytest
5 |
6 | wokwi_cli_required = pytest.mark.skipif(
7 | shutil.which('wokwi-cli') is None,
8 | reason='Please make sure that `wokwi-cli` is in your PATH env var. '
9 | + 'To install: https://docs.wokwi.com/wokwi-ci/getting-started#cli-installation',
10 | )
11 |
12 | wokwi_token_required = pytest.mark.skipif(
13 | os.getenv('WOKWI_CLI_TOKEN') is None,
14 | reason='Please make sure that `WOKWI_CLI_TOKEN` env var is set. Get a token here: https://wokwi.com/dashboard/ci',
15 | )
16 |
17 |
18 | @wokwi_cli_required
19 | @wokwi_token_required
20 | def test_pexpect_by_wokwi_esp32(testdir):
21 | testdir.makepyfile("""
22 | import pexpect
23 | import pytest
24 |
25 | def test_pexpect_by_wokwi(dut):
26 | dut.expect('Hello world!')
27 | dut.expect('Restarting')
28 | with pytest.raises(pexpect.TIMEOUT):
29 | dut.expect('foo bar not found', timeout=1)
30 | """)
31 |
32 | result = testdir.runpytest(
33 | '-s',
34 | '--embedded-services',
35 | 'idf,wokwi',
36 | '--app-path',
37 | os.path.join(testdir.tmpdir, 'hello_world_esp32'),
38 | )
39 |
40 | result.assert_outcomes(passed=1)
41 |
42 |
43 | @wokwi_cli_required
44 | @wokwi_token_required
45 | def test_pexpect_by_wokwi_esp32_arduino(testdir):
46 | testdir.makepyfile("""
47 | import pexpect
48 | import pytest
49 | def test_pexpect_by_wokwi(dut):
50 | dut.expect('Hello Arduino!')
51 | with pytest.raises(pexpect.TIMEOUT):
52 | dut.expect('foo bar not found', timeout=1)
53 | """)
54 |
55 | result = testdir.runpytest(
56 | '-s',
57 | '--embedded-services',
58 | 'arduino,wokwi',
59 | '--app-path',
60 | os.path.join(testdir.tmpdir, 'hello_world_arduino'),
61 | '--wokwi-diagram',
62 | os.path.join(testdir.tmpdir, 'hello_world_arduino/esp32.diagram.json'),
63 | )
64 |
65 | result.assert_outcomes(passed=1)
66 |
--------------------------------------------------------------------------------
/pytest-embedded/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pytest-embedded/README.md:
--------------------------------------------------------------------------------
1 | ### pytest-embedded
2 |
3 | A pytest plugin for embedded systems. Could activate different services for extra functionalities.
4 |
--------------------------------------------------------------------------------
/pytest-embedded/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "pytest-embedded"
7 | authors = [{name = "Fu Hanxi", email = "fuhanxi@espressif.com"}]
8 | readme = "README.md"
9 | license = {file = "LICENSE"}
10 | classifiers = [
11 | "Development Status :: 5 - Production/Stable",
12 | "Framework :: Pytest",
13 | "Intended Audience :: Developers",
14 | "License :: OSI Approved :: MIT License",
15 | "Operating System :: OS Independent",
16 | "Programming Language :: Python :: 3 :: Only",
17 | "Programming Language :: Python :: 3",
18 | "Programming Language :: Python :: 3.7",
19 | "Programming Language :: Python :: 3.8",
20 | "Programming Language :: Python :: 3.9",
21 | "Programming Language :: Python :: 3.10",
22 | "Programming Language :: Python :: 3.11",
23 | "Programming Language :: Python :: 3.12",
24 | "Programming Language :: Python :: 3.13",
25 | "Programming Language :: Python",
26 | "Topic :: Software Development :: Testing",
27 | ]
28 | dynamic = ["version", "description"]
29 | requires-python = ">=3.7"
30 |
31 | dependencies = [
32 | "pytest>=7.0",
33 | "pexpect>=4.4",
34 | ]
35 |
36 | [project.urls]
37 | homepage = "https://github.com/espressif/pytest-embedded"
38 | repository = "https://github.com/espressif/pytest-embedded"
39 | documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
40 | changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
41 |
42 | [project.entry-points."pytest11"]
43 | pytest_embedded = "pytest_embedded.plugin"
44 |
--------------------------------------------------------------------------------
/pytest-embedded/pytest_embedded/__init__.py:
--------------------------------------------------------------------------------
1 | """A pytest plugin that designed for embedded testing."""
2 |
3 | from .app import App
4 | from .dut import Dut
5 | from .dut_factory import DutFactory
6 |
7 | __all__ = ['App', 'Dut', 'DutFactory']
8 |
9 | __version__ = '1.16.1'
10 |
--------------------------------------------------------------------------------
/pytest-embedded/pytest_embedded/app.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | from typing import Optional
4 |
5 |
6 | class App:
7 | """
8 | Built binary files base class
9 |
10 | Attributes:
11 | app_path (str): application folder path
12 | binary_path (str): binary folder path
13 | """
14 |
15 | def __init__(
16 | self,
17 | app_path: Optional[str] = None,
18 | build_dir: Optional[str] = None,
19 | **kwargs,
20 | ):
21 | if app_path is None:
22 | app_path = os.getcwd()
23 |
24 | self.app_path = os.path.realpath(app_path)
25 | self.binary_path = self._get_binary_path(build_dir or 'build')
26 |
27 | for k, v in kwargs.items():
28 | setattr(self, k, v)
29 |
30 | def _get_binary_path(self, build_dir: Optional[str] = None) -> Optional[str]:
31 | if not build_dir:
32 | return None
33 |
34 | # if build_dir is an absolute path, use it directly
35 | logging.debug(f'checking if {build_dir} is an absolute path...')
36 | if os.path.isabs(build_dir):
37 | return os.path.realpath(build_dir)
38 |
39 | # try relative path based on app_path first
40 | logging.debug(f'checking if {build_dir} exists in {self.app_path}...')
41 | path = os.path.join(self.app_path, build_dir)
42 | if os.path.isdir(path):
43 | return path
44 |
45 | # try relative path based on cwd
46 | logging.debug(f'checking if {build_dir} exists in {os.getcwd()}...')
47 | if os.path.isdir(build_dir):
48 | return os.path.realpath(build_dir)
49 |
50 | logging.debug(f"{path} doesn't exist.")
51 | return None
52 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # This is a requirements.txt for extra dependencies.
2 | # To install all packages, please run `bash foreach.sh install`
3 |
4 | # build
5 | flit
6 |
7 | # testing
8 | pytest-cov
9 | pytest-rerunfailures
10 |
--------------------------------------------------------------------------------
/ruff.toml:
--------------------------------------------------------------------------------
1 | line-length = 120
2 | target-version = "py37"
3 |
4 | [lint]
5 | select = [
6 | 'F', # Pyflakes
7 | 'E', # pycodestyle
8 | 'W', # pycodestyle
9 | 'I', # isort
10 | 'UP', # pyupgrade
11 | 'YTT', # flake8-2020
12 | 'A', # flake8-builtins
13 | 'ARG', # flake8-unused-arguments
14 | 'ERA', # eradicate
15 | 'LOG', # flake8-logging
16 | 'RUF', # Ruff-specific rules
17 | ]
18 |
19 | ignore = [
20 | 'LOG015', # use pytest logging
21 | ]
22 |
23 | [format]
24 | quote-style = "single"
25 | exclude = [
26 | "pytest-embedded-jtag/pytest_embedded_jtag/_telnetlib/telnetlib.py"
27 | ]
28 | docstring-code-format = true
29 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 0
2 | #define SOC_B 1
3 | #define SOC_C 0
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32c2/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 0
2 | #define SOC_B 1
3 | #define SOC_C 0
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32c3/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 1
2 | #define SOC_B 1
3 | #define SOC_C 1
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32c5/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 1
2 | #define SOC_B 1
3 | #define SOC_C 0
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32c6/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 1
2 | #define SOC_B 0
3 | #define SOC_C 0
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32c61/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 0
2 | #define SOC_B 0
3 | #define SOC_C 1
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32h2/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 0
2 | #define SOC_B 1
3 | #define SOC_C 1
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32h21/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 0
2 | #define SOC_B 0
3 | #define SOC_C 0
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32p4/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 0
2 | #define SOC_B 0
3 | #define SOC_C 1
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32s2/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 0
2 | #define SOC_B 0
3 | #define SOC_C 0
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/components/soc/esp32s3/include/soc/soc_caps.h:
--------------------------------------------------------------------------------
1 | #define SOC_A 1
2 | #define SOC_B 0
3 | #define SOC_C 1
4 |
--------------------------------------------------------------------------------
/tests/esp-idf/tools/cmake/version.cmake:
--------------------------------------------------------------------------------
1 | set(IDF_VERSION_MAJOR 5)
2 | set(IDF_VERSION_MINOR 5)
3 | set(IDF_VERSION_PATCH 0)
4 |
5 | set(ENV{IDF_VERSION} "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}.${IDF_VERSION_PATCH}")
6 |
--------------------------------------------------------------------------------
/tests/esp-idf/tools/idf_py_actions/constants.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
2 | # SPDX-License-Identifier: Apache-2.0
3 |
4 | SUPPORTED_TARGETS = ['esp32', 'esp32s2', 'esp32c3', 'esp32s3', 'esp32c2', 'esp32c6', 'esp32h2', 'esp32p4']
5 | PREVIEW_TARGETS = ['linux', 'esp32c5', 'esp32c61', 'esp32h21']
6 |
--------------------------------------------------------------------------------
/tests/fixtures/esp32_qemu.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/esp32_qemu.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_arduino/build/build.options.json:
--------------------------------------------------------------------------------
1 | {
2 | "additionalFiles": "",
3 | "builtInLibrariesFolders": "",
4 | "builtInToolsFolders": "/Applications/Arduino.app/Contents/Java/tools-builder",
5 | "compiler.optimization_flags": "-Os",
6 | "customBuildProperties": "",
7 | "fqbn": "espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app",
8 | "hardwareFolders": "/Users/prochy/Documents/Arduino/hardware",
9 | "otherLibrariesFolders": "/Users/prochy/Documents/Arduino/libraries",
10 | "runtime.ide.version": "10810",
11 | "sketchLocation": "/Users/prochy/Documents/Arduino/hardware/espressif/esp32/tests/hello_world/hello_world.ino"
12 | }
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_arduino/build/hello_world_arduino.ino.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_arduino/build/hello_world_arduino.ino.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_arduino/build/hello_world_arduino.ino.bootloader.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_arduino/build/hello_world_arduino.ino.bootloader.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_arduino/build/hello_world_arduino.ino.elf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_arduino/build/hello_world_arduino.ino.elf
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_arduino/build/hello_world_arduino.ino.partitions.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_arduino/build/hello_world_arduino.ino.partitions.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_arduino/esp32.diagram.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "author": "jan.prochazka@espressif.com",
4 | "editor": "wokwi",
5 | "parts": [
6 | {
7 | "type": "board-esp32-devkit-c-v4",
8 | "id": "esp"
9 | }
10 | ],
11 | "connections": [
12 | [
13 | "esp:TX",
14 | "$serialMonitor:RX",
15 | ""
16 | ],
17 | [
18 | "esp:RX",
19 | "$serialMonitor:TX",
20 | ""
21 | ]
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32/build/bootloader/bootloader.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32/build/bootloader/bootloader.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32/build/flasher_args.json:
--------------------------------------------------------------------------------
1 | {
2 | "write_flash_args" : [ "--flash_mode", "dio",
3 | "--flash_size", "2MB",
4 | "--flash_freq", "40m" ],
5 | "flash_settings" : {
6 | "flash_mode": "dio",
7 | "flash_size": "2MB",
8 | "flash_freq": "40m"
9 | },
10 | "flash_files" : {
11 | "0x1000" : "bootloader/bootloader.bin",
12 | "0x10000" : "hello_world.bin",
13 | "0x8000" : "partition_table/partition-table.bin"
14 | },
15 | "bootloader" : { "offset" : "0x1000", "file" : "bootloader/bootloader.bin", "encrypted" : "false" },
16 | "app" : { "offset" : "0x10000", "file" : "hello_world.bin", "encrypted" : "false" },
17 | "partition-table" : { "offset" : "0x8000", "file" : "partition_table/partition-table.bin", "encrypted" : "false" },
18 | "extra_esptool_args" : {
19 | "after" : "hard_reset",
20 | "before" : "default_reset",
21 | "stub" : true,
22 | "chip" : "esp32"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32/build/hello_world.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32/build/hello_world.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32/build/hello_world.elf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32/build/hello_world.elf
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32/build/partition_table/partition-table.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32/build/partition_table/partition-table.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32/gdbinit:
--------------------------------------------------------------------------------
1 | set pagination off
2 | # Connect to a running instance of OpenOCD
3 | target remote 127.0.0.1:3333
4 | # Reset and halt the target
5 | mon reset halt
6 | # Run to a specific point in ROM code,
7 | # where most of initialization is complete.
8 | thb *0x40007d54
9 | c
10 | # Load the application into RAM
11 | load
12 | # Run till app_main
13 | tb app_main
14 | c
15 |
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32_flash_enc/build/bootloader/bootloader.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32_flash_enc/build/bootloader/bootloader.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32_flash_enc/build/flasher_args.json:
--------------------------------------------------------------------------------
1 | {
2 | "write_flash_args" : [ "--flash_mode", "dio",
3 | "--flash_size", "2MB",
4 | "--flash_freq", "40m" ],
5 | "flash_settings" : {
6 | "flash_mode": "dio",
7 | "flash_size": "2MB",
8 | "flash_freq": "40m"
9 | },
10 | "flash_files" : {
11 | "0x1000" : "bootloader/bootloader.bin",
12 | "0x20000" : "hello_world.bin",
13 | "0xb000" : "partition_table/partition-table.bin"
14 | },
15 | "bootloader" : { "offset" : "0x1000", "file" : "bootloader/bootloader.bin", "encrypted" : "true" },
16 | "app" : { "offset" : "0x20000", "file" : "hello_world.bin", "encrypted" : "true" },
17 | "partition-table" : { "offset" : "0xb000", "file" : "partition_table/partition-table.bin", "encrypted" : "true" },
18 | "extra_esptool_args" : {
19 | "after" : "no_reset",
20 | "before" : "default_reset",
21 | "stub" : true,
22 | "chip" : "esp32"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32_flash_enc/build/hello_world.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32_flash_enc/build/hello_world.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32_flash_enc/build/partition_table/partition-table.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32_flash_enc/build/partition_table/partition-table.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32_flash_enc/pre_encryption_efuses.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32_flash_enc/pre_encryption_efuses.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32_flash_enc/pre_encryption_key.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32_flash_enc/pre_encryption_key.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3/build/bootloader/bootloader.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32c3/build/bootloader/bootloader.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3/build/flasher_args.json:
--------------------------------------------------------------------------------
1 | {
2 | "write_flash_args" : [ "--flash_mode", "dio",
3 | "--flash_size", "2MB",
4 | "--flash_freq", "80m" ],
5 | "flash_settings" : {
6 | "flash_mode": "dio",
7 | "flash_size": "2MB",
8 | "flash_freq": "80m"
9 | },
10 | "flash_files" : {
11 | "0x0" : "bootloader/bootloader.bin",
12 | "0x10000" : "hello_world.bin",
13 | "0x8000" : "partition_table/partition-table.bin"
14 | },
15 | "bootloader" : { "offset" : "0x0", "file" : "bootloader/bootloader.bin", "encrypted" : "false" },
16 | "app" : { "offset" : "0x10000", "file" : "hello_world.bin", "encrypted" : "false" },
17 | "partition-table" : { "offset" : "0x8000", "file" : "partition_table/partition-table.bin", "encrypted" : "false" },
18 | "extra_esptool_args" : {
19 | "after" : "hard_reset",
20 | "before" : "default_reset",
21 | "stub" : true,
22 | "chip" : "esp32c3"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3/build/hello_world.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32c3/build/hello_world.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3/build/hello_world.elf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32c3/build/hello_world.elf
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3/build/partition_table/partition-table.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32c3/build/partition_table/partition-table.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3_panic/build/bootloader/bootloader.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32c3_panic/build/bootloader/bootloader.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3_panic/build/flasher_args.json:
--------------------------------------------------------------------------------
1 | {
2 | "write_flash_args" : [ "--flash_mode", "dio",
3 | "--flash_size", "2MB",
4 | "--flash_freq", "80m" ],
5 | "flash_settings" : {
6 | "flash_mode": "dio",
7 | "flash_size": "2MB",
8 | "flash_freq": "80m"
9 | },
10 | "flash_files" : {
11 | "0x0" : "bootloader/bootloader.bin",
12 | "0x10000" : "hello_world.bin",
13 | "0x8000" : "partition_table/partition-table.bin"
14 | },
15 | "bootloader" : { "offset" : "0x0", "file" : "bootloader/bootloader.bin", "encrypted" : "false" },
16 | "app" : { "offset" : "0x10000", "file" : "hello_world.bin", "encrypted" : "false" },
17 | "partition-table" : { "offset" : "0x8000", "file" : "partition_table/partition-table.bin", "encrypted" : "false" },
18 | "extra_esptool_args" : {
19 | "after" : "hard_reset",
20 | "before" : "default_reset",
21 | "stub" : true,
22 | "chip" : "esp32c3"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3_panic/build/hello_world.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32c3_panic/build/hello_world.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3_panic/build/hello_world.elf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32c3_panic/build/hello_world.elf
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3_panic/build/partition_table/partition-table.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_esp32c3_panic/build/partition_table/partition-table.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3_panic/build/prefix_map_gdbinit:
--------------------------------------------------------------------------------
1 | set substitute-path /COMPONENT_MAIN_DIR ./hello_world_esp32c3_panic/main
2 |
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_esp32c3_panic/main/hello_world_main.c:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
3 | *
4 | * SPDX-License-Identifier: CC0-1.0
5 | */
6 |
7 | #include
8 | #include "sdkconfig.h"
9 | #include "freertos/FreeRTOS.h"
10 | #include "freertos/task.h"
11 | #include "esp_chip_info.h"
12 | #include "esp_flash.h"
13 |
14 | void app_main(void)
15 | {
16 | printf("Hello world!\n");
17 | *((int*) 0) = 0;
18 | /* Print chip information */
19 | esp_chip_info_t chip_info;
20 | uint32_t flash_size;
21 | esp_chip_info(&chip_info);
22 | printf("This is %s chip with %d CPU core(s), WiFi%s%s, ",
23 | CONFIG_IDF_TARGET,
24 | chip_info.cores,
25 | (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
26 | (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
27 |
28 | printf("silicon revision %d, ", chip_info.revision);
29 | if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
30 | printf("Get flash size failed");
31 | return;
32 | }
33 |
34 | printf("%uMB %s flash\n", flash_size / (1024 * 1024),
35 | (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
36 |
37 | printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());
38 |
39 | for (int i = 10; i >= 0; i--) {
40 | printf("Restarting in %d seconds...\n", i);
41 | vTaskDelay(1000 / portTICK_PERIOD_MS);
42 | }
43 | printf("Restarting now.\n");
44 | fflush(stdout);
45 | esp_restart();
46 | }
47 |
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_linux/build/config/sdkconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "APP_BUILD_TYPE_ELF_RAM": true,
3 | "APP_NO_BLOBS": false,
4 | "APP_REPRODUCIBLE_BUILD": false,
5 | "BOOT_ROM_LOG_ALWAYS_OFF": false,
6 | "BOOT_ROM_LOG_ALWAYS_ON": true,
7 | "BOOT_ROM_LOG_ON_GPIO_HIGH": false,
8 | "BOOT_ROM_LOG_ON_GPIO_LOW": false,
9 | "COMPILER_CXX_EXCEPTIONS": false,
10 | "COMPILER_CXX_RTTI": false,
11 | "COMPILER_DUMP_RTL_FILES": false,
12 | "COMPILER_FLOAT_LIB_FROM_GCCLIB": true,
13 | "COMPILER_HIDE_PATHS_MACROS": true,
14 | "COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE": false,
15 | "COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE": true,
16 | "COMPILER_OPTIMIZATION_ASSERTIONS_SILENT": false,
17 | "COMPILER_OPTIMIZATION_ASSERTION_LEVEL": 2,
18 | "COMPILER_OPTIMIZATION_CHECKS_SILENT": false,
19 | "COMPILER_OPTIMIZATION_DEFAULT": true,
20 | "COMPILER_OPTIMIZATION_NONE": false,
21 | "COMPILER_OPTIMIZATION_PERF": false,
22 | "COMPILER_OPTIMIZATION_SIZE": false,
23 | "COMPILER_STACK_CHECK_MODE_ALL": false,
24 | "COMPILER_STACK_CHECK_MODE_NONE": true,
25 | "COMPILER_STACK_CHECK_MODE_NORM": false,
26 | "COMPILER_STACK_CHECK_MODE_STRONG": false,
27 | "COMPILER_WARN_WRITE_STRINGS": false,
28 | "ESP_CONSOLE_MULTIPLE_UART": true,
29 | "ESP_CONSOLE_NONE": false,
30 | "ESP_CONSOLE_UART": true,
31 | "ESP_CONSOLE_UART_BAUDRATE": 115200,
32 | "ESP_CONSOLE_UART_CUSTOM": false,
33 | "ESP_CONSOLE_UART_DEFAULT": true,
34 | "ESP_CONSOLE_UART_NUM": 0,
35 | "ESP_DEBUG_OCDAWARE": true,
36 | "ESP_DEBUG_STUBS_ENABLE": false,
37 | "ESP_ERR_TO_NAME_LOOKUP": true,
38 | "ESP_INT_WDT": true,
39 | "ESP_INT_WDT_TIMEOUT_MS": 300,
40 | "ESP_IPC_TASK_STACK_SIZE": 1024,
41 | "ESP_MAIN_TASK_AFFINITY": 0,
42 | "ESP_MAIN_TASK_AFFINITY_CPU0": true,
43 | "ESP_MAIN_TASK_AFFINITY_NO_AFFINITY": false,
44 | "ESP_MAIN_TASK_STACK_SIZE": 3584,
45 | "ESP_MINIMAL_SHARED_STACK_SIZE": 2048,
46 | "ESP_PANIC_HANDLER_IRAM": false,
47 | "ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND": true,
48 | "ESP_SLEEP_GPIO_RESET_WORKAROUND": false,
49 | "ESP_SLEEP_MSPI_NEED_ALL_IO_PU": false,
50 | "ESP_SLEEP_POWER_DOWN_FLASH": false,
51 | "ESP_SYSTEM_CHECK_INT_LEVEL_4": true,
52 | "ESP_SYSTEM_EVENT_QUEUE_SIZE": 32,
53 | "ESP_SYSTEM_EVENT_TASK_STACK_SIZE": 2304,
54 | "ESP_SYSTEM_GDBSTUB_RUNTIME": false,
55 | "ESP_SYSTEM_PANIC_GDBSTUB": false,
56 | "ESP_SYSTEM_PANIC_PRINT_HALT": false,
57 | "ESP_SYSTEM_PANIC_PRINT_REBOOT": true,
58 | "ESP_SYSTEM_PANIC_SILENT_REBOOT": false,
59 | "ESP_SYSTEM_SINGLE_CORE_MODE": true,
60 | "ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0": true,
61 | "ESP_TASK_WDT_EN": true,
62 | "ESP_TASK_WDT_INIT": true,
63 | "ESP_TASK_WDT_PANIC": false,
64 | "ESP_TASK_WDT_TIMEOUT_S": 5,
65 | "FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER": true,
66 | "FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE": false,
67 | "FREERTOS_CHECK_STACKOVERFLOW_CANARY": true,
68 | "FREERTOS_CHECK_STACKOVERFLOW_NONE": false,
69 | "FREERTOS_CHECK_STACKOVERFLOW_PTRVAL": false,
70 | "FREERTOS_CORETIMER_SYSTIMER_LVL1": true,
71 | "FREERTOS_CORETIMER_SYSTIMER_LVL3": false,
72 | "FREERTOS_DEBUG_OCDAWARE": true,
73 | "FREERTOS_ENABLE_BACKWARD_COMPATIBILITY": false,
74 | "FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP": false,
75 | "FREERTOS_ENABLE_TASK_SNAPSHOT": true,
76 | "FREERTOS_GENERATE_RUN_TIME_STATS": false,
77 | "FREERTOS_HZ": 100,
78 | "FREERTOS_IDLE_TASK_STACKSIZE": 1536,
79 | "FREERTOS_INTERRUPT_BACKTRACE": true,
80 | "FREERTOS_ISR_STACKSIZE": 1536,
81 | "FREERTOS_MAX_TASK_NAME_LEN": 16,
82 | "FREERTOS_NO_AFFINITY": 2147483647,
83 | "FREERTOS_OPTIMIZED_SCHEDULER": true,
84 | "FREERTOS_PLACE_FUNCTIONS_INTO_FLASH": false,
85 | "FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH": false,
86 | "FREERTOS_QUEUE_REGISTRY_SIZE": 0,
87 | "FREERTOS_SMP": false,
88 | "FREERTOS_SUPPORT_STATIC_ALLOCATION": true,
89 | "FREERTOS_SYSTICK_USES_SYSTIMER": true,
90 | "FREERTOS_TASK_FUNCTION_WRAPPER": true,
91 | "FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES": 1,
92 | "FREERTOS_THREAD_LOCAL_STORAGE_POINTERS": 1,
93 | "FREERTOS_TICK_SUPPORT_SYSTIMER": true,
94 | "FREERTOS_TIMER_QUEUE_LENGTH": 10,
95 | "FREERTOS_TIMER_TASK_PRIORITY": 1,
96 | "FREERTOS_TIMER_TASK_STACK_DEPTH": 2053,
97 | "FREERTOS_TLSP_DELETION_CALLBACKS": true,
98 | "FREERTOS_UNICORE": true,
99 | "FREERTOS_USE_IDLE_HOOK": false,
100 | "FREERTOS_USE_TICK_HOOK": false,
101 | "FREERTOS_USE_TRACE_FACILITY": false,
102 | "FREERTOS_WATCHPOINT_END_OF_STACK": false,
103 | "HAL_ASSERTION_DISABLE": false,
104 | "HAL_ASSERTION_ENABLE": false,
105 | "HAL_ASSERTION_EQUALS_SYSTEM": true,
106 | "HAL_ASSERTION_SILENT": false,
107 | "HAL_DEFAULT_ASSERTION_LEVEL": 2,
108 | "HEAP_ABORT_WHEN_ALLOCATION_FAILS": false,
109 | "HEAP_POISONING_COMPREHENSIVE": false,
110 | "HEAP_POISONING_DISABLED": true,
111 | "HEAP_POISONING_LIGHT": false,
112 | "HEAP_TRACING_OFF": true,
113 | "HEAP_TRACING_STANDALONE": false,
114 | "HEAP_TRACING_TOHOST": false,
115 | "IDF_CMAKE": true,
116 | "IDF_FIRMWARE_CHIP_ID": 65535,
117 | "IDF_TARGET": "linux",
118 | "IDF_TARGET_LINUX": true,
119 | "LOG_COLORS": true,
120 | "LOG_DEFAULT_LEVEL": 3,
121 | "LOG_DEFAULT_LEVEL_DEBUG": false,
122 | "LOG_DEFAULT_LEVEL_ERROR": false,
123 | "LOG_DEFAULT_LEVEL_INFO": true,
124 | "LOG_DEFAULT_LEVEL_NONE": false,
125 | "LOG_DEFAULT_LEVEL_VERBOSE": false,
126 | "LOG_DEFAULT_LEVEL_WARN": false,
127 | "LOG_MAXIMUM_EQUALS_DEFAULT": true,
128 | "LOG_MAXIMUM_LEVEL": 3,
129 | "LOG_MAXIMUM_LEVEL_DEBUG": false,
130 | "LOG_MAXIMUM_LEVEL_VERBOSE": false,
131 | "LOG_TIMESTAMP_SOURCE_RTOS": true,
132 | "LOG_TIMESTAMP_SOURCE_SYSTEM": false,
133 | "MMU_PAGE_MODE": "64KB",
134 | "MMU_PAGE_SIZE": 65536,
135 | "MMU_PAGE_SIZE_64KB": true,
136 | "PERIPH_CTRL_FUNC_IN_IRAM": false
137 | }
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_linux/build/hello_world.elf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_linux/build/hello_world.elf
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_linux/build/project_description.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_name": "hello_world",
3 | "project_path": "/home/aleksei/esp/esp-idf/tools/test_apps/linux_compatible/hello_world_linux_compatible",
4 | "build_dir": "/home/aleksei/esp/esp-idf/tools/test_apps/linux_compatible/hello_world_linux_compatible/build",
5 | "config_file": "/home/aleksei/esp/esp-idf/tools/test_apps/linux_compatible/hello_world_linux_compatible/sdkconfig",
6 | "config_defaults": "",
7 | "bootloader_elf": "",
8 | "app_elf": "hello_world.elf",
9 | "app_bin": "hello_world.bin",
10 | "git_revision": "v5.1-dev-2351-gc01f71cfcf",
11 | "target": "linux",
12 | "rev": "",
13 | "min_rev": "",
14 | "max_rev": "",
15 | "phy_data_partition": "",
16 | "monitor_baud" : "",
17 | "monitor_toolprefix": "",
18 | "config_environment" : {
19 | "COMPONENT_KCONFIGS" : "/home/aleksei/esp/esp-idf/components/esp_common/Kconfig;/home/aleksei/esp/esp-idf/components/esp_hw_support/Kconfig;/home/aleksei/esp/esp-idf/components/esp_system/Kconfig;/home/aleksei/esp/esp-idf/components/freertos/Kconfig;/home/aleksei/esp/esp-idf/components/hal/Kconfig;/home/aleksei/esp/esp-idf/components/heap/Kconfig;/home/aleksei/esp/esp-idf/components/log/Kconfig",
20 | "COMPONENT_KCONFIGS_PROJBUILD" : "/home/aleksei/esp/esp-idf/components/esp_rom/Kconfig.projbuild"
21 | },
22 | "build_components" : [ "esp_common", "esp_hw_support", "esp_rom", "esp_system", "freertos", "hal", "heap", "linux", "log", "main", "soc", "" ],
23 | "build_component_paths" : [ "/home/aleksei/esp/esp-idf/components/esp_common", "/home/aleksei/esp/esp-idf/components/esp_hw_support", "/home/aleksei/esp/esp-idf/components/esp_rom", "/home/aleksei/esp/esp-idf/components/esp_system", "/home/aleksei/esp/esp-idf/components/freertos", "/home/aleksei/esp/esp-idf/components/hal", "/home/aleksei/esp/esp-idf/components/heap", "/home/aleksei/esp/esp-idf/components/linux", "/home/aleksei/esp/esp-idf/components/log", "/home/aleksei/esp/esp-idf/tools/test_apps/linux_compatible/hello_world_linux_compatible/main", "/home/aleksei/esp/esp-idf/components/soc", "" ],
24 | "debug_prefix_map_gdbinit": ""
25 | }
26 |
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_nuttx/nuttx.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_nuttx/nuttx.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_nuttx_mcuboot/mcuboot-esp32.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_nuttx_mcuboot/mcuboot-esp32.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_nuttx_mcuboot/nuttx.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_nuttx_mcuboot/nuttx.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_nuttx_qemu/nuttx.merged.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_nuttx_qemu/nuttx.merged.bin
--------------------------------------------------------------------------------
/tests/fixtures/hello_world_nuttx_qemu/qemu_efuse.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/hello_world_nuttx_qemu/qemu_efuse.bin
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.16)
2 |
3 | set(EXTRA_COMPONENT_DIRS
4 | "$ENV{IDF_PATH}/tools/unit-test-app/components")
5 |
6 | include($ENV{IDF_PATH}/tools/cmake/project.cmake)
7 | project(case_tester_example)
8 |
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32/build/bootloader/bootloader.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/unit_test_app_esp32/build/bootloader/bootloader.bin
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32/build/case_tester_example.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/unit_test_app_esp32/build/case_tester_example.bin
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32/build/flasher_args.json:
--------------------------------------------------------------------------------
1 | {
2 | "write_flash_args" : [ "--flash_mode", "dio",
3 | "--flash_size", "2MB",
4 | "--flash_freq", "40m" ],
5 | "flash_settings" : {
6 | "flash_mode": "dio",
7 | "flash_size": "2MB",
8 | "flash_freq": "40m"
9 | },
10 | "flash_files" : {
11 | "0x1000" : "bootloader/bootloader.bin",
12 | "0x10000" : "case_tester_example.bin",
13 | "0x8000" : "partition_table/partition-table.bin"
14 | },
15 | "bootloader" : { "offset" : "0x1000", "file" : "bootloader/bootloader.bin", "encrypted" : "false" },
16 | "app" : { "offset" : "0x10000", "file" : "case_tester_example.bin", "encrypted" : "false" },
17 | "partition-table" : { "offset" : "0x8000", "file" : "partition_table/partition-table.bin", "encrypted" : "false" },
18 | "extra_esptool_args" : {
19 | "after" : "hard_reset",
20 | "before" : "default_reset",
21 | "stub" : true,
22 | "chip" : "esp32"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32/build/partition_table/partition-table.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/unit_test_app_esp32/build/partition_table/partition-table.bin
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32/main/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | idf_component_register(SRCS "test_app_main.c"
2 | "case_tester_example.c"
3 | INCLUDE_DIRS "."
4 | PRIV_REQUIRES test_utils unity
5 | WHOLE_ARCHIVE)
6 |
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32/main/case_tester_example.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "freertos/FreeRTOS.h"
3 | #include "freertos/task.h"
4 | #include "esp_chip_info.h"
5 | #include "hal/rtc_cntl_ll.h"
6 | #include "unity.h"
7 | #include "test_utils.h"
8 | #include "esp_log.h"
9 |
10 |
11 | TEST_CASE("normal_case1", "[normal_case]")
12 | {
13 | esp_chip_info_t chip_info;
14 | esp_chip_info(&chip_info);
15 | ESP_LOGI("normal case1", "This is %s chip with %d CPU core(s), WiFi%s%s, ",
16 | CONFIG_IDF_TARGET,
17 | chip_info.cores,
18 | (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
19 | (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
20 | TEST_ASSERT(true);
21 | }
22 |
23 | TEST_CASE("normal_case2", "[normal_case][timeout=10]")
24 | {
25 | ESP_LOGI("normal case2", "delay 3s");
26 | vTaskDelay(pdMS_TO_TICKS(3000));
27 |
28 | // cause a crash
29 | volatile uint8_t *test = (uint8_t*)0x0;
30 | *test = 1;
31 |
32 | TEST_ASSERT(true);
33 | }
34 |
35 | void test_stage1(void)
36 | {
37 | ESP_LOGI("multi_stage", "stage1: software restart");
38 |
39 | vTaskDelay(pdMS_TO_TICKS(100));
40 | esp_restart();
41 | }
42 |
43 | void test_stage2(void)
44 | {
45 | ESP_LOGI("multi_stage", "stage2: assert fail");
46 | vTaskDelay(pdMS_TO_TICKS(100));
47 | assert(false); // this one will cause a panic, the test won't fail
48 | }
49 |
50 | void test_stage3(void)
51 | {
52 | ESP_LOGI("multi_stage", "stage3: system reset");
53 | rtc_cntl_ll_reset_system();
54 | }
55 |
56 | void test_stage4(void)
57 | {
58 | ESP_LOGI("multi_stage", "stage4: finish");
59 | }
60 |
61 | TEST_CASE_MULTIPLE_STAGES("multiple_stages_test", "[multi_stage]",
62 | test_stage1, test_stage2, test_stage3, test_stage4);
63 |
64 |
65 | void test_dev1(void)
66 | {
67 | ESP_LOGI("multi_dev", "dev1 start");
68 | unity_send_signal("signal 1 from dev 1");
69 | unity_wait_for_signal("signal 2 from dev 2");
70 | unity_send_signal("signal 3 from dev 1");
71 |
72 | for (int i = 0; i < 10; i++) {
73 | unity_wait_for_signal("continuous signal");
74 | }
75 | }
76 |
77 | void test_dev2(void)
78 | {
79 | ESP_LOGI("multi_dev", "dev2 start");
80 | unity_wait_for_signal("signal 1 from dev 1");
81 | unity_send_signal("signal 2 from dev 2");
82 | unity_wait_for_signal("signal 3 from dev 1");
83 | for (int i = 0; i < 10; i++) {
84 | unity_send_signal("continuous signal");
85 | }
86 | }
87 |
88 | TEST_CASE_MULTIPLE_DEVICES("multiple_devices_test", "[multi_dev][timeout=150]",
89 | test_dev1, test_dev2);
90 |
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32/main/test_app_main.c:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | #include "unity.h"
8 | #include "unity_test_runner.h"
9 | #include "esp_heap_caps.h"
10 |
11 | #define TEST_MEMORY_LEAK_THRESHOLD (-200)
12 |
13 | static size_t before_free_8bit;
14 | static size_t before_free_32bit;
15 |
16 | static void check_leak(size_t before_free, size_t after_free, const char *type)
17 | {
18 | ssize_t delta = after_free - before_free;
19 | printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
20 | TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
21 | }
22 |
23 | void setUp(void)
24 | {
25 | before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
26 | before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
27 | }
28 |
29 | void tearDown(void)
30 | {
31 | size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
32 | size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
33 | check_leak(before_free_8bit, after_free_8bit, "8BIT");
34 | check_leak(before_free_32bit, after_free_32bit, "32BIT");
35 | }
36 |
37 | void app_main(void)
38 | {
39 | unity_run_menu();
40 | }
41 |
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32c3/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.16)
2 |
3 | set(EXTRA_COMPONENT_DIRS
4 | "$ENV{IDF_PATH}/tools/unit-test-app/components")
5 |
6 | include($ENV{IDF_PATH}/tools/cmake/project.cmake)
7 | project(case_tester_example)
8 |
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32c3/build/bootloader/bootloader.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/unit_test_app_esp32c3/build/bootloader/bootloader.bin
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32c3/build/case_tester_example.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/unit_test_app_esp32c3/build/case_tester_example.bin
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32c3/build/flasher_args.json:
--------------------------------------------------------------------------------
1 | {
2 | "write_flash_args" : [ "--flash_mode", "dio",
3 | "--flash_size", "2MB",
4 | "--flash_freq", "80m" ],
5 | "flash_settings" : {
6 | "flash_mode": "dio",
7 | "flash_size": "2MB",
8 | "flash_freq": "80m"
9 | },
10 | "flash_files" : {
11 | "0x0" : "bootloader/bootloader.bin",
12 | "0x10000" : "case_tester_example.bin",
13 | "0x8000" : "partition_table/partition-table.bin"
14 | },
15 | "bootloader" : { "offset" : "0x0", "file" : "bootloader/bootloader.bin", "encrypted" : "false" },
16 | "app" : { "offset" : "0x10000", "file" : "case_tester_example.bin", "encrypted" : "false" },
17 | "partition-table" : { "offset" : "0x8000", "file" : "partition_table/partition-table.bin", "encrypted" : "false" },
18 | "extra_esptool_args" : {
19 | "after" : "hard_reset",
20 | "before" : "default_reset",
21 | "stub" : true,
22 | "chip" : "esp32c3"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32c3/build/partition_table/partition-table.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/unit_test_app_esp32c3/build/partition_table/partition-table.bin
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32c3/main/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | idf_component_register(SRCS "test_app_main.c"
2 | "case_tester_example.c"
3 | INCLUDE_DIRS "."
4 | PRIV_REQUIRES test_utils unity
5 | WHOLE_ARCHIVE)
6 |
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32c3/main/case_tester_example.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "freertos/FreeRTOS.h"
3 | #include "freertos/task.h"
4 | #include "esp_chip_info.h"
5 | #include "hal/rtc_cntl_ll.h"
6 | #include "unity.h"
7 | #include "test_utils.h"
8 | #include "esp_log.h"
9 |
10 |
11 | TEST_CASE("normal_case1", "[normal_case]")
12 | {
13 | esp_chip_info_t chip_info;
14 | esp_chip_info(&chip_info);
15 | ESP_LOGI("normal case1", "This is %s chip with %d CPU core(s), WiFi%s%s, ",
16 | CONFIG_IDF_TARGET,
17 | chip_info.cores,
18 | (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
19 | (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
20 | TEST_ASSERT(true);
21 | }
22 |
23 | TEST_CASE("normal_case2", "[normal_case][timeout=10]")
24 | {
25 | ESP_LOGI("normal case2", "delay 3s");
26 | vTaskDelay(pdMS_TO_TICKS(3000));
27 |
28 | // cause a crash
29 | volatile uint8_t *test = (uint8_t*)0x0;
30 | *test = 1;
31 |
32 | TEST_ASSERT(true);
33 | }
34 |
35 | void test_stage1(void)
36 | {
37 | ESP_LOGI("multi_stage", "stage1: software restart");
38 |
39 | vTaskDelay(pdMS_TO_TICKS(100));
40 | esp_restart();
41 | }
42 |
43 | void test_stage2(void)
44 | {
45 | ESP_LOGI("multi_stage", "stage2: assert fail");
46 | vTaskDelay(pdMS_TO_TICKS(100));
47 | assert(false);
48 | }
49 |
50 | void test_stage3(void)
51 | {
52 | ESP_LOGI("multi_stage", "stage3: system reset");
53 | rtc_cntl_ll_reset_system();
54 | }
55 |
56 | void test_stage4(void)
57 | {
58 | ESP_LOGI("multi_stage", "stage4: finish");
59 | }
60 |
61 | TEST_CASE_MULTIPLE_STAGES("multiple_stages_test", "[multi_stage]",
62 | test_stage1, test_stage2, test_stage3, test_stage4);
63 |
64 |
65 | void test_dev1(void)
66 | {
67 | ESP_LOGI("multi_dev", "dev1 start");
68 | unity_send_signal("signal 1 from dev 1");
69 | unity_wait_for_signal("signal 2 from dev 2");
70 | unity_send_signal("signal 3 from dev 1");
71 |
72 | for (int i = 0; i < 10; i++) {
73 | unity_wait_for_signal("continuous signal");
74 | }
75 | }
76 |
77 | void test_dev2(void)
78 | {
79 | ESP_LOGI("multi_dev", "dev2 start");
80 | unity_wait_for_signal("signal 1 from dev 1");
81 | unity_send_signal("signal 2 from dev 2");
82 | unity_wait_for_signal("signal 3 from dev 1");
83 | for (int i = 0; i < 10; i++) {
84 | unity_send_signal("continuous signal");
85 | }
86 | }
87 |
88 | TEST_CASE_MULTIPLE_DEVICES("multiple_devices_test", "[multi_dev][timeout=150]",
89 | test_dev1, test_dev2);
90 |
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_esp32c3/main/test_app_main.c:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | #include "unity.h"
8 | #include "unity_test_runner.h"
9 | #include "esp_heap_caps.h"
10 |
11 | #define TEST_MEMORY_LEAK_THRESHOLD (-200)
12 |
13 | static size_t before_free_8bit;
14 | static size_t before_free_32bit;
15 |
16 | static void check_leak(size_t before_free, size_t after_free, const char *type)
17 | {
18 | ssize_t delta = after_free - before_free;
19 | printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
20 | TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
21 | }
22 |
23 | void setUp(void)
24 | {
25 | before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
26 | before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
27 | }
28 |
29 | void tearDown(void)
30 | {
31 | size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
32 | size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
33 | check_leak(before_free_8bit, after_free_8bit, "8BIT");
34 | check_leak(before_free_32bit, after_free_32bit, "32BIT");
35 | }
36 |
37 | void app_main(void)
38 | {
39 | unity_run_menu();
40 | }
41 |
--------------------------------------------------------------------------------
/tests/fixtures/unit_test_app_linux/build/test_esp_event.elf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espressif/pytest-embedded/5f412f1280d432ecfff39d25792fb9400832439b/tests/fixtures/unit_test_app_linux/build/test_esp_event.elf
--------------------------------------------------------------------------------