⚠️ Clean-Room / Educativo

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

Fecha: 2026-01-03 Step ID: 0462 Estado: VERIFIED

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 os a nivel de módulo (correcto)
  • Línea 765: Uso de os.environ.get() dentro de run()
  • Línea 777: import os dentro de run() (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 - Eliminado import os redundante en línea 777
  • tests/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 shadowing
  • test_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 ausente
  • test_viboy_module_imports_without_crash() - Detecta UnboundLocalError al 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 - Eliminado import os redundante (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.gbc arranca sin UnboundLocalError

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

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 el import os de 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