Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.
Hotfix os Shadowing + Test Anti-Regresión
Resumen
Corrección crítica del bug UnboundLocalError: cannot access local variable 'os' que impedía que el emulador arrancara. El bug fue introducido en Step 0461 por un import os redundante dentro de run() que shadoweaba el os importado a nivel de módulo. Solución: Eliminación del import redundante. Test anti-regresión añadido para detectar este tipo de bugs antes del push.
Concepto de Hardware
Este Step no trata sobre hardware del Game Boy, sino sobre un concepto fundamental de Python: variable shadowing y el comportamiento del intérprete al analizar funciones.
Variable Shadowing en Python: Cuando Python analiza una función, identifica todas las asignaciones a una variable (incluyendo import). Si encuentra una asignación a os en cualquier parte de la función, marca os como variable local para toda la función, incluso antes de que se ejecute la asignación.
El Problema: En src/viboy.py:
- Línea 28:
import osa nivel de módulo (correcto) - Línea 765: Uso de
os.environ.get()dentro derun() - Línea 777:
import osdentro derun()(shadowing)
Python detecta el import os de la línea 777 y marca os como variable local para toda la función run(). Al ejecutar la línea 765, os aún no está definida localmente (el import de la línea 777 no se ha ejecutado), causando UnboundLocalError.
La Solución: Eliminar el import redundante. El os importado a nivel de módulo (línea 28) es accesible desde cualquier función del módulo, por lo que el import local es innecesario y causa el bug.
Implementación
El fix es mínimo y directo: eliminar el import os redundante dentro de run().
Componentes modificados
src/viboy.py- Eliminadoimport osredundante en línea 777tests/test_runtime_flags_no_crash_0462.py- Test anti-regresión nuevo
Cambio aplicado
Antes (línea 777):
# --- Step 0393: Toggle de trazas via variable de entorno ---
# VBC_TRACE=1 activa trazas, por defecto desactivadas para rendimiento
import os
ENABLE_DEBUG_LOGS = os.getenv('VBC_TRACE', '0') == '1'
# ------------------------------------------------------------------------------------
Después:
# --- Step 0393: Toggle de trazas via variable de entorno ---
# VBC_TRACE=1 activa trazas, por defecto desactivadas para rendimiento
# Step 0462: Eliminado import os redundante (ya importado a nivel de módulo línea 28)
ENABLE_DEBUG_LOGS = os.getenv('VBC_TRACE', '0') == '1'
# ------------------------------------------------------------------------------------
Test Anti-Regresión
Se creó tests/test_runtime_flags_no_crash_0462.py con 4 tests que verifican:
test_env_flag_helper_importable()- Verifica que el módulo se puede importar sin shadowingtest_env_flag_helper_with_env_on()- Verifica flags de entorno con valor '1'test_env_flag_helper_with_env_off()- Verifica flags de entorno con valor '0' o ausentetest_viboy_module_imports_without_crash()- DetectaUnboundLocalErroral importar el módulo
Estos tests son baratos (no requieren Pygame ni inicialización completa del emulador) y detectan el bug antes del push.
Archivos Afectados
src/viboy.py- Eliminadoimport osredundante (línea 777)tests/test_runtime_flags_no_crash_0462.py- Test anti-regresión nuevo (4 tests)
Tests y Verificación
Validación de la implementación:
- Test anti-regresión: 4/4 tests pasan
- Verificación de import:
python3 -c "import src.viboy"ejecuta sin errores - Verificación de arranque:
python3 main.py roms/mario.gbcarranca sinUnboundLocalError
Comando ejecutado
pytest -v tests/test_runtime_flags_no_crash_0462.py
Resultado
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0
collected 4 items
tests/test_runtime_flags_no_crash_0462.py::test_env_flag_helper_importable PASSED [ 25%]
tests/test_runtime_flags_no_crash_0462.py::test_env_flag_helper_with_env_on PASSED [ 50%]
tests/test_runtime_flags_no_crash_0462.py::test_env_flag_helper_with_env_off PASSED [ 75%]
tests/test_runtime_flags_no_crash_0462.py::test_viboy_module_imports_without_crash PASSED [100%]
============================== 4 passed in 0.32s ===============================
Código del Test
def test_viboy_module_imports_without_crash():
"""Verifica que el módulo viboy se puede importar sin crashes.
Este test detecta UnboundLocalError y otros errores de importación.
"""
try:
import src.viboy
# Si llegamos aquí, el import fue exitoso
assert True
except UnboundLocalError as e:
pytest.fail(f"UnboundLocalError al importar viboy (posible shadowing): {e}")
except Exception as e:
# Otros errores son aceptables (ej: pygame no disponible en CI)
# pero UnboundLocalError NO
if "UnboundLocalError" in str(type(e)):
pytest.fail(f"UnboundLocalError inesperado: {e}")
Validación Nativa: Validación de módulo compilado C++ no aplicable (este fix es solo Python).
Fuentes Consultadas
- Python Documentation: Scopes and Namespaces
- Python Documentation: UnboundLocalError FAQ
Integridad Educativa
Lo que Entiendo Ahora
- Variable Shadowing en Python: Python analiza toda la función antes de ejecutarla. Si encuentra una asignación a una variable (incluyendo
import), marca esa variable como local para toda la función, incluso antes de que se ejecute la asignación. - UnboundLocalError: Ocurre cuando se intenta acceder a una variable local antes de que se haya asignado un valor. En este caso, el acceso a
os.environ.get()en la línea 765 ocurre antes de que se ejecute elimport osde la línea 777. - Imports a Nivel de Módulo: Los imports a nivel de módulo son accesibles desde cualquier función del módulo. No es necesario re-importar dentro de funciones a menos que haya una razón específica (ej: import condicional).
Lo que Falta Confirmar
- Nada pendiente - el fix es directo y está verificado.
Hipótesis y Suposiciones
No hay suposiciones. El comportamiento de Python está bien documentado y el fix es estándar.
Próximos Pasos
- [ ] Continuar con el desarrollo normal del emulador
- [ ] Considerar añadir más tests anti-regresión para otros tipos de bugs comunes