The fixture board, a host client, and three bugs that shipped in 0.1.0 - #2
Merged
Conversation
Every test point was written with net ordinal 1 under a different name.
Through KiCad 9 a net is identified by its ordinal and the name beside it
is only a label, so KiCad read all sixteen as one net, kept the first name
it saw and dropped the other fifteen to no-net on the first save.
`kicad-cli pcb export ipcd356`, which is KiCad's own answer to what a
board's netlist is, reported the board as shipped like this:
327/DUT_TXD TP1
327N/C TP2 <- and TP3..TP14, TP90, TP91
`pinside check` called it clean throughout, because the reader takes the
name off each pad and never compares the ordinals.
Give each of the fifteen names its own number and declare them in a net
table, the way a board KiCad wrote does. The same export now reports every
net correctly. Nothing pinside reads changes: the names are identical, so
`check` output, the generated fixture and the config hash are unaffected.
The check that catches this, PS043, comes with the rest of 0.2.0.
Claude-Session: https://claude.ai/code/session_01G1pCpWna66S63N4BMjB7jo
Opening the example board in KiCad drops a .kicad_prl, a .history/ of automatic snapshots and a .kicad_pro next to it. They churn on every open whether or not anything was edited. Not *.kicad_pro in general: that one carries the design rules, the net classes and the stackup, so it belongs in a repository, and `pinside project` generates one deliberately as part of the project it writes. Ignoring it by pattern would make pinside's own output unversionable. Only the demo board's is ignored, by path, because that board is an input to pinside rather than a project: a bare .kicad_pcb with no schematic, whose .kicad_pro is one KiCad invents on first open. Claude-Session: https://claude.ai/code/session_01G1pCpWna66S63N4BMjB7jo
The tasklist's seven sections, plus what fell out of doing them. Three bugs that shipped in 0.1.0 -------------------------------- `pinside init` did not work at all. The positional `board` and the carrier option `--board` shared an argparse destination, so every invocation looked the .kicad_pcb path up in the module catalogue and refused. CI's end-to-end job ran that exact command. The option is now `--carrier`, and `main()` derives its command list from the parser rather than a hand-kept set, which is what let a newly added subcommand be swallowed too. `pinside project --board pico2w` emitted a schematic KiCad would not open. KiCad writes the Pico W as a symbol deriving from the Pico, carrying properties and no pins; copied into a schematic's lib_symbols that `extends` resolves to nothing, and every net in the design is isolated. Derived symbols are flattened against their parent now, units renamed to match -- miss that last part and KiCad says only "Failed to load schematic". A board with a slot was reported as having an unclosed outline, an error, on geometry any fab would cut. Edge.Cuts holds every edge a board has; the largest ring is the perimeter now and the rest are cutouts. New --- pinside.client and `pinside probe`, an optional extra (pinside[client]). A JSON-RPC client that refuses a fixture whose config hash disagrees with the config it was handed, and classifies responses the way JSON-RPC defines them rather than by id -- an echoing port otherwise looks like an answer, found by pointing it at pyserial's own loop://. Baselines. `--write-baseline` records the findings a board has been judged on; `--baseline` accepts them. Suppression is by code *and* reference, so a new occurrence of an accepted code still fails. `--json` on generate and project, on the refusal path too. Eight checks: cutouts and second outlines (PS003, PS004), probes and holes over a cutout (PS013, PS014), a receptacle body fouling a component (PS027), rails and reset lines the fixture cannot reach (PS033, PS034), and net numbering KiCad will not preserve (PS043, PS044). The last two were established by asking KiCad: three other malformed netlists turned out to round-trip fine and got no check. A GND pour on the generated fixture board, and a generated README that turns the plate force into hardware. The rp2354a target. Probe provenance. PyPI trusted publishing. Coverage at 87%. CI now runs the KiCad tests inside the KiCad container with PINSIDE_REQUIRE_KICAD=1, which turns a skip into a failure, and cross-compiles the generated firmware against a pinned Pico SDK. Both paths were previously verified only on a developer's machine. Tests ----- 208 to 221. tests/boards.py had the same net-numbering defect as the example board; `_wrap` numbers the nets now, since it is the only place that sees a whole board. Two tests removed footprints by reconstructing a helper's output and string-replacing it away, which stopped matching once the renumbering landed and left them asserting against a board they had not built; there is a `without(text, *refs)` helper for that now. Claude-Session: https://claude.ai/code/session_01G1pCpWna66S63N4BMjB7jo
The version-consistency test imported tomllib, so it failed to import on Python 3.10 -- which pinside supports, and which CI runs. A test whose whole purpose is to notice when something claims a version it does not have was a poor place to depend on one. The field needed is a quoted literal on its own line in [project]. Verified by running the suite on real 3.10, 3.11, 3.12 and 3.13 interpreters rather than inferring from the syntax. Claude-Session: https://claude.ai/code/session_01G1pCpWna66S63N4BMjB7jo
…ndable Inserting TestHardwareNote above TestKiCadAcceptsIt left the existing decorator attached to the new class. TestHardwareNote is arithmetic over the probe catalogue and needs nothing; TestKiCadAcceptsIt runs kicad-cli and needs everything. So on a machine without KiCad its setUpClass raised, and one unguarded class errored the whole suite -- which is every job in the test matrix, on a runner that has no KiCad. It passed locally throughout, because this machine has KiCad and the guard was never consulted. That is the shape of the problem: anyone likely to touch the KiCad emitter has KiCad installed, so the configuration CI actually runs is the one nobody exercises. So `scripts/test.sh --no-kicad` sets PINSIDE_NO_KICAD=1, which forces KICAD_CLI and HAVE_SYMBOLS to their absent values. It is the mirror of PINSIDE_REQUIRE_KICAD, which the container job uses to turn a skip into a failure. Between them both configurations are reachable from either machine. Also extends the documented-codes test to the PK family. It scanned PS and PF only, so PK001 through PK003 -- project generation's own errors, the ones that stop anything being written -- had gone a release undocumented and unnoticed by the test that exists to notice exactly that. Verified on real 3.10, 3.11, 3.12 and 3.13 interpreters, in both the with-KiCad and without-KiCad configurations. Claude-Session: https://claude.ai/code/session_01G1pCpWna66S63N4BMjB7jo
… root Both new CI jobs failed on their first run, which is what they were for. The firmware never linked against a real Pico SDK. main.c calls set_sys_clock_khz, a static inline in hardware/clocks.h, and included only pico/stdlib.h, which does not pull that in. Under C11 an undeclared function is a warning, so all 102 translation units compiled and the link failed with nothing but a symbol name. The host tests could not have caught it: they build fixture_core.c against the mock HAL and never compile main.c. So: include the header, link hardware_clocks, and build the generated project with -Werror=implicit-function-declaration, which turns the next missing header into a compile error naming the function instead of a bare undefined reference. Verified by cross-compiling the demo fixture against a real pico-sdk 2.1.1 with arm-none-eabi-gcc: it links, produces a 107 KB .uf2, and removing the include again fails at compile with the function named. The KiCad container job died in actions/checkout with EACCES. The image declares USER kicad while the runner mounts its own /__w owned by root, so checkout cannot write /__w/_temp/_runner_file_commands. Running the container with --user root fixes it. The safe.directory step stays: git still refuses a tree it considers someone else's. This one is not verified locally -- there is no docker on this machine -- so it rests on the error message and the standard fix for it. Claude-Session: https://claude.ai/code/session_01G1pCpWna66S63N4BMjB7jo
The entry was meant to land with the fix itself; the edit failed its anchor match and the commit went out without it. Claude-Session: https://claude.ai/code/session_01G1pCpWna66S63N4BMjB7jo
…e missing --user root got the container as far as running the suite -- 221 tests with KiCad present, 3 skipped rather than 36 -- and then two ERC assertions failed on 44 violations, every one of them "the current configuration does not include the symbol library 'MCU_Module'". A KiCad install is two things: libraries on disk, and a profile mapping library nicknames to them. The profile is created on the first GUI launch from KiCad's own templates, and kicad-cli never creates one. So in a fresh container nothing resolves `MCU_Module:` or `power:`, and ERC reports one warning per reference: a statement about the environment, not about the schematic pinside generated. Seeding the two tables is what a first launch does. There is no docker on this machine, so rather than guess, both halves were reproduced on a workstation by pointing KICAD_CONFIG_HOME at an empty directory: 44 violations without the tables, 0 with them, matching CI's number exactly. The assertion now names this case too. Forty warnings that all mean "no profile" should not cost the next person an afternoon to interpret, and a seeding step that silently lands in the wrong directory would otherwise fail exactly as opaquely as it did this time. Claude-Session: https://claude.ai/code/session_01G1pCpWna66S63N4BMjB7jo
`find /usr/share/kicad -name sym-lib-table | head -1` returned template/Edgeberry_Cartridge/sym-lib-table -- a project template KiCad ships, which has a sym-lib-table and no fp-lib-table beside it -- so the step died on the copy. Traversal order decided which one it found, and locally it happened to pick the right one, which is why the command looked fine. The defaults live directly in the template directory. Requiring both files in one directory, at a known path, removes the ambiguity. And a check on the result: a wrong table copies exactly as successfully as a right one, so the step now confirms the seeded file names MCU_Module, power and Device, the libraries the generated schematic actually references. Without that, a mis-seed shows up forty warnings later in a test, which is where this started. Verified locally by running the step's own shell against the macOS layout, which carries the same nested Edgeberry_Cartridge template: it selects the top-level directory, passes its checks, and the ERC that follows reports 0 violations. Pointed at the nested template alone it exits non-zero saying so. The step was also extracted from the workflow and parsed with sh -n. Claude-Session: https://claude.ai/code/session_01G1pCpWna66S63N4BMjB7jo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Works through the tasklist, and fixes three bugs found along the way that
were all present in 0.1.0.
Large, so the three commits are meant to be read in order. The first is a
data fix to the example board, the second is
.gitignore, and the third iseverything else.
Three bugs that shipped in 0.1.0
pinside initdid not work at all. The positionalboardand thecarrier option
--boardshared an argparse destination, so every invocationlooked the
.kicad_pcbpath up in the module catalogue and refused. CI'send-to-end job runs that exact command. The option is now
--carrier, andmain()derives its command list from the parser rather than a hand-keptset, which is what let a newly added subcommand be swallowed too.
pinside project --board pico2wemitted a schematic KiCad would notopen. KiCad writes the Pico W as a symbol deriving from the Pico, carrying
properties and no pins; copied into a schematic's
lib_symbolsthatextendsresolves to nothing and every net in the design is isolated.Derived symbols are flattened against their parent now. The units have to be
renamed too, and missing that gives you
Failed to load schematicand nofurther explanation, which took a file-level bisect to find.
The example board's netlist could not survive being opened. All sixteen
test points were written with net ordinal
1under sixteen different names.Through KiCad 9 a net is identified by its ordinal and the name is a
label, so KiCad read them as one net and dropped fifteen on the first save.
pinside checkcalled it clean, because the reader takes the name off eachpad and never compares ordinals. That is now
PS043.A board with a slot was reported as having an unclosed outline
(
PS002, an error) on geometry any fab would cut. Edge.Cuts holds everyedge a board has; the largest ring is the perimeter now and the rest are
cutouts.
New
pinside.clientandpinside probe, an optional extra(
pip install 'pinside[client]'). Refuses a fixture whose config hashdisagrees with the config it was handed, and classifies responses the way
JSON-RPC 2.0 defines them rather than by id: an echoing port otherwise
looks like an answer. Found by pointing the client at pyserial's own
loop://.--write-baselinerecords the findings a board has beenjudged on;
--baselineaccepts them. Suppression is by code and byreference, so a new occurrence of an accepted code still fails, which is
what makes the file safe to commit.
--jsonongenerateandproject, including the refusal path.PS003,PS004(cutouts, second outlines),PS013,PS014(probes and holes over a cutout),PS027(receptacle body foulinga component),
PS033,PS034(rails and reset lines the fixture cannotreach),
PS043,PS044(net numbering KiCad will not preserve).turns the plate force into hardware: clamp or thumbscrew, load per
standoff, and how long the standoffs must be to leave the probes travel.
rp2354a, probe provenance, PyPI trusted publishing, coverage at 87%,SECURITY.md, issue and PR templates.How the net checks were decided
PS043andPS044claim KiCad loses nets, so I asked KiCad rather thanassuming.
kicad-cli pcb export ipcd356is its own answer to what a board'snetlist is. Five malformed netlists went through it:
N/C0N/C; net 0 is the no-connection netN/CThree of the five turned out not to be defects and got no check.
tests/test_kicad.pyruns the same boards throughipcd356and compares,including the merge case pinside deliberately does not flag, so that
decision is confirmed rather than assumed.
Testing
208 → 221 tests, and the two CI-invisible paths are now covered: the KiCad
tests run inside the
kicad/kicad:10.0container withPINSIDE_REQUIRE_KICAD=1, which turns a skip into a failure so the jobcannot go green having run nothing, and the generated firmware is
cross-compiled against a pinned Pico SDK.