Files
esp-idf/components/esp_blockdev/test/test_check_ioctl_overlap.py

158 lines
5.8 KiB
Python

# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import shutil
import sys
import tempfile
import unittest
from pathlib import Path
COMPONENT_DIR = Path(__file__).resolve().parents[1]
TOOLS_DIR = COMPONENT_DIR / 'tools'
ESP_BLOCKDEV_H = COMPONENT_DIR / 'include' / 'esp_blockdev.h'
ESP_COMMON_INCLUDE = COMPONENT_DIR.parent / 'esp_common' / 'include'
sys.path.insert(0, str(TOOLS_DIR))
import check_ioctl_overlap as checker # noqa: E402
COMPILER = shutil.which('gcc') or shutil.which('cc')
INCLUDE_DIRS = [str(ESP_COMMON_INCLUDE), str(ESP_BLOCKDEV_H.parent)]
# The header itself reserves [0x00..0x01] for esp_blockdev core commands.
# Tests that include esp_blockdev.h will see that reservation.
CORE_RESERVATION_END = 0x01
def run_check(contents: str) -> list[checker.Reservation]:
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
(tmp_path / 'sdkconfig.h').write_text('/* test stub */\n')
header = tmp_path / 'defs.h'
header.write_text(contents)
include_dirs = [tmp, *INCLUDE_DIRS]
return checker.check_files([header], compiler=COMPILER, include_dirs=include_dirs) # type: ignore[no-any-return]
class TestIoctlOverlapChecker(unittest.TestCase):
@unittest.skipUnless(COMPILER, 'C compiler required for preprocessing')
def test_no_overlap(self) -> None:
# Use ranges that don't overlap with core [0x00..0x01]
reservations = run_check(
f'''#include "{ESP_BLOCKDEV_H}"
#define BASE 0x10
ESP_BLOCKDEV_RESERVE_CMD_RANGE(nand_flash, BASE + 0, BASE + 0x0F);
ESP_BLOCKDEV_RESERVE_CMD_RANGE(sdcard, BASE + 0x10, BASE + 0x1F);
ESP_BLOCKDEV_RESERVE_CMD(system, 0x40);
'''
)
# 3 from this file + 1 from esp_blockdev.h core
self.assertEqual(len(reservations), 4)
system_reservations = [r for r in reservations if r.component == 'system']
self.assertEqual(len(system_reservations), 1)
self.assertEqual(system_reservations[0].start, 0x40)
self.assertEqual(system_reservations[0].end, 0x40)
@unittest.skipUnless(COMPILER, 'C compiler required for preprocessing')
def test_direct_overlap(self) -> None:
with self.assertRaises(checker.ReservationError) as ctx:
run_check(
f'''#include "{ESP_BLOCKDEV_H}"
ESP_BLOCKDEV_RESERVE_CMD_RANGE(nand_flash, 0x10, 0x20);
ESP_BLOCKDEV_RESERVE_CMD_RANGE(sdcard, 0x1F, 0x30);
'''
)
msg = str(ctx.exception)
self.assertIn('ioctl reservation overlap:', msg)
self.assertIn('nand_flash', msg)
self.assertIn('sdcard', msg)
@unittest.skipUnless(COMPILER, 'C compiler required for preprocessing')
def test_adjacent_non_overlap(self) -> None:
reservations = run_check(
f'''#include "{ESP_BLOCKDEV_H}"
ESP_BLOCKDEV_RESERVE_CMD_RANGE(a, 0x10, 0x1F);
ESP_BLOCKDEV_RESERVE_CMD_RANGE(b, 0x20, 0x2F);
'''
)
# 2 from this file + 1 core
self.assertEqual(len(reservations), 3)
@unittest.skipUnless(COMPILER, 'C compiler required for preprocessing')
def test_single_command_reservation(self) -> None:
reservations = run_check(
f'''#include "{ESP_BLOCKDEV_H}"
ESP_BLOCKDEV_RESERVE_CMD(single_cmd, 0x42);
ESP_BLOCKDEV_RESERVE_CMD_RANGE(other, 0x43, 0x44);
'''
)
single = [r for r in reservations if r.component == 'single_cmd']
self.assertEqual(len(single), 1)
self.assertEqual(single[0].start, 0x42)
self.assertEqual(single[0].end, 0x42)
@unittest.skipUnless(COMPILER, 'C compiler required for preprocessing')
def test_reversed_range(self) -> None:
with self.assertRaises(checker.ReservationError) as ctx:
run_check(
f'''#include "{ESP_BLOCKDEV_H}"
ESP_BLOCKDEV_RESERVE_CMD_RANGE(reversed, 0x20, 0x10);
'''
)
self.assertIn('start 0x20 > end 0x10', str(ctx.exception))
@unittest.skipUnless(COMPILER, 'C compiler required for preprocessing')
def test_out_of_range_values(self) -> None:
with self.assertRaises(checker.ReservationError) as ctx:
run_check(
f'''#include "{ESP_BLOCKDEV_H}"
ESP_BLOCKDEV_RESERVE_CMD_RANGE(big, 0x100, 0x101);
'''
)
self.assertIn('out of range', str(ctx.exception))
@unittest.skipUnless(COMPILER, 'C compiler required for preprocessing')
def test_macro_expanded_constants(self) -> None:
reservations = run_check(
f'''#include "{ESP_BLOCKDEV_H}"
#define BASE ESP_BLOCKDEV_CMD_SYSTEM_BASE
ESP_BLOCKDEV_RESERVE_CMD_RANGE(range_a, BASE + 10, BASE + 20);
ESP_BLOCKDEV_RESERVE_CMD(single, BASE + 2);
'''
)
# These overlap with core [0x00..0x01], but single=2 is adjacent (OK),
# range_a=[10..20] is non-overlapping with core. However single=2 > core end=1, OK.
range_a = [r for r in reservations if r.component == 'range_a']
single = [r for r in reservations if r.component == 'single']
self.assertEqual(len(range_a), 1)
self.assertEqual(range_a[0].start, 10)
self.assertEqual(range_a[0].end, 20)
self.assertEqual(len(single), 1)
self.assertEqual(single[0].start, 2)
self.assertEqual(single[0].end, 2)
@unittest.skipUnless(COMPILER, 'C compiler required for preprocessing')
def test_overlap_with_core_commands(self) -> None:
"""Reserving a range that overlaps with core [0x00..0x01] must fail."""
with self.assertRaises(checker.ReservationError) as ctx:
run_check(
f'''#include "{ESP_BLOCKDEV_H}"
ESP_BLOCKDEV_RESERVE_CMD_RANGE(my_comp, 0x00, 0x05);
'''
)
msg = str(ctx.exception)
self.assertIn('esp_blockdev', msg)
self.assertIn('my_comp', msg)
if __name__ == '__main__':
unittest.main()