kodeOS internals

Test pipeline

The checks each repository carries as scripts, run on a computer before a change is merged: what each one runs, which risk it covers, and the rule that merges a change.

What it is

The checks of kodeOS, the SDK and the C5's firmware are scripts in each repository. Nothing runs them for you: whoever opens a pull request runs them on a computer before asking for the merge, and the reviewer runs the ones the change touches again. A green result is a terminal that ends in ok or all passing, not a mark on GitHub.

This page lists the scripts per repository, which risk each one covers and the rule that merges a change. The release, built by hand from a clean clone of its tag, is on internals/release-pipeline.

How it is connected

flowchart LR
change["a change on a branch"] --> fast["the fast checks<br/>no ESP-IDF"]
fast --> host["the host suites<br/>ESP-IDF v6.1"]
host --> fw["the firmware build<br/>and its four checks"]
fw --> pr["pull request"]
pr --> review["one approval"]
review --> main["main"]
class change ext
class main store
What a change goes through before it is merged

The checks run from the order of their cost: the fast ones first, so a broken rule is found in seconds and nobody waits for a firmware build to hear about it.

kodeOS runs its checks on the SDK as pinned by the sdk submodule. A suite that passed in the SDK says nothing about the commit kodeOS builds from, so the submodule must be where the change leaves it: git submodule update --init --recursive before you trust a result.

Specifications

kodeOS

From the root of the repository, with ESP-IDF v6.1 loaded (. ~/.espressif/v6.1/esp-idf/export.sh).

StepCommandNeeds
The kit's tokenspython3 sdk/components/kui/tools/gen_tokens.py --check, when tokens.json changedPython only
The tool teststools/tool_tests.sh: the bench tools, the release scripts, the factory tools and the SDK's gate tests, against fakesPython and bash
The C5's host testssh sdk/c5/test/run.shA C compiler
The wireKODE_REQUIRE_C5=1 sdk/tools/check_wire_twins.sh: the P4's and the C5's copies of each wire header are the sameThe C5 submodule
The layerstools/check_layers.shNothing
The to-do markerstools/check_todo_refs.sh BASE, where BASE is the commit the branch started fromgit
The host suitestools/host_tests.sh: the six SDK suitesESP-IDF v6.1, Ruby
The buildidf.py buildESP-IDF v6.1
The sizetools/check_os_size.sh build/kodeos.binThe build
The RAMsdk/tools/check_ram.sh build/kodeos.elf tools/ram_budget.txtThe build
The startssdk/tools/check_starts.sh build --allThe build
MISRAsdk/tools/check_misra.sh build/compile_commands.json tools/misra_baseline.txt sdk/components components/kguiThe build, and cppcheck at the version on the baseline's first line
A clean treegit status: the build changed no file that is in the repositoryThe build

Details of the build's checks:

CheckBehaviour
SizeAgainst the smaller of two ceilings, both read from the tree: the 4 MB slot ESP-IDF checks, and the mailbox's image space, KAPPS_PENDING_INTENT_OFF in kapps_fmt.h. It prints name n of ceiling bytes, left (pct%) and warns once under 10 % is left
RAMThe image's static use of internal RAM against static_max (417000 bytes) in tools/ram_budget.txt, and every data symbol of 2048 bytes or more in internal RAM must have its own line there
StartsEvery Active Object compiled is in the image, each one's start sits in the object that holds its queue, and the OS role names every start strongly, so kodeOS parks none of its own owners
MISRAcppcheck and its MISRA addon over the first-party C in sdk/components/ and components/kgui/. The baseline lists, per file and rule id, a count the file must not exceed. sdk/tools/build_cppcheck.sh builds the version the baseline names; --update rewrites the baseline after a change that lowers a count
Clean treeA file the build rewrote and nobody committed is a generated file that belongs in the change

tools/check_todo_refs.sh reads only the lines a change adds. A TODO, FIXME, HACK or XXX there must name its issue right after it, as in TODO(#42) or FIXME kodediy/kode-sdk#7.

tools/check_starts.sh reads the archives a build made. It fails when an Active Object's start is not in the object that holds its queue: in an app, kode_init() names each optional start weakly, so a door linked without its start would post to an AO with no queue and loop the boot. --all also asks that every AO is in the image; --absent asks that the named ones are not.

The SDK

From the root of the SDK repository. The first four steps need no ESP-IDF.

StepCommand
The kit's tokenspython3 components/kui/tools/gen_tokens.py --check, when tokens.json changed
The gates' own testsbash tools/test_check_ram.sh, test_check_misra.sh, test_check_wire_twins.sh, test_check_starts.sh and test_check_app_size.sh, each in tools/
The examples' pintools/check_example_pins.sh
The layerstools/check_layers.sh
The host suitestools/host_tests.sh, with ESP-IDF v6.1 loaded. Delete tests/host*/build first when a header changed: stale mocks fake compile errors
The target buildtests/target in each of five variants, below, when components/ changed

A variant is the file tests/target/sdkconfig.<variant> on top of the defaults, named by KODE_TARGET_VARIANT. A build directory holds one variant and refuses another, so rm -rf build between two. In tests/target, with ESP-IDF v6.1 loaded:

VariantBuildRAM budgetStart check
The full build, in the OS role as kodeOS isidf.py buildram_budget.txt--all
nowifi: radio on, Wi-Fi off, no mirror. The only build with the display linked, the console on and no streamKODE_TARGET_VARIANT=nowifi idf.py buildram_budget.txt--all
minimal: the LED and the buttons on an app's own defaults (main/minimal.c)KODE_TARGET_VARIANT=minimal idf.py buildram_budget.minimal.txt--absent the twelve owners the app never uses
minimal_noradio: the same with KODEOS_RADIO=n and KODEOS_CONSOLE=n, the build that compiles kdrv_c5_hold.cKODE_TARGET_VARIANT=minimal_noradio idf.py buildram_budget.minimal_noradio.txt--absent the same twelve, AO_Console and AO_Autotest
testdot: the full build with AO_TestDot, the only one that compiles the final test, the OV5640 and the station's chipsKODE_TARGET_VARIANT=testdot idf.py buildram_budget.testdot.txt read before ram_budget.txt--all

After each build, ../../tools/check_ram.sh build/kode_target.elf <budget> and ../../tools/check_starts.sh build <rule>. The twelve owners a minimal app never uses are AO_Radio, AO_Net, AO_Display, AO_Storage, AO_AppMgr, AO_Ir, AO_Nfc, AO_Rfid, AO_Audio, AO_Imu, AO_Haptic and AO_Touch.

tests/target links every SDK component with every option on, except the camera, so its static use of internal RAM is what the SDK costs an app before the app adds a line. check_example_pins.sh checks that every example's main/idf_component.yml and the SDK's README name the same SDK version, and that it is not older than the newest v* tag. An example builds against the tag it pins, not against the tree it sits in. In a shallow clone with no tags it checks only the first rule and says so.

The C5's firmware

From the root of kode_slave_c5 (in kodeOS, sdk/c5/).

StepCommandESP-IDF
The host testsKODE_C5_CFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer -g" sh test/run.sh: the pure parts, pointer and length arithmetic over wire frames and recordsNone
The image kodeOS shipsidf.py -B build_kode set-target esp32c5 build, then tools/check_image.sh build_kodev6.0.2
The production line's imageidf.py -B build_closed -DSDKCONFIG=build_closed/sdkconfig -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.esp32c5;sdkconfig.defaults.closed" set-target esp32c5 build, then tools/check_image.sh build_closed closed: what it must hold, and that its bootloader fits once signedv6.1

The closed image needs ESP-IDF v6.1: the bootloader of 6.0.2 revokes the free key digest slots on its first boot, whatever the configuration says.

What is tested where

What can go wrongWhere it is testedHow
The bootloader jumps into a bad app or keeps a sketch armed after the wrong resettest_kapps_boot.c, test_kapps_req.cThe decisions are pure functions in kapps_fmt.h; the bootloader asserts at compile time that its reset numbers are ESP-IDF's
The apps region believes a broken directory copy, or loses a write to a power cuttest_kapps_dir.c, test_kdrv_apps_region.cEmulated flash with the board's table, failing at every step of the write
An install, an eviction or an erase leaves the region wrongtest_appmgr_ao.c, test_kdrv_apps_plan.cAO_AppMgr over the emulated flash
An update is never confirmedtest_boot_ao.c, the host_slot suiteAO_Boot's first transition; kdrv_slot over esp_ota_ops
The console misreads a linetest_console_grammar.c, test_console_parse.c, test_log_ao.cThe grammar functions, then whole lines through AO_Log and AO_Console
A stored setting answers wrongtest_console_config.c and each owner's fileconfig_support.h plays the console against each owner
The Kode channel accepts a wrong answertest_net_channel.c, test_net_ao.cThe parser alone, then AO_Net through a session
A bench tool sends a file wrongtools/tests/test_kcli.py and the other tool testsA fake serial port plays the board
The P4 and the C5 drift apart on the wiresdk/tools/check_wire_twins.shCompares kode_wire.h and kir_wire.h with their twins in the C5 firmware, ignoring the SPDX line. In kodeOS it runs against the real headers (KODE_REQUIRE_C5=1); in the SDK alone the C5's copies are not checked out, so the SDK runs only the script's own test
A door links without its owner's startsdk/tools/check_starts.shOver the archives of kodeOS's build and of each tests/target variant
The hardware, or the boot of a real boardtest component=... on a Kode Dot, and the checks of a release on a flashed unitinternals/release-pipeline, internals/production-test

What no script covers

GapWhy
The bootloader on a boardHost tests cover its decisions, not the flash reads, the image check or the jump
The logic of kguiEntangled with LVGL objects. components/kgui/sim renders the screens only
What the screen showsChecked on a board with a screenshot and the mirror: internals/bench-tools
The rows a person judgesbuttons, led and display: internals/production-test
The C5's closed configuration on a chiptools/check_image.sh checks the unsigned build; the signing, the flash encryption and the first boot happen on the production line
Radios on the air, power-off on batteryOnly a person with a Kode Dot on the bench

How a change reaches main

RulekodeOSSDKC5 firmware
Ruleset on main"Protect main""Protect main"None
main cannot be deleted or force-pushedYesYesNo
A change arrives by pull request onlyYesYesNo
ApprovalsOne, from the main-approvers team; a new push dismisses itThe sameNone
BypassThe main-approvers team, at merge time onlyThe same
Tags"Release tags: admins only": only an admin creates, moves or deletes one

The approval is where the checks are vouched for: the pull request says which of the steps above ran and how they ended, and the reviewer asks for any the change touches that it does not name. The rulesets of kodeOS and the SDK also list required status checks (static, host, firmware in kodeOS; host-tests, esp32p4, esp32p4 (nowifi) in the SDK). No workflow reports them, so every merge goes through the bypass of the main-approvers team.

The workflow files under .github/workflows/ stay in the three repositories, and GitHub Actions does not run them. They hold the same commands as the tables above, so they are also a written record of each check.

Gotchas

Example code

Run kodeOS's checks before you open a pull request, from the root of the repository.

  1. Run tools/check_layers.sh. Every line reads ok and the exit status is 0.
  2. Run tools/tool_tests.sh. It ends with tool tests, all passing.
  3. Run KODE_REQUIRE_C5=1 sdk/tools/check_wire_twins.sh and sh sdk/c5/test/run.sh.
  4. Load ESP-IDF v6.1 and run tools/host_tests.sh. It ends with cases across 6 suites, all passing.
  5. Build with idf.py build, then run tools/check_os_size.sh build/kodeos.bin. It prints the image size against its ceiling.
  6. Run sdk/tools/check_ram.sh build/kodeos.elf tools/ram_budget.txt and sdk/tools/check_starts.sh build --all.
  7. Run sdk/tools/check_misra.sh build/compile_commands.json tools/misra_baseline.txt sdk/components components/kgui with the baseline's cppcheck version on your path.
  8. If you added a TODO, run tools/check_todo_refs.sh BASE, where BASE is the commit your branch started from.
  9. Run git status. The build left no change in a tracked file.

In the SDK