feat: Update components metadata timestamp and mark subproject commits as dirty; add diagnostic test for real library paths
This commit is contained in:
parent
2ba8020438
commit
382e13cfe7
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"generatedAt": "2026-04-11T00:28:26.563Z",
|
||||
"generatedAt": "2026-04-11T18:35:07.056Z",
|
||||
"components": [
|
||||
{
|
||||
"id": "arduino-mega",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
"""
|
||||
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 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
|
||||
|
||||
|
||||
class TestRealPathsSSD1306(unittest.TestCase):
|
||||
"""
|
||||
Uses actual library directories on this machine to reproduce the build failure.
|
||||
"""
|
||||
|
||||
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)
|
||||
Loading…
Reference in New Issue