diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e6813d..6d5cc17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: strategy: fail-fast: false matrix: - platform: [esp8266, esp32, esp32_3_3_11] + platform: [esp8266, esp32] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -43,6 +43,4 @@ jobs: python-version: '3.11' - run: python -m pip install --upgrade platformio==6.1.19 - run: ./scripts/test.sh compile --platform ${{ matrix.platform }} - - if: matrix.platform == 'esp32_3_3_11' - run: ./scripts/test.sh packages --platform esp32_3_3_11 - run: ./scripts/test.sh examples --platform ${{ matrix.platform }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f2cee1..afdae2c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,9 +51,6 @@ jobs: - run: pio test -e native --filter test_native_core - run: ./scripts/test.sh compile --platform esp32 - run: ./scripts/test.sh examples --platform esp32 - - run: ./scripts/test.sh compile --platform esp32_3_3_11 - - run: ./scripts/test.sh packages --platform esp32_3_3_11 - - run: ./scripts/test.sh examples --platform esp32_3_3_11 - run: ./scripts/check-docs.sh - run: ./scripts/prepare-release.sh "$GITHUB_REF_NAME" - run: ./scripts/release-notes.sh "$GITHUB_REF_NAME" > "$RUNNER_TEMP/release-notes.md" diff --git a/docs/ESP8266-LINKER-WORKAROUND.md b/docs/ESP8266-LINKER-WORKAROUND.md index 674d94d..b50de82 100644 --- a/docs/ESP8266-LINKER-WORKAROUND.md +++ b/docs/ESP8266-LINKER-WORKAROUND.md @@ -16,17 +16,17 @@ or application-source error. ArduinoHA's root `pio test -e esp8266` environment and every guided ESP8266 PlatformIO example now use this same snapshot. There is no root-test exception -that can hide a linker regression from the example contract. +that can hide a linker regression from the example test harness. Keep this exact framework snapshot in ESP8266 environments that need the -maintained test contract. It is unrelated to ESP32, whose pioarduino platform +maintained test harness. It is unrelated to ESP32, whose pioarduino platform selects its framework and compiler as a unit. Do not replace the SHA with a version range: remove or advance the pin only after an upstream release includes the fix and the affected large firmware has compiled successfully. In particular, changing the ESP32 validation lane does not justify changing this ESP8266 pin. -The corresponding ESP32 Core 3 pin and shared PlatformIO-cache recovery steps -are documented in [DeviceFramework's toolchain guide](https://github.com/alexhopeoconnor/DeviceFramework/blob/main/docs/TOOLCHAINS.md). +The corresponding ESP32 Core 3.3.11 pin is documented in +[DeviceFramework's toolchain guide](https://github.com/alexhopeoconnor/DeviceFramework/blob/main/docs/TOOLCHAINS.md). Back to the [documentation map](README.md). diff --git a/docs/compatibility.md b/docs/compatibility.md index 1758e2f..7c85068 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -19,36 +19,33 @@ lifecycle handling, and tests are fork-native changes. ## Arduino framework validation lanes ArduinoHA compiles its embedded test suites and guided PlatformIO examples in -three deliberately named lanes: +two maintained lanes: | Selector | Target | Pinned stack | Role | | --- | --- | --- | --- | -| `esp8266` | ESP8266 D1 mini | Arduino-ESP8266 commit `521ae60` | Maintained Postmortem linker-fix contract | -| `esp32` | ESP32 Dev Module | pioarduino `51.03.05` / Arduino-ESP32 3.0.5 | Legacy compatibility coverage | -| `esp32_3_3_11` | ESP32 Dev Module | pioarduino `55.03.311` / Arduino-ESP32 3.3.11 / ESP-IDF 5.5.5 | Current validation baseline | +| `esp8266` | ESP8266 D1 mini | Arduino-ESP8266 commit `521ae60` | Maintained Postmortem linker-fix test-harness lane | +| `esp32` | ESP32 Dev Module | pioarduino `55.03.311` / Arduino-ESP32 3.3.11 / ESP-IDF 5.5.5 | Maintained baseline | -The ESP32 3.0.5 lane remains intentional compatibility coverage until the -supported floor is changed in a reviewed release decision. It is not allowed to -select a framework or compiler from a global PlatformIO cache by accident; -every lane pins its complete pioarduino platform. The ESP8266 framework pin is -independent of both ESP32 lanes; see the [linker workaround](ESP8266-LINKER-WORKAROUND.md). +Every lane pins its complete framework stack. The ESP32 runner keeps its +maintained pioarduino graph in a persistent repository cache so stale global +metadata cannot select an uploader or compiler by accident. The ESP8266 +framework pin is independent of the ESP32 baseline; see the [linker +workaround](ESP8266-LINKER-WORKAROUND.md). Run the current ESP32 lane locally with: ```bash -./scripts/test.sh compile --platform esp32_3_3_11 -./scripts/test.sh packages --platform esp32_3_3_11 -./scripts/test.sh examples --platform esp32_3_3_11 +./scripts/test.sh compile --platform esp32 +./scripts/test.sh examples --platform esp32 ``` -The script keeps that current lane in a dedicated PlatformIO Core/cache +The script keeps that lane in a dedicated persistent PlatformIO Core/cache directory by default (`${XDG_CACHE_HOME:-$HOME/.cache}/arduinoha-platformio/core-3.3.11`). -This prevents a legacy 3.0.5 `tool-esptoolpy` cache from shadowing the current +This prevents stale global `tool-esptoolpy` metadata from shadowing the current pioarduino package-form uploader. Set `ARDUINOHA_PLATFORMIO_CORE_DIR`, `ARDUINOHA_PLATFORMIO_PACKAGES_DIR`, and -`ARDUINOHA_PLATFORMIO_CACHE_DIR` for a dedicated disk or a disposable -clean-room check; do not repair this condition by overriding a single compiler -or deleting an unrelated global toolchain. +`ARDUINOHA_PLATFORMIO_CACHE_DIR` for a dedicated disk. The script never clears +that cache or repairs it by overriding one compiler package. The embedded multi-suite compile path also defaults `PLATFORMIO_RUN_JOBS=1`. A caller may explicitly set another value, but the @@ -76,5 +73,4 @@ git diff --stat main...upstream/develop Port only independently reviewed changes with regression tests; treat open upstream pull requests as proposals, not release inputs. Run the native, -board-compile (including both ESP32 lanes), and HA test harness gates before -publishing a new release. +board-compile, and HA test harness gates before publishing a new release. diff --git a/examples/01-esp-sensor/platformio.ini b/examples/01-esp-sensor/platformio.ini index 5030506..8893738 100644 --- a/examples/01-esp-sensor/platformio.ini +++ b/examples/01-esp-sensor/platformio.ini @@ -16,13 +16,9 @@ platform_packages = [env:esp32] extends = common -platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip board = esp32dev build_unflags = -std=gnu++11 build_flags = -std=gnu++14 -I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src - -[env:esp32_3_3_11] -extends = env:esp32 -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip diff --git a/examples/02-switch-callback/platformio.ini b/examples/02-switch-callback/platformio.ini index 5030506..8893738 100644 --- a/examples/02-switch-callback/platformio.ini +++ b/examples/02-switch-callback/platformio.ini @@ -16,13 +16,9 @@ platform_packages = [env:esp32] extends = common -platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip board = esp32dev build_unflags = -std=gnu++11 build_flags = -std=gnu++14 -I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src - -[env:esp32_3_3_11] -extends = env:esp32 -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip diff --git a/examples/03-multi-entity-device/platformio.ini b/examples/03-multi-entity-device/platformio.ini index 5030506..8893738 100644 --- a/examples/03-multi-entity-device/platformio.ini +++ b/examples/03-multi-entity-device/platformio.ini @@ -16,13 +16,9 @@ platform_packages = [env:esp32] extends = common -platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip board = esp32dev build_unflags = -std=gnu++11 build_flags = -std=gnu++14 -I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src - -[env:esp32_3_3_11] -extends = env:esp32 -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip diff --git a/examples/04-device-discovery/platformio.ini b/examples/04-device-discovery/platformio.ini index 5030506..8893738 100644 --- a/examples/04-device-discovery/platformio.ini +++ b/examples/04-device-discovery/platformio.ini @@ -16,13 +16,9 @@ platform_packages = [env:esp32] extends = common -platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip board = esp32dev build_unflags = -std=gnu++11 build_flags = -std=gnu++14 -I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src - -[env:esp32_3_3_11] -extends = env:esp32 -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip diff --git a/examples/README.md b/examples/README.md index a83e280..dd32dda 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,9 +7,7 @@ pio run -d examples/01-esp-sensor -e esp8266 pio run -d examples/01-esp-sensor -e esp8266 -t upload ``` -The checked-in `esp32` environment preserves Arduino-ESP32 3.0.5 compatibility -coverage. Source-checkout maintenance also builds every guided project with -`esp32_3_3_11`, the Arduino-ESP32 3.3.11 validation lane. A consuming +The checked-in `esp32` environment uses Arduino-ESP32 3.3.11. A consuming application should choose and pin its complete PlatformIO platform stack. | Guided example | What it demonstrates | diff --git a/platformio.ini b/platformio.ini index f298749..7bb9303 100644 --- a/platformio.ini +++ b/platformio.ini @@ -3,8 +3,7 @@ ; Full suite: ; pio test -e esp8266 ; pio test -e esp32 -; ./scripts/test.sh compile --platform esp32_3_3_11 -; (uses the isolated current Arduino-ESP32 validation lane) +; ./scripts/test.sh compile --platform esp32 ; ; Single suite (examples): ; pio test -e esp8266 --filter test_utils_serializers @@ -40,17 +39,11 @@ platform_packages = [env:esp32] extends = common test_ignore = test_native_core -platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip board = esp32dev build_unflags = -std=gnu++11 build_flags = ${common.build_flags} -std=gnu++14 -; Current Arduino-ESP32 validation lane. Keep the legacy env above until the -; supported floor changes through an explicit compatibility decision. -[env:esp32_3_3_11] -extends = env:esp32 -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip - ; Host-only regression checks for serialization, topic validation, and the ; PubSubClient mock path. The Arduino compatibility files live exclusively ; under test/native and are never included in firmware environments. diff --git a/scripts/test.sh b/scripts/test.sh index e7cdb37..d565988 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -2,27 +2,27 @@ set -euo pipefail usage() { - echo "Usage: $0 compile|examples|packages --platform esp8266|esp32|esp32_3_3_11" >&2 + echo "Usage: $0 compile|examples --platform esp8266|esp32" >&2 exit 2 } -[[ $# -eq 3 && ( "${1:-}" == "compile" || "${1:-}" == "examples" || "${1:-}" == "packages" ) && "${2:-}" == "--platform" ]] || usage +[[ $# -eq 3 && ( "${1:-}" == "compile" || "${1:-}" == "examples" ) && "${2:-}" == "--platform" ]] || usage case "${3:-}" in - esp8266|esp32|esp32_3_3_11) environment="${3}" ;; + esp8266|esp32) environment="${3}" ;; *) usage ;; esac root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" pio_for_platform() { - if [[ "$environment" != "esp32_3_3_11" ]]; then + if [[ "$environment" != "esp32" ]]; then pio "$@" return fi # Keep Core 3.3.11's package-form esptool and generated pioarduino - # environment separate from the legacy Core 3.0.5 validation lane. - # A caller can point this at a disposable directory for cache diagnosis. + # environment in a persistent repository cache. It is never cleared by + # this script, and isolates this tested graph from stale global metadata. local core_dir packages_dir cache_dir core_dir="${ARDUINOHA_PLATFORMIO_CORE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/arduinoha-platformio/core-3.3.11}" packages_dir="${ARDUINOHA_PLATFORMIO_PACKAGES_DIR:-$core_dir/packages}" @@ -42,9 +42,6 @@ case "$1" in pio_for_platform test -d "$root" -e "$environment" --without-uploading --without-testing echo "ArduinoHA compile check passed for $environment" ;; - packages) - pio_for_platform pkg list -d "$root" -e "$environment" - ;; examples) mapfile -t examples < <(find "$root/examples" -mindepth 1 -maxdepth 1 -type d -name '[0-9][0-9]-*' -print | sort) if (( ${#examples[@]} == 0 )); then diff --git a/test-support/ha-mqtt-test-harness/src/ha_mqtt_test_harness/home_assistant.py b/test-support/ha-mqtt-test-harness/src/ha_mqtt_test_harness/home_assistant.py index 86d3655..e9594f2 100644 --- a/test-support/ha-mqtt-test-harness/src/ha_mqtt_test_harness/home_assistant.py +++ b/test-support/ha-mqtt-test-harness/src/ha_mqtt_test_harness/home_assistant.py @@ -42,34 +42,34 @@ class HomeAssistantClient: token_file = state / "ha-token" cls.wait_until_ready(base_url) if token_file.exists(): - return cls(base_url, token_file.read_text(encoding="utf-8").strip()) - - client_id = "http://ha-mqtt-test-harness.local/" - user = { - "client_id": client_id, - "name": owner_name, - "username": username, - "password": password, - "language": "en", - } - created = cls._response_json( - requests.post(f"{base_url}/api/onboarding/users", json=user, timeout=10), - "Home Assistant onboarding user creation", - ) - auth_code = created.get("auth_code") - if not auth_code: - raise TestHarnessError("Home Assistant onboarding did not return an auth_code") - token_response = cls._response_json( - requests.post( - f"{base_url}/auth/token", - data={"client_id": client_id, "grant_type": "authorization_code", "code": auth_code}, - timeout=10, - ), - "Home Assistant token exchange", - ) - token = token_response.get("access_token") - if not token: - raise TestHarnessError("Home Assistant token exchange did not return an access_token") + token = token_file.read_text(encoding="utf-8").strip() + else: + client_id = "http://ha-mqtt-test-harness.local/" + user = { + "client_id": client_id, + "name": owner_name, + "username": username, + "password": password, + "language": "en", + } + created = cls._response_json( + requests.post(f"{base_url}/api/onboarding/users", json=user, timeout=10), + "Home Assistant onboarding user creation", + ) + auth_code = created.get("auth_code") + if not auth_code: + raise TestHarnessError("Home Assistant onboarding did not return an auth_code") + token_response = cls._response_json( + requests.post( + f"{base_url}/auth/token", + data={"client_id": client_id, "grant_type": "authorization_code", "code": auth_code}, + timeout=10, + ), + "Home Assistant token exchange", + ) + token = token_response.get("access_token") + if not token: + raise TestHarnessError("Home Assistant token exchange did not return an access_token") client = cls(base_url, token) for path, payload in ( @@ -81,6 +81,51 @@ class HomeAssistantClient: raise TestHarnessError( f"Home Assistant onboarding step {path} failed: {response.status_code} {response.text}" ) + + # Complete the final server-side step before any browser test starts. + # Leaving it to first-login UI redirects makes a shared disposable + # instance timing-dependent and can leave it stuck on the onboarding + # route. Matching Home Assistant's own origin keeps the IndieAuth + # client and redirect URI locally verifiable. + integration_response = requests.post( + f"{base_url}/api/onboarding/integration", + headers=client._headers, + json={ + "client_id": f"{base_url}/", + "redirect_uri": f"{base_url}/onboarding.html?auth_callback=1", + }, + timeout=10, + ) + if integration_response.status_code not in (200, 201, 400, 403, 404): + raise TestHarnessError( + "Home Assistant onboarding integration step failed: " + f"{integration_response.status_code} {integration_response.text}" + ) + + onboarding_response = requests.get(f"{base_url}/api/onboarding", timeout=10) + if not onboarding_response.ok: + raise TestHarnessError( + "Home Assistant onboarding status check failed: " + f"{onboarding_response.status_code} {onboarding_response.text}" + ) + try: + onboarding_steps = onboarding_response.json() + except ValueError as error: + raise TestHarnessError("Home Assistant onboarding status returned invalid JSON") from error + if not isinstance(onboarding_steps, list): + raise TestHarnessError( + "Home Assistant onboarding status returned unexpected JSON: " + f"{onboarding_steps}" + ) + pending_steps = [ + str(step.get("step", "unknown")) + for step in onboarding_steps + if isinstance(step, dict) and step.get("done") is not True + ] + if pending_steps: + raise TestHarnessError( + "Home Assistant onboarding remains incomplete after bootstrap: " + ", ".join(pending_steps) + ) state.mkdir(parents=True, exist_ok=True) token_file.write_text(token, encoding="utf-8") return client diff --git a/test/test_native_core/test_main.cpp b/test/test_native_core/test_main.cpp index acb455b..ad331bc 100644 --- a/test/test_native_core/test_main.cpp +++ b/test/test_native_core/test_main.cpp @@ -81,7 +81,7 @@ void onNativeStateChanged(HAMqtt::ConnectionState) } -void test_json_helpers_escape_control_bytes_and_preserve_cursor_contract() +void test_json_helpers_escape_control_bytes_and_preserve_cursor_position() { const char value[] = "quote\" slash\\ newline\n tab\t control\x01"; char output[96] = {}; @@ -341,7 +341,7 @@ void tearDown(void) { } int main(int, char**) { UNITY_BEGIN(); - RUN_TEST(test_json_helpers_escape_control_bytes_and_preserve_cursor_contract); + RUN_TEST(test_json_helpers_escape_control_bytes_and_preserve_cursor_position); RUN_TEST(test_availability_and_serializer_array_escape_json_values); RUN_TEST(test_discovery_topic_tokens_are_rejected_before_topic_generation); RUN_TEST(test_streaming_serializer_writes_exact_escaped_payload_to_mqtt_mock);