Operating System
What feature would you like to be added?
I would like to ask how the maintainers feel about property-based tests (PBTs) for purego, and if they are welcome, in what form they should be contributed so that CI does not become flaky.
What I mean by PBT here: instead of one hand-written case with one fixed signature, a test generates many random inputs/types from a small model, runs them against an oracle, and asserts an invariant that must hold for every generated case. The oracle in my prototypes is the real C ABI itself: the test emits C declarations equivalent to the generated Go types, compiles them into a shared library, and compares purego's behavior byte-for-byte against what the C compiler did. Generation is seeded so failures reproduce deterministically, and every failure dumps the seed, the Go type, the emitted C source, and a hex diff.
I prototyped three such probes locally:
-
internal/strings CString / GoString round trip. Generates 5000 random strings over a mixed alphabet (ASCII, symbols, multibyte runes, sometimes NUL-terminated to cover both the zero-copy and copy paths in CString) plus deterministic edge cases (empty, leading/interior NULs, 64KB strings, 4096 Japanese characters). Invariant: GoString(CString(s)) equals the prefix of s up to the first NUL, i.e. the C-string contract survives both paths untouched.
-
RegisterFunc struct ABI round trip. Generates random struct types (scalars, small arrays, nested structs) and call signatures mixing structs with scalars, emits equivalent C declarations, compiles them, and round-trips values through the real C ABI in both argument and return directions, including register-exhaustion and stack layouts. Invariant: every argument byte the Go side passes must arrive intact, and every return byte must come back intact.
-
NewCallback argument/return round trip. Generates random callback signatures (scalar/array/struct parameters with scalar/struct/void returns), emits a C forwarder that calls the Go callback through the real C ABI, and checks both directions byte-for-byte. Invariant: arguments passed from Go must reach the Go callback uncorrupted, and the callback's return value must come back uncorrupted. The current prototype skips Windows (stdcall restrictions on callback argument types) and excludes float returns per compileCallback; I am not sure how far the Windows boundary should be covered, hence all OS boxes are checked above.
The expected benefit for purego, if these are accepted in some form: struct and callback paths encode per-OS/per-arch ABI rules (register classes, eightbyte classification, padding, memory-vs-register decisions) that are easy to get subtly wrong and hard to cover with hand-written cases. A seeded random round trip against the C compiler acts as a conformance net — it catches drift and corner cases a fixed table misses, and each counterexample is directly actionable because it carries its repro (seed + C source + diff).
My concern, and the reason this is a question rather than a PR: while the failures such tests find are legitimate, landing randomized tests directly into the normal unit-test suite makes CI noisy. So I would like to ask:
- Are property-based / randomized round-trip tests welcome in this repository at all?
- If a PBT is red on current
main but green after a fix, how should it be submitted — together with the fix, held until the fix lands, landed as skipped, or some other way?
- Where should such tests live: normal
*_test.go run by go test ./..., files behind a build tag, or a separate workflow (e.g. nightly / manual)?
- What determinism/reproducibility rules would you want: fixed seeds, logged seeds, C-source dump on failure, byte-level diff output?
- Is there interest in Go native fuzz (
FuzzXxx + seed corpus in CI, longer fuzzing locally/nightly) as an alternative shape for these tests?
Why is this needed?
As background for the questions above, I ran the three prototypes locally on Linux/amd64 (Go 1.26.x, main at 763bb99):
-
CString / GoString round trip: green on current main.
-
RegisterFunc struct ABI round trip: 27 counterexamples on current main. All fall into amd64 struct handling, for example: fields packed back-to-back ignoring in-memory offsets (a 64-bit field overwriting pending small fields and shifting later arguments by one slot; padding ignored, e.g. int32 at offset 4 in {int8; int32}); a struct split between registers and stack when one register class is exhausted while the SysV ABI requires the whole aggregate to go in memory; struct returns reconstructed from hand-rolled per-field checks instead of per-eightbyte classification (e.g. struct{ [1]float32 } read from RAX while the callee delivers it in xmm0, float[3] followed by uint16 panicking, nested all-float first eightbyte reading both halves from xmm0). The test is deterministic (fixed seeds) and turns green once those amd64 paths are fixed.
-
NewCallback argument/return round trip: 20 counterexamples on current main. Analysis showed the same amd64 struct-packing cluster manifesting in the C-to-Go direction: where GCC correctly passes a struct argument in two words, the purego sender side packed it into one word ignoring offsets. During development this test even produced a hard crash (SIGSEGV), which shows its detection power. It also turns green once the amd64 paths are fixed.
I am not submitting these tests or fixes here yet because of the CI-noise concern above. I can share minimal reproducers or the prototype tests if that helps discussion — please let me know what form would be most useful.
Operating System
What feature would you like to be added?
I would like to ask how the maintainers feel about property-based tests (PBTs) for purego, and if they are welcome, in what form they should be contributed so that CI does not become flaky.
What I mean by PBT here: instead of one hand-written case with one fixed signature, a test generates many random inputs/types from a small model, runs them against an oracle, and asserts an invariant that must hold for every generated case. The oracle in my prototypes is the real C ABI itself: the test emits C declarations equivalent to the generated Go types, compiles them into a shared library, and compares purego's behavior byte-for-byte against what the C compiler did. Generation is seeded so failures reproduce deterministically, and every failure dumps the seed, the Go type, the emitted C source, and a hex diff.
I prototyped three such probes locally:
internal/stringsCString/GoStringround trip. Generates 5000 random strings over a mixed alphabet (ASCII, symbols, multibyte runes, sometimes NUL-terminated to cover both the zero-copy and copy paths inCString) plus deterministic edge cases (empty, leading/interior NULs, 64KB strings, 4096 Japanese characters). Invariant:GoString(CString(s))equals the prefix ofsup to the first NUL, i.e. the C-string contract survives both paths untouched.RegisterFuncstruct ABI round trip. Generates random struct types (scalars, small arrays, nested structs) and call signatures mixing structs with scalars, emits equivalent C declarations, compiles them, and round-trips values through the real C ABI in both argument and return directions, including register-exhaustion and stack layouts. Invariant: every argument byte the Go side passes must arrive intact, and every return byte must come back intact.NewCallbackargument/return round trip. Generates random callback signatures (scalar/array/struct parameters with scalar/struct/void returns), emits a C forwarder that calls the Go callback through the real C ABI, and checks both directions byte-for-byte. Invariant: arguments passed from Go must reach the Go callback uncorrupted, and the callback's return value must come back uncorrupted. The current prototype skips Windows (stdcall restrictions on callback argument types) and excludes float returns percompileCallback; I am not sure how far the Windows boundary should be covered, hence all OS boxes are checked above.The expected benefit for purego, if these are accepted in some form: struct and callback paths encode per-OS/per-arch ABI rules (register classes, eightbyte classification, padding, memory-vs-register decisions) that are easy to get subtly wrong and hard to cover with hand-written cases. A seeded random round trip against the C compiler acts as a conformance net — it catches drift and corner cases a fixed table misses, and each counterexample is directly actionable because it carries its repro (seed + C source + diff).
My concern, and the reason this is a question rather than a PR: while the failures such tests find are legitimate, landing randomized tests directly into the normal unit-test suite makes CI noisy. So I would like to ask:
mainbut green after a fix, how should it be submitted — together with the fix, held until the fix lands, landed as skipped, or some other way?*_test.gorun bygo test ./..., files behind a build tag, or a separate workflow (e.g. nightly / manual)?FuzzXxx+ seed corpus in CI, longer fuzzing locally/nightly) as an alternative shape for these tests?Why is this needed?
As background for the questions above, I ran the three prototypes locally on Linux/amd64 (Go 1.26.x,
mainat763bb99):CString/GoStringround trip: green on currentmain.RegisterFuncstruct ABI round trip: 27 counterexamples on currentmain. All fall into amd64 struct handling, for example: fields packed back-to-back ignoring in-memory offsets (a 64-bit field overwriting pending small fields and shifting later arguments by one slot; padding ignored, e.g.int32at offset 4 in{int8; int32}); a struct split between registers and stack when one register class is exhausted while the SysV ABI requires the whole aggregate to go in memory; struct returns reconstructed from hand-rolled per-field checks instead of per-eightbyte classification (e.g.struct{ [1]float32 }read from RAX while the callee delivers it inxmm0,float[3]followed byuint16panicking, nested all-float first eightbyte reading both halves fromxmm0). The test is deterministic (fixed seeds) and turns green once those amd64 paths are fixed.NewCallbackargument/return round trip: 20 counterexamples on currentmain. Analysis showed the same amd64 struct-packing cluster manifesting in the C-to-Go direction: where GCC correctly passes a struct argument in two words, the purego sender side packed it into one word ignoring offsets. During development this test even produced a hard crash (SIGSEGV), which shows its detection power. It also turns green once the amd64 paths are fixed.I am not submitting these tests or fixes here yet because of the CI-noise concern above. I can share minimal reproducers or the prototype tests if that helps discussion — please let me know what form would be most useful.