Commit Graph

13 Commits

Author SHA1 Message Date
davidmonterocrespo24 9d26fa6de3 test(dht22): skip the microsecond-timing analysis under CI=true
test_response_timing_analysis measures the actual µs duration of the
DHT22 preamble LOW pulse and asserts it stays under 1000µs (real
hardware target ~80µs, busy-wait tolerance ~500µs). On a deploy box
under load (concurrent docker build + zstd compression + container
runtime) GIL contention inflates the observed timing far past the
threshold — the deploy gate just hit 3242µs and aborted.

Mirror the same @skipIf(CI=='true') gate the sibling
test_response_data_matches_payload already has (line 390-393).
Locally / when debugging the DHT22 path the test still runs in full.
2026-05-17 03:56:43 +02:00
davidmonterocrespo24 04ac1bf53b chore(tests): silence three noisy warnings in deploy-gate output
deploy.sh's vitest + pytest output was polluted with three benign
but loud warnings that buried real signal:

1. AVRSimulator.start() unconditionally read `window.__spiceDebug`.
   In node-side vitest runs `window` is undefined → ReferenceError
   → console.warn('[spice] debug dump failed', e). Logged once per
   AVR test. Guarded with `typeof window !== 'undefined'`; in
   production the browser path is unchanged.

2. pinPositionCalculator.calculatePinPosition() warned every time
   document.getElementById returned null. In node-side tests there
   is no real DOM and every wire-related test triggers the warning
   for every component. Skip the console.warn when
   import.meta.env.MODE === 'test' (vitest sets MODE=test); the
   function still returns null and production retains the
   actionable warning for unmounted components.

3. test_esp32_wifi_args.py::test_start_instance_accepts_wifi_params
   mocked asyncio.create_task with no side_effect, so the coroutine
   from self._boot(...) leaked and triggered a "coroutine never
   awaited" RuntimeWarning. Mock now closes the coroutine.

After fixes:
  frontend tests:  0 spice/pinPositionCalculator stderr lines
  backend tests:   259 passed, 15 skipped, 1 warning (starlette
                   third-party python_multipart deprecation —
                   not ours, fixed when starlette updates).
2026-05-16 22:39:43 +02:00
davidmonterocrespo24 b9d39c0bd7 fix(pi3): show kernel boot + autologin SD + sidecar cache invalidation
User report: clicked Pi 3 board → nothing visible happens. Three
defects, all on the same path:

1. The kernel cmdline carried over from the original pre-OSS-split
   code: `quiet init=/bin/sh`. Result: kernel boot messages
   suppressed, then dropped straight to bare /bin/sh with no PS1 so
   the user sees an empty serial. Removed both. The kernel cmdline
   is now just `console=ttyAMA0 root=/dev/mmcblk0p2 rootwait rw
   dwc_otg.lpm_enable=0`, which lets systemd start a real
   serial-getty@ttyAMA0.service.

2. Pi OS Trixie armhf since Bookworm ships without a default user
   (no more pi/raspberry). With cmdline #1 fixed, the user would
   land at a login prompt and be stuck. Fix: pre-bake a systemd
   drop-in at /etc/systemd/system/serial-getty@ttyAMA0.service.d/
   autologin.conf that uses `agetty --autologin root` so the serial
   console drops to a root shell on first prompt. The browser
   canvas IS the authentication boundary; the SD image is mounted
   RO via a qcow2 overlay so per-session edits don't persist.
   Edit happens in velxio-prod/scripts/configure-pi3-autologin.sh
   (to follow in a separate commit).

3. Architectural: the original cache-hit probe was size-only.
   Today's SD image rebake produced a file with identical byte count
   but different SHA256 — the cache served stale content for every
   request even after a manifest bump. Fix: write a sidecar
   `<file>.sha256` after every successful materialise and trust it
   on subsequent probes. Manifest SHA bumps invalidate the cache
   regardless of size. Two regression tests guard this:
     - test_provider_sidecar_invalidates_on_sha_mismatch
     - test_provider_missing_sidecar_treats_file_as_invalid

Manifest bumped to version "2026-04-21+autologin" for the SD image
(kernel + DTB unchanged, still 2026-04-21).
2026-05-16 06:23:22 +02:00
davidmonterocrespo24 93fd4617af feat(sim): boot_images module + Pi 3 emulation restored
Pi 3 simulation had been broken since at least April 2026 (51
fail-events / 24h per docs/PI3_EMULATION_BROKEN.md). Two distinct
defects compounded:

1. qemu_manager.py hard-coded paths for kernel8.img, a device-tree
   blob (under a DOS 8.3 short name!), and a 5.4 GiB Raspberry Pi OS
   SD image — none of which shipped in the repo or were pulled at
   image build.

2. qemu-system-arm + qemu-utils were missing from the Docker image
   entirely, so even with the boot files in place QEMU couldn't
   launch. Add both to Dockerfile.standalone (~200 MB).

The architecture fix is a new `app.services.boot_images` module:

  * Manifest-driven (boot_images/manifest.json, versioned in repo,
    declares SHA256 + size for each file, supports an optional
    `compressed.{encoding,sha256,size_bytes}` block for assets shipped
    as .zst).

  * `BootImageProvider` materialises files lazily, atomically (temp +
    rename), verifies SHA256 pre- AND post-decompression, caches under
    /var/cache/velxio/boot-images, serialises concurrent get() calls
    per image set via asyncio.Lock.

  * `AssetDownloader` Protocol with two impls:
    - `LicenseGatedDownloader` — same flow ESP32 / RISC-V QEMU libs
      use (VELXIO_BINARY_BASE_URL + VELXIO_LICENSE_KEY).
    - `LocalDirectoryDownloader` — for tests + in-prod use where the
      licence-module storage is already on the same filesystem (saves
      the loopback HTTP roundtrip on a 1.4 GiB blob).

  * `build_downloader_from_env()` picks one — local-dir wins if both
    sets of env vars are present, so the prod box short-circuits to
    direct disk reads automatically.

  * Lifespan hook in qemu_manager.py pre-warms the cache on container
    boot so first-time user requests don't pay the 30-60 s download
    + decompress latency.

Adding a future board kind (Pi 4 / Pi 5) is now: upload assets via
upload-binary.sh, append an entry to manifest.json, register a
lifespan pre-warm in the new board's service module. Zero edits to
provider.py / downloader.py.

Manifest entries for raspberry-pi-3:
  kernel8.img             9 695 883 bytes  (uncompressed)
  bcm2710-rpi-3-b.dtb        34 687 bytes  (uncompressed)
  raspios-trixie-armhf.img  5 729 419 264 bytes raw
                          / 1 488 002 803 bytes .zst on wire (zstd -19)
  source: 2026-04-21 build from raspberrypi.com

Tests: 21 new unit tests covering manifest parsing, integrity
helpers, both downloaders, and the provider's idempotent /
concurrent / integrity / decompression / warmup paths. In-process
FakeDownloader keeps the suite under 1 s and httpx-free.

Docs: new docs/BOOT_IMAGES.md describes the architecture, on-disk
layout, named-volume operation, and the procedure for adding a new
image set.
2026-05-16 05:41:46 +02:00
davidmonterocrespo24 c2fe1af250 perf(compile): dedup, concurrency limits, and persistent build dir for ESP-IDF
Three coordinated fixes that together close the "ESP-IDF compile takes
5-7 min every time" gap and prevent the failure mode where a user clicking
compile multiple times spawns six ninja processes that peel each other
apart on a modest VPS.

What was wrong
- /compile/start generated a fresh uuid4 every call, so 6 clicks = 6
  independent builds racing each other. Saw load average 30 on the prod
  VPS during a real BMP280 attempt today.
- No concurrency limit anywhere; asyncio.create_task() fired without
  gating.
- ccache was wired in last week (PR #149) but reported 18,350 cacheable
  calls and **0 hits** because the build dir was a fresh
  tempfile.TemporaryDirectory(prefix='espidf_') per compile. The random
  /tmp/espidf_<random>/ path baked into -I and -fmacro-prefix-map flags
  → different command line every compile → ccache hash miss every time.

What this PR does

1. Job deduplication (`backend/app/api/routes/compile.py`)
   - New `_job_key(files, board_fqbn)` returns SHA-256 of normalised file
     names + contents + board. Order-independent.
   - New `JOB_BY_KEY: dict[str, str]` indexes hash → job_id.
   - `compile_start` checks JOB_BY_KEY before spawning a new task; if a
     job for this exact content is already pending or running, returns
     the existing job_id (logs `[compile] dedup hit — reusing job <id>`).
   - `_purge_expired_jobs` evicts both COMPILE_JOBS and JOB_BY_KEY,
     keeping the index consistent. Edge case where two jobs share a key
     (old finished, new running) is handled — only evict the key entry
     if it still points at the purged job.

2. Concurrency control (`backend/app/api/routes/compile.py`)
   - `_COMPILE_SEMAPHORE = asyncio.Semaphore(2)` global cap on
     simultaneous compiles.
   - `_target_lock(board_fqbn)` returns a per-target asyncio.Lock so
     concurrent compiles to the SAME board (sharing the persistent build
     dir) serialise. Different boards still run in parallel up to the
     semaphore cap.
   - `_compile_job` acquires sema → per-target lock → flips state to
     `running` → calls `_run_compile`. Pending state now accurately
     reflects "queued waiting for resources".

3. Persistent build dir (`backend/app/services/espidf_compiler.py`)
   - New `_prepare_persistent_project_dir(idf_target)` materialises
     `/var/lib/velxio-build/<target>/project/` from the template on
     first use; on subsequent compiles it wipes only `main/` and
     `user_libs/` (the per-compile parts) and leaves `build/` alone so
     ninja's incremental cache + ccache .o files survive.
   - Toolchain version sentinel (`.idf_version`) wipes the whole target
     dir if the ESP-IDF or arduino-esp32 version changes — cached
     objects from the old toolchain are no longer ABI-compatible.
   - `compile()` is now a thin dispatcher: persistent path or fallback
     to the legacy `tempfile.TemporaryDirectory()` flow. The actual
     build logic was extracted into `_compile_in_dir()` so both paths
     share one implementation, no duplication.
   - Escape hatch: `VELXIO_PERSISTENT_BUILD_DIR=0` env var falls back
     to the tempfile path without rebuilding the image. Critical for
     production safety.

4. ccache normalisation (`Dockerfile.standalone`)
   - + `ENV CCACHE_BASEDIR=/var/lib/velxio-build` makes ccache canonicalise
     absolute paths under that prefix when computing the cache key.
     Robustens hits against any future subdir rearrangement.

5. Docker compose (`docker-compose.yml`)
   - + named volume `velxio-build:/var/lib/velxio-build` so the persistent
     build dir survives `docker compose up -d --build`.
   - + env `VELXIO_PERSISTENT_BUILD_DIR=1` (default ON; users disable
     without rebuilding).

Expected impact
- Cold first compile per container per target: unchanged (~5-7 min).
- Same sketch re-compiled: ~2-5 s (everything cached).
- Different sketch, same target: ~5-30 s (only user code + new lib steps
  rebuild; ESP-IDF base hits cache).
- Different sketch with new libraries: ~30-90 s (new lib component
  compiles; rest hits cache).
- Concurrent clicks on same example: 1 build, others poll the same
  job_id. No more six-ninja meltdown.

Tests
- `test/backend/unit/test_compile_dedup.py` covers `_job_key` stability +
  variance and `_purge_expired_jobs` consistency (including the
  "two jobs share a key" edge case).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 08:33:11 +02:00
David Montero Crespo 26c7d50310 fix(ci): two stale-path bugs from the recent refactors
1. Backend test (test_arduino_cli_attinycore.py): the entrypoint script
   was renamed deploy/ → docker/ in commit b736aea but this test still
   pointed at the old path. Update the read_text() call + docstring.

2. Frontend CI (frontend-tests.yml): the cache key
   `frontend-${{ hashFiles('frontend/package-lock.json') }}` was tied to
   a file that has since been gitignored (commit eb9a3ec). hashFiles()
   on a missing file returns the same empty hash forever, so every CI
   run was restoring the same stale node_modules — including the
   symlinks to `file:../third-party/wokwi-elements` that existed before
   the npm migration in commit 531c337. On revalidation, npm tried to
   run wokwi-elements' `prepare` script (`husky install && npm run
   build`), which failed with "husky: not found".

   Drop the cache step entirely; lock files aren't committed so cache
   keys can't be made meaningful without overcomplication. Adds ~30s
   per CI run, but actually correct. Also pass --no-audit --no-fund
   to npm install for cleaner logs.
2026-05-05 11:22:02 -03:00
David Montero Crespo 641ac8c1de Add comprehensive tests for Cyw43Emulator functionality and lifecycle
- Implemented handshake tests to validate initial bus state and register responses.
- Created end-to-end tests for Pico W LED blinking using MicroPython firmware.
- Added SDPCM framing tests to ensure proper encoding and decoding of control frames.
- Developed IOCTL tests to verify command responses and state changes in the emulator.
- Established a full lifecycle test for WiFi operations, including scanning, connecting, and packet handling.
- Introduced TypeScript configuration for test files to ensure compatibility and strict type checking.
2026-04-29 00:21:26 -03:00
David Montero Crespo 07c9718bc3 test: update BMP280 chip ID assertions and clarify tagName uniqueness in metadata drift test 2026-04-21 17:31:51 -03:00
David Montero Crespo d4a48e6e1e feat: Skip real paths test in CI; mark subproject commits as dirty 2026-04-11 15:49:32 -03:00
David Montero Crespo 382e13cfe7 feat: Update components metadata timestamp and mark subproject commits as dirty; add diagnostic test for real library paths 2026-04-11 15:42:38 -03:00
David Montero Crespo 2ba8020438 feat: Enhance ESPIDFCompiler library resolution logic; add support for dynamic library detection and patching in CMakeLists.txt
refactor: Update wiring examples for E32 OLED integration; correct pin mappings for VCC, GND, DATA, and CLK
test: Improve unit tests for ESPIDFCompiler; add scenarios for library resolution and CMake patching
chore: Mark subproject commits as dirty for wokwi-libs
2026-04-11 15:25:40 -03:00
David Montero Crespo 6b161eab34 feat: Enhance CI workflows for backend unit and e2e tests; add environment setup and skip timing-sensitive tests in CI 2026-04-10 23:47:25 -03:00
David Montero Crespo 7971743174 Add unit tests for I2C slave devices, MCP tools, and WiFi/BLE status parser
- Implement tests for BMP280, DS1307, DS3231, I2CWriteSink, and MPU6050 slaves in test_i2c_slaves.py.
- Create test suite for Velxio MCP server tools in test_mcp_tools.py, covering Wokwi utilities and circuit management functions.
- Add tests for parsing WiFi and BLE serial output in test_wifi_status_parser.py, ensuring correct status events are captured.
2026-04-10 23:39:51 -03:00