fix(core): stop generate_files from clobbering the required_paths global

The directory-creation loop iterated as "for required_paths in
required_paths:" while declaring "global required_paths", so after the
first call the module-level list was rebound to the last iterated string
('data/labels'). A second call then iterated that string character by
character and attempted os.mkdir('d'), os.mkdir('a'), ... which raises
FileExistsError and aborts boot.

Rename the loop variable and drop the now-unneeded global declaration;
add regression tests pinning the module state and the repeat-call
behaviour.
This commit is contained in:
ShaoLOWu
2026-10-12 15:27:12 +08:00
parent ec021c3c41
commit 05a27c3c89
2 changed files with 51 additions and 5 deletions
+3 -5
View File
@@ -18,13 +18,11 @@ required_paths = [
async def generate_files() -> list[str]:
global required_files, required_paths
from ...utils import paths as path_utils
for required_paths in required_paths:
if not os.path.exists(required_paths):
os.mkdir(required_paths)
for path in required_paths:
if not os.path.exists(path):
os.mkdir(path)
generated_files = []
for file in required_files:
@@ -0,0 +1,48 @@
"""Regression tests for bootutils.files.generate_files.
Historically the directory-creation loop iterated as
``for required_paths in required_paths:`` while the function declared
``global required_paths``, which rebound the module-level list to the last
iterated string (``'data/labels'``) after the first call. A second call then
iterated over that string character by character and attempted to create
single-character directories. These tests pin the module-level state and the
repeat-call behaviour.
"""
from __future__ import annotations
import asyncio
from importlib import import_module
def get_files_module():
"""Lazy import to avoid circular import issues."""
return import_module('langbot.pkg.core.bootutils.files')
def test_required_paths_stays_a_list_after_generate_files(tmp_path, monkeypatch):
files = get_files_module()
monkeypatch.chdir(tmp_path)
original = list(files.required_paths)
asyncio.run(files.generate_files())
assert isinstance(files.required_paths, list)
assert files.required_paths == original
def test_generate_files_is_idempotent(tmp_path, monkeypatch):
files = get_files_module()
monkeypatch.chdir(tmp_path)
first = asyncio.run(files.generate_files())
second = asyncio.run(files.generate_files())
for path in files.required_paths:
assert (tmp_path / path).is_dir()
# The config template is copied on the first run only.
assert first == ['data/config.yaml']
assert second == []
assert (tmp_path / 'data' / 'config.yaml').is_file()
# No junk single-character directories left behind by a second run.
assert sorted(p.name for p in tmp_path.iterdir()) == ['data', 'temp']