Pytest Migration Guideο
This document describes the ongoing migration from lopper_sanity.py to pytest-based testing.
Current Statusο
β Completedο
Tree tests (
tests/test_tree.py) - 42 tests migrated (100% complete)Complete 1:1 migration of
tree_sanity_test()from lopper_sanity.py (lines 1260-1985)All test classes migrated:
TestBasicTreeWalking- Tree iteration and node countingTestTreePrint- Tree printing with /memreserve/TestNodeAccess- Node access by path, number, phandleTestCustomNodeLists- Custom iterations and restricted walksTestSubnodeCalls- Subnode access methodsTestResolveReferences- Reference resolutionTestNodeStringRepresentation- Node__str__methodsTestNodeRegexFind- Regex-based node searchingTestPropertyRegexFind- Property regex matchingTestTreeManipulation- Node creation and additionTestTreeResolveAndExport- Tree resolve and exportTestNodeDeepCopy- Node deep copyingTestPropertyManipulation- Property operationsTestPropertyAccess- Property index and dict accessTestAliases- Alias lookups
YAML tests (
tests/test_yaml.py) - 6 tests migrated (100% complete)Complete 1:1 migration of
yaml_sanity_test()from lopper_sanity.py (lines 2534-2569)All test classes migrated:
TestYAMLReadWrite- YAML load and write operationsTestYAMLToTree- YAML to tree conversion and DTS writingTestSDTToYAML- Device tree to YAML conversionTestComplexPropertyAccess- Complex nested property access from YAML
FDT tests (
tests/test_fdt.py) - 15 tests migrated (100% complete)Complete 1:1 migration of
fdt_sanity_test()from lopper_sanity.py (lines 2412-2533)All test classes migrated:
TestFDTExport- FDT export to dictionaryTestTreeLoadFromFDT- Loading tree from exported FDTTestTreeSync- Syncing tree changes back to FDTTestNodeDeletion- Node and property deletionTestNodeAddition- Adding nodes and propertiesTestNodeIteration- Tree and subnode iterationTestStringTypeDetection- String decoding in node printing
Schema tests (
tests/test_schema.py) - 14 tests migrated (100% complete)Complete 1:1 migration of
schema_type_sanity_test()from lopper_sanity.py (lines 2335-2411)All test classes migrated:
TestSchemaTypeDetection- Schema-based property type detection (8 tests)TestPropertyFormatPreservation- Format preservation in output (6 tests)
Format tests (
tests/test_format.py) - 1 test migrated (100% complete)Complete 1:1 migration of
format_sanity_test()from lopper_sanity.py (lines 2328-2333)Test class migrated:
TestDTSWrite- DTS writing with enhanced mode
Lops tests (
tests/test_lops.py) - 18 tests migrated (100% complete)Complete 1:1 migration of
lops_sanity_test()from lopper_sanity.py (lines 2172-2305)All test classes migrated:
TestLopsNodeDeletion- Node deletion via lops (1 test)TestLopsNodeRename- Node renaming via lops (1 test)TestLopsPropertyRemoval- Property removal via lops (2 tests)TestLopsNodeAddition- Node addition via lops (1 test)TestLopsPropertyModification- Property modification via lops (2 tests)TestLopsSelectiveOutput- Selective node output (2 tests)TestLopsPropertyAddition- Property addition via lops (2 tests)TestLopsSubtrees- Subtree operations (3 tests)TestLopsListModification- List property modifications (4 tests)
π TODO - Medium Priorityο
Lops code tests - Migrate
lops_code_test()
π TODO - Lower Priorityο
Assists tests - Migrate
assists_sanity_test()OpenAMP tests - Migrate
openamp_sanity_test()Domain generation tests - Migrate
xlnx_gen_domain_sanity_test()
Migration Processο
Step 1: Choose a Test Suiteο
Pick a test function from lopper_sanity.py to migrate (e.g., yaml_sanity_test).
Step 2: Create Test Fileο
Create tests/test_<feature>.py with test classes:
"""
Tests for <feature> functionality.
Migrated from lopper_sanity.py's <feature>_sanity_test() function.
"""
class Test<FeatureName>:
"""Tests for <specific aspect>."""
def test_something(self, lopper_tree):
"""Test that something works."""
# Your test code here
assert expected == actual
Step 3: Use Fixturesο
Available fixtures in tests/conftest.py:
test_outdir- Temporary directory (session-scoped)system_device_tree- Path to test DTS file (session-scoped)compiled_fdt- Compiled FDT object (session-scoped)lopper_tree- Fresh LopperTree instance (function-scoped)
Step 4: Convert Assertionsο
Before (lopper_sanity.py):
if node_count != 22:
test_failed(f"node count is incorrect ({node_count} expected 22)")
else:
test_passed("node walk passed")
After (pytest):
assert node_count == 22, f"Expected 22 nodes, got {node_count}"
Step 5: Run Testsο
# Run your new tests
pytest tests/test_<feature>.py -v
# Run all tests (pytest + legacy)
./scripts/run_tests.sh
Step 6: Update Documentationο
Update this fileβs βCurrent Statusβ section and tests/README.md.
Benefits of Pytestο
Clear failure messages: See exactly what went wrong
AssertionError: Expected 22 nodes, got 20 assert 20 == 22
Better organization: Test classes group related tests
class TestNodeAccess: def test_by_path(self): ... def test_by_number(self): ...
Reusable setup: Fixtures eliminate duplicate setup code
@pytest.fixture def configured_tree(lopper_tree): # Setup code runs once, used by many tests return tree
Parametrization: Test multiple inputs easily
@pytest.mark.parametrize("path,expected", [ ("/cpus", "cpus"), ("/memory@0", "memory@0"), ]) def test_node_names(lopper_tree, path, expected): assert lopper_tree[path].name == expected
Parallel execution: Run tests faster
pytest -n auto # Uses all CPU cores
Common Patternsο
Checking File Contentsο
Before:
with open(output_file) as fp:
content = fp.read()
if "expected string" not in content:
test_failed("String not found")
After:
with open(output_file) as fp:
content = fp.read()
assert "expected string" in content
Counting Occurrencesο
Before:
count = 0
for line in file:
if pattern in line:
count += 1
if count != expected:
test_failed(f"Found {count}, expected {expected}")
After:
with open(file) as fp:
count = sum(1 for line in fp if pattern in line)
assert count == expected, f"Expected {expected} matches, found {count}"
Testing Exceptionsο
Before:
try:
risky_operation()
test_failed("Should have raised an exception")
except ExpectedException:
test_passed("Correctly raised exception")
After:
import pytest
with pytest.raises(ExpectedException):
risky_operation()
CI Integrationο
Both pytest and legacy tests run in CI:
Pytest tests run first (faster feedback)
Legacy tests run second (comprehensive coverage)
Both must pass for CI to succeed
See .github/workflows/ci.yml and scripts/run_tests.sh.
Questions?ο
See
tests/test_tree.pyfor working examplesCheck
tests/README.mdfor running testsReview
tests/conftest.pyfor available fixturesRefer to pytest documentation
Timelineο
Phase 1 (Complete): Tree tests migrated, CI updated
Phase 2 (2-3 weeks): Core tests (YAML, FDT, Schema)
Phase 3 (3-4 weeks): Complex tests (Assists, OpenAMP)
Phase 4 (1 week): Remove legacy lopper_sanity.py (optional)