Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,40 @@
*.pyc
.piolibdeps
.pio

# Testing
.pytest_cache/
.coverage
htmlcov/
coverage.xml
.tox/
.nox/

# Virtual environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# IDEs
.vscode/
.idea/
*.swp
*.swo
*~

# Build artifacts
build/
dist/
*.egg-info/
__pycache__/

# OS
.DS_Store
Thumbs.db

# Claude
.claude/*
697 changes: 697 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
[tool.poetry]
name = "platform-raspberrypi"
version = "1.17.0"
description = "RP2040 is a low-cost, high-performance microcontroller device with a large on-chip memory, symmetric dual-core processor complex, and rich peripheral."
authors = ["PlatformIO <[email protected]>"]
license = "Apache-2.0"
readme = "README.md"
homepage = "https://www.raspberrypi.org/documentation/rp2040/getting-started/"
repository = "https://github.com/platformio/platform-raspberrypi.git"
keywords = ["dev-platform", "ARM", "Cortex-M", "Raspberry Pi", "RP2040", "platformio"]
package-mode = false

[tool.poetry.dependencies]
python = "^3.8"
platformio = "^6.0.0"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
pytest-cov = "^4.0.0"
pytest-mock = "^3.12.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--strict-config",
"--verbose",
"--cov=platform.py",
"--cov=builder",
"--cov-report=term-missing",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-fail-under=80"
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Tests that take a long time to run"
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning"
]

[tool.coverage.run]
source = [".", "builder"]
omit = [
"tests/*",
"examples/*",
"misc/*",
"*/__pycache__/*",
"*/.*",
"setup.py"
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:"
]
show_missing = true
skip_covered = false

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"
Empty file added tests/__init__.py
Empty file.
149 changes: 149 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import os
import pytest
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock, MagicMock


@pytest.fixture
def temp_dir():
"""Create a temporary directory for test files."""
temp_path = tempfile.mkdtemp()
yield Path(temp_path)
shutil.rmtree(temp_path)


@pytest.fixture
def mock_platform_variables():
"""Mock platform variables for testing."""
return {
"board": "pico",
"upload_protocol": "picotool",
"debug_tool": "cmsis-dap"
}


@pytest.fixture
def mock_board_config():
"""Mock board configuration."""
config = Mock()
config.get.return_value = {
"debug.default_tools": ["cmsis-dap"],
"upload.protocol": "picotool"
}
return config


@pytest.fixture
def sample_board_manifest():
"""Sample board manifest data for testing."""
return {
"debug": {
"jlink_device": "RP2040_M0_0",
"openocd_target": "rp2040.cfg",
"onboard_tools": ["cmsis-dap"]
},
"upload": {
"protocols": ["cmsis-dap", "jlink", "raspberrypi-swd"]
}
}


@pytest.fixture
def mock_debug_config():
"""Mock debug configuration."""
config = Mock()
config.speed = "5000"
config.server = {
"arguments": ["-f", "interface/cmsis-dap.cfg"],
"executable": "openocd"
}
return config


@pytest.fixture
def platform_json_content():
"""Sample platform.json content for testing."""
return {
"name": "raspberrypi",
"title": "Raspberry Pi RP2040",
"version": "1.17.0",
"frameworks": {
"arduino": {
"package": "framework-arduino-mbed"
}
},
"packages": {
"toolchain-gccarmnoneeabi": {
"type": "toolchain",
"version": "~1.90201.0"
},
"tool-jlink": {
"type": "uploader",
"optional": True
}
}
}


@pytest.fixture
def mock_platformio_board():
"""Mock PlatformIO board object."""
board = Mock()
board.id = "pico"
board.manifest = {
"debug": {
"jlink_device": "RP2040_M0_0",
"openocd_target": "rp2040.cfg"
},
"upload": {
"protocols": ["cmsis-dap", "jlink"]
}
}
return board


@pytest.fixture(autouse=True)
def clean_environment():
"""Clean environment variables before each test."""
original_env = os.environ.copy()
yield
os.environ.clear()
os.environ.update(original_env)


@pytest.fixture
def mock_file_system(temp_dir):
"""Create a mock file system structure for testing."""
structure = {
"platform.json": '{"name": "raspberrypi", "version": "1.17.0"}',
"platform.py": "# Platform implementation",
"boards": {
"pico.json": '{"name": "Raspberry Pi Pico"}',
"nanorp2040connect.json": '{"name": "Arduino Nano RP2040 Connect"}'
},
"builder": {
"main.py": "# Builder main",
"frameworks": {
"arduino": {
"mbed-core": {
"arduino-core-mbed.py": "# Arduino core"
}
}
}
}
}

def create_structure(base_path, structure_dict):
for name, content in structure_dict.items():
path = base_path / name
if isinstance(content, dict):
path.mkdir(parents=True, exist_ok=True)
create_structure(path, content)
else:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content)

create_structure(temp_dir, structure)
return temp_dir
Empty file added tests/integration/__init__.py
Empty file.
86 changes: 86 additions & 0 deletions tests/test_setup_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import pytest
import sys
import importlib.util
from pathlib import Path


def test_python_imports():
"""Test that Python can import basic modules."""
import json
import os
import platform
assert True


def test_pytest_installed():
"""Test that pytest is properly installed."""
import pytest
assert hasattr(pytest, "main")


def test_coverage_installed():
"""Test that coverage tools are installed."""
try:
import coverage
assert True
except ImportError:
pytest.fail("Coverage module not installed")


def test_mock_installed():
"""Test that pytest-mock is installed."""
import pytest_mock
assert hasattr(pytest_mock, "MockerFixture")


def test_project_structure():
"""Test that the project has the expected structure."""
project_root = Path(__file__).parent.parent

assert (project_root / "platform.py").exists()
assert (project_root / "platform.json").exists()
assert (project_root / "pyproject.toml").exists()
assert (project_root / "tests").is_dir()
assert (project_root / "tests" / "__init__.py").exists()
assert (project_root / "tests" / "conftest.py").exists()


def test_platform_module_importable():
"""Test that the platform module can be imported."""
project_root = Path(__file__).parent.parent
platform_path = project_root / "platform.py"

spec = importlib.util.spec_from_file_location("platform", platform_path)
module = importlib.util.module_from_spec(spec)

try:
spec.loader.exec_module(module)
assert hasattr(module, "RaspberrypiPlatform")
except ImportError as e:
pytest.skip(f"Platform module requires PlatformIO dependencies: {e}")


def test_fixtures_available(temp_dir, mock_platform_variables):
"""Test that shared fixtures are working."""
assert temp_dir.exists()
assert temp_dir.is_dir()
assert isinstance(mock_platform_variables, dict)
assert "board" in mock_platform_variables


@pytest.mark.unit
def test_unit_marker():
"""Test that unit test marker works."""
assert True


@pytest.mark.integration
def test_integration_marker():
"""Test that integration test marker works."""
assert True


@pytest.mark.slow
def test_slow_marker():
"""Test that slow test marker works."""
assert True
Empty file added tests/unit/__init__.py
Empty file.