This is the documentation for the latest (main) development branch. If you are looking for the documentation of previous releases, use the drop-down menu on the left and select the desired version.

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 counting

      • TestTreePrint - Tree printing with /memreserve/

      • TestNodeAccess - Node access by path, number, phandle

      • TestCustomNodeLists - Custom iterations and restricted walks

      • TestSubnodeCalls - Subnode access methods

      • TestResolveReferences - Reference resolution

      • TestNodeStringRepresentation - Node __str__ methods

      • TestNodeRegexFind - Regex-based node searching

      • TestPropertyRegexFind - Property regex matching

      • TestTreeManipulation - Node creation and addition

      • TestTreeResolveAndExport - Tree resolve and export

      • TestNodeDeepCopy - Node deep copying

      • TestPropertyManipulation - Property operations

      • TestPropertyAccess - Property index and dict access

      • TestAliases - 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 operations

      • TestYAMLToTree - YAML to tree conversion and DTS writing

      • TestSDTToYAML - Device tree to YAML conversion

      • TestComplexPropertyAccess - 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 dictionary

      • TestTreeLoadFromFDT - Loading tree from exported FDT

      • TestTreeSync - Syncing tree changes back to FDT

      • TestNodeDeletion - Node and property deletion

      • TestNodeAddition - Adding nodes and properties

      • TestNodeIteration - Tree and subnode iteration

      • TestStringTypeDetection - 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

  1. Clear failure messages: See exactly what went wrong

    AssertionError: Expected 22 nodes, got 20
    assert 20 == 22
    
  2. Better organization: Test classes group related tests

    class TestNodeAccess:
        def test_by_path(self): ...
        def test_by_number(self): ...
    
  3. Reusable setup: Fixtures eliminate duplicate setup code

    @pytest.fixture
    def configured_tree(lopper_tree):
        # Setup code runs once, used by many tests
        return tree
    
  4. 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
    
  5. 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:

  1. Pytest tests run first (faster feedback)

  2. Legacy tests run second (comprehensive coverage)

  3. Both must pass for CI to succeed

See .github/workflows/ci.yml and scripts/run_tests.sh.

Questions?

  • See tests/test_tree.py for working examples

  • Check tests/README.md for running tests

  • Review tests/conftest.py for available fixtures

  • Refer 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)