diff --git a/ports/zephyr-cp/tests/conftest.py b/ports/zephyr-cp/tests/conftest.py index 03451048324de..321bb8ae7735f 100644 --- a/ports/zephyr-cp/tests/conftest.py +++ b/ports/zephyr-cp/tests/conftest.py @@ -289,12 +289,22 @@ def circuitpython(request, board, sim_id, native_sim_binary, native_sim_env, tmp tmp_drive = tmp_path / f"drive{i}" tmp_drive.mkdir(exist_ok=True) + created_dirs: set[str] = set() for name, content in files.items(): src = tmp_drive / name + src.parent.mkdir(parents=True, exist_ok=True) if isinstance(content, bytes): src.write_bytes(content) else: src.write_text(content) + parent = Path(name).parent + if parent != Path("."): + parts = parent.parts + for depth in range(1, len(parts) + 1): + sub = "/".join(parts[:depth]) + if sub not in created_dirs: + subprocess.run(["mmd", "-i", str(flash), f"::{sub}"], check=True) + created_dirs.add(sub) subprocess.run(["mcopy", "-i", str(flash), str(src), f"::{name}"], check=True) trace_file = tmp_path / f"trace-{i}.perfetto" diff --git a/ports/zephyr-cp/tests/test_basics.py b/ports/zephyr-cp/tests/test_basics.py index 84b31849a8e81..7247f375f1524 100644 --- a/ports/zephyr-cp/tests/test_basics.py +++ b/ports/zephyr-cp/tests/test_basics.py @@ -92,6 +92,28 @@ def test_ctrl_c_interrupt(circuitpython): assert "completed" not in output +IMPORT_PRECEDENCE_CODE = """\ +import fake_lib +print("done") +""" + + +@pytest.mark.circuitpy_drive( + { + "code.py": IMPORT_PRECEDENCE_CODE, + "fake_lib/a_spritesheet.bmp": b"", + "fake_lib.py": 'print("hello fake_lib.py")\n', + } +) +def test_py_file_wins_over_namespace_dir(circuitpython): + """#10614: a .py module beats a sibling directory lacking __init__.py.""" + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + assert "hello fake_lib.py" in output + assert "done" in output + + RELOAD_CODE = """\ print("first run") import time diff --git a/py/builtinimport.c b/py/builtinimport.c index 8fcac22ccb491..5d77ac42859aa 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -105,7 +105,26 @@ static mp_import_stat_t stat_module(vstr_t *path) { mp_import_stat_t stat = stat_path(path); DEBUG_printf("stat %s: %d\n", vstr_str(path), stat); if (stat == MP_IMPORT_STAT_DIR) { - return stat; + // CIRCUITPY-CHANGE: match CPython import precedence. A regular + // package (directory with __init__.py/.mpy) takes precedence, then a + // sibling .py/.mpy module, and only then a namespace package + // (directory without __init__). See + // https://docs.python.org/3/reference/import.html#regular-packages + size_t orig_len = path->len; + vstr_add_str(path, PATH_SEP_CHAR "__init__.py"); + mp_import_stat_t init_stat = stat_file_py_or_mpy(path); + path->len = orig_len; + if (init_stat == MP_IMPORT_STAT_FILE) { + return MP_IMPORT_STAT_DIR; + } + + vstr_add_str(path, ".py"); + mp_import_stat_t file_stat = stat_file_py_or_mpy(path); + if (file_stat == MP_IMPORT_STAT_FILE) { + return file_stat; + } + path->len = orig_len; + return MP_IMPORT_STAT_DIR; } // Not a directory, add .py and try as a file.