velxio/test/backend/unit/test_espidf_real_paths.py

240 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Diagnostic test: exercises _resolve_library_components with the REAL library
paths on this machine (C:/Users/David/Documents/Arduino/libraries and
C:/Espressif/components/arduino-esp32/libraries).
This reproduces the exact SSD1306 compilation failure and shows exactly what
user_libs/ structure the current code produces.
Run:
python test/backend/unit/test_espidf_real_paths.py
or:
python -m pytest test/backend/unit/test_espidf_real_paths.py -v -s
"""
import importlib
import os
import sys
import tempfile
import shutil
import unittest
from pathlib import Path
# Force fresh import — bypass any .pyc cache
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / 'backend'))
# Delete cached module if already imported so we get the live .py file
for key in list(sys.modules.keys()):
if 'espidf_compiler' in key:
del sys.modules[key]
from app.services.espidf_compiler import ESPIDFCompiler
# ── Real paths on this machine ────────────────────────────────────────────────
ARDUINO_LIBS = Path.home() / 'Documents' / 'Arduino' / 'libraries'
ESP32_LIBS = Path('C:/Espressif/components/arduino-esp32/libraries')
SSD1306_SKETCH_HEADERS = ['Wire.h', 'Adafruit_GFX.h', 'Adafruit_SSD1306.h']
# ── SSD1306 sketch (same as the example) ─────────────────────────────────────
SSD1306_SKETCH = """\
// ESP32 — SSD1306 OLED Display (I2C 128×64)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.display();
}
void loop() {
delay(1000);
}
"""
def make_compiler_with_real_paths() -> ESPIDFCompiler:
comp = ESPIDFCompiler.__new__(ESPIDFCompiler)
comp.idf_path = 'C:/Espressif/frameworks/esp-idf-v4.4.7'
comp.arduino_path = str(ESP32_LIBS.parent) # C:/Espressif/components/arduino-esp32
comp.has_arduino = ESP32_LIBS.parent.is_dir()
return comp
@unittest.skipIf(
os.environ.get('CI') == 'true',
'Requires local Arduino libraries installed on Windows — skipped in CI',
)
class TestRealPathsSSD1306(unittest.TestCase):
"""
Uses actual library directories on this machine to reproduce the build failure.
Only runs locally (not in CI) because it needs real Arduino library paths.
"""
def setUp(self):
self.comp = make_compiler_with_real_paths()
self.tmp = tempfile.mkdtemp(prefix='velxio_diag_')
self.user_libs = Path(self.tmp) / 'user_libs'
self.user_libs.mkdir()
def tearDown(self):
shutil.rmtree(self.tmp, ignore_errors=True)
# ── Pre-conditions ────────────────────────────────────────────────────────
def test_prereq_arduino_libs_exists(self):
self.assertTrue(ARDUINO_LIBS.is_dir(),
f'Arduino libs not found at {ARDUINO_LIBS}')
def test_prereq_gfx_library_installed(self):
gfx = ARDUINO_LIBS / 'Adafruit_GFX_Library'
self.assertTrue(gfx.is_dir(),
'Adafruit_GFX_Library not installed — install via Library Manager')
def test_prereq_ssd1306_library_installed(self):
ssd = ARDUINO_LIBS / 'Adafruit_SSD1306'
self.assertTrue(ssd.is_dir(),
'Adafruit_SSD1306 not installed — install via Library Manager')
def test_prereq_gfx_header_exists(self):
h = ARDUINO_LIBS / 'Adafruit_GFX_Library' / 'Adafruit_GFX.h'
self.assertTrue(h.exists(), f'Adafruit_GFX.h not found at {h}')
def test_prereq_ssd1306_header_exists(self):
h = ARDUINO_LIBS / 'Adafruit_SSD1306' / 'Adafruit_SSD1306.h'
self.assertTrue(h.exists(), f'Adafruit_SSD1306.h not found at {h}')
# ── Module identity ───────────────────────────────────────────────────────
def test_loaded_module_has_user_libs_all(self):
"""Verify the LIVE .py file is loaded, not stale .pyc."""
import inspect
source = inspect.getsource(ESPIDFCompiler._resolve_library_components)
self.assertIn('user_libs_all', source,
'STALE CODE: _resolve_library_components does not contain '
'"user_libs_all" — the old bytecode (.pyc) is being used. '
'Kill uvicorn, delete __pycache__, restart.')
def test_module_file_path(self):
"""Print the actual file being loaded."""
import app.services.espidf_compiler as mod
print(f'\n[diag] Loaded module from: {mod.__file__}')
self.assertIn('espidf_compiler', mod.__file__)
# ── _detect_external_includes ─────────────────────────────────────────────
def test_detect_finds_gfx_and_ssd1306(self):
"""Headers from the SSD1306 sketch must all be detected."""
result = self.comp._detect_external_includes(SSD1306_SKETCH)
print(f'\n[diag] _detect_external_includes → {result}')
self.assertIn('Adafruit_GFX.h', result,
'Adafruit_GFX.h not detected — it was silently skipped')
self.assertIn('Adafruit_SSD1306.h', result)
self.assertIn('Wire.h', result)
# ── _find_library_for_header ──────────────────────────────────────────────
def test_find_gfx_in_arduino_libs(self):
result = self.comp._find_library_for_header('Adafruit_GFX.h', ARDUINO_LIBS)
print(f'\n[diag] _find_library_for_header(Adafruit_GFX.h) → {result}')
self.assertIsNotNone(result,
f'Adafruit_GFX.h not found in {ARDUINO_LIBS}')
def test_find_ssd1306_in_arduino_libs(self):
result = self.comp._find_library_for_header('Adafruit_SSD1306.h', ARDUINO_LIBS)
print(f'\n[diag] _find_library_for_header(Adafruit_SSD1306.h) → {result}')
self.assertIsNotNone(result,
f'Adafruit_SSD1306.h not found in {ARDUINO_LIBS}')
def test_find_arduino_libs_dir(self):
result = self.comp._find_arduino_libraries_dir()
print(f'\n[diag] _find_arduino_libraries_dir() → {result}')
self.assertIsNotNone(result, '_find_arduino_libraries_dir() returned None')
self.assertTrue(result.is_dir())
# ── _resolve_library_components ───────────────────────────────────────────
def test_resolve_creates_user_libs_all(self):
"""After resolution, user_libs/ must contain user_libs_all/, NOT separate dirs."""
arduino_libs = self.comp._find_arduino_libraries_dir()
esp32_libs = Path(self.comp.arduino_path) / 'libraries' if self.comp.arduino_path else None
names, h2c = self.comp._resolve_library_components(
SSD1306_SKETCH_HEADERS,
arduino_libs, esp32_libs,
'arduino-esp32', self.user_libs,
)
print(f'\n[diag] component_names = {names}')
print(f'[diag] header_to_comp = {h2c}')
print(f'[diag] user_libs/ contents:')
for p in sorted(self.user_libs.rglob('*')):
print(f' {p.relative_to(self.user_libs)}')
self.assertEqual(names, ['user_libs_all'],
f'Expected ["user_libs_all"], got {names}.\n'
'If names contains "Adafruit_SSD1306" etc., the old code is still running.')
def test_gfx_header_in_user_libs_all(self):
"""Adafruit_GFX.h must be inside user_libs_all/, not in a separate dir."""
arduino_libs = self.comp._find_arduino_libraries_dir()
esp32_libs = Path(self.comp.arduino_path) / 'libraries' if self.comp.arduino_path else None
self.comp._resolve_library_components(
SSD1306_SKETCH_HEADERS,
arduino_libs, esp32_libs,
'arduino-esp32', self.user_libs,
)
gfx_in_all = self.user_libs / 'user_libs_all' / 'Adafruit_GFX.h'
gfx_separate = self.user_libs / 'Adafruit_GFX_Library' / 'Adafruit_GFX.h'
print(f'\n[diag] user_libs_all/Adafruit_GFX.h exists: {gfx_in_all.exists()}')
print(f'[diag] separate Adafruit_GFX_Library/Adafruit_GFX.h exists: {gfx_separate.exists()}')
self.assertTrue(gfx_in_all.exists(),
'Adafruit_GFX.h NOT in user_libs_all — '
'when Adafruit_SSD1306.cpp compiles it cannot find it')
self.assertFalse(gfx_separate.exists(),
'Separate Adafruit_GFX_Library/ dir found — OLD CODE IS RUNNING')
def test_cmake_lists_both_cpp_files(self):
"""user_libs_all/CMakeLists.txt must list Adafruit_GFX.cpp and Adafruit_SSD1306.cpp."""
arduino_libs = self.comp._find_arduino_libraries_dir()
esp32_libs = Path(self.comp.arduino_path) / 'libraries' if self.comp.arduino_path else None
self.comp._resolve_library_components(
SSD1306_SKETCH_HEADERS,
arduino_libs, esp32_libs,
'arduino-esp32', self.user_libs,
)
cmake_path = self.user_libs / 'user_libs_all' / 'CMakeLists.txt'
if not cmake_path.exists():
self.fail('user_libs_all/CMakeLists.txt does not exist — no component was created')
cmake_text = cmake_path.read_text(encoding='utf-8')
print(f'\n[diag] user_libs_all/CMakeLists.txt:\n{cmake_text}')
self.assertIn('Adafruit_GFX.cpp', cmake_text)
self.assertIn('Adafruit_SSD1306.cpp', cmake_text)
self.assertIn('INCLUDE_DIRS "."', cmake_text)
if __name__ == '__main__':
print('=' * 70)
print('SSD1306 Real-Path Diagnostic Test')
print(f'Arduino libs : {ARDUINO_LIBS}')
print(f'ESP32 libs : {ESP32_LIBS}')
print('=' * 70)
unittest.main(verbosity=2)