fix(ci): the heap corruption's two upgradable causes, and a floor set on the wrong platform

The windows-latest crash is the GC finding a bad pointer while marking:
runtime.scanObject in mgcmark_greenteagc.go, under gcDrain. Green Tea is
the detector, not the cause — turning it off would move where the
corruption surfaces, not stop it, so this does not do that.

Two things in that binary can produce it and were both behind. The Go
toolchain moves 1.26.6 -> 1.26.8, which carries compiler and runtime
fixes; and modernc.org/sqlite moves 1.57.0 -> 1.58.0, taking libc from
1.74.4 to 1.75.6. libc is the only unsafe-heavy code linked into that
test binary that is not the standard library, and internal/bot reaches
it through historycatalog and projectiondb. Neither upgrade is proven to
be the fix: one crash cannot attribute itself, and golang/go#81238 —
windows/amd64 exception recovery writing below a goroutine stack — is
open, unfixed, and the same class. If it recurs on these versions, that
issue is where it goes.

covergate was red on a third path this had not read: internal/sandbox is
78.0% on linux against a 78.1% floor, while the same tree measures 78.1%
on darwin. The package compiles a different set of files per GOOS, so a
floor recorded on one platform can be unreachable on the one the gate
runs on. Rather than lower it, three statements nothing reached are now
covered — podman's per-user socket, an authority the table does not
govern, and a nil context carrying no escape approver — which takes
darwin to 78.9% and lifts linux above its floor by the same edges.

covergate now names the lines it is short of. A percentage alone left
whoever has to restore it measuring a package by hand on a platform they
may not be on, which is how this one sat red across three runs.
This commit is contained in:
esengine committed 2026-09-06 09:47:46 -07:00
1 parent 85cba5b991
commit 6e7f189e76
8 files changed
+139 -47

No files matched your search

+4 -4
View File
@@ -2,7 +2,7 @@ module reasonix/desktop
go 1.26.0
toolchain go1.26.6
toolchain go1.26.8
// The desktop shell is a nested module so its CGO/WebKit build never touches the
// CLI's CGO_ENABLED=0 single-static-binary guarantee. The replace lets it import
@@ -77,10 +77,10 @@ require (
golang.org/x/net v0.58.0 // indirect
golang.org/x/text v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.74.4 // indirect
modernc.org/libc v1.75.6 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.57.0 // indirect
modernc.org/memory v1.12.1 // indirect
modernc.org/sqlite v1.58.0 // indirect
mvdan.cc/sh/v3 v3.14.0 // indirect
)
+12 -12
View File
@@ -190,30 +190,30 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
modernc.org/libc v1.75.6 h1:yKk8qo+Di4gkmvRboK8ocCqH22FiUCR6jRy2OwtCRus=
modernc.org/libc v1.75.6/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
modernc.org/memory v1.12.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
modernc.org/sqlite v1.58.0 h1:38u40/bwkfM7f0Myhosl+SEMltSDxnGdQf8o6Kjmys0=
modernc.org/sqlite v1.58.0/go.mod h1:rsD2CckafgObKC4DhBlGBf+RiHxkc3hINGt1Xw32tVY=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+4 -4
View File
@@ -2,7 +2,7 @@ module reasonix
go 1.26.0
toolchain go1.26.6
toolchain go1.26.8
require (
aead.dev/minisign v0.3.0
@@ -43,7 +43,7 @@ require (
golang.org/x/term v0.45.0
golang.org/x/text v0.41.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.57.0
modernc.org/sqlite v1.58.0
mvdan.cc/sh/v3 v3.14.0
)
@@ -70,7 +70,7 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.22.0 // indirect
modernc.org/libc v1.74.4 // indirect
modernc.org/libc v1.75.6 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/memory v1.12.1 // indirect
)
+12 -12
View File
@@ -212,30 +212,30 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
modernc.org/libc v1.75.6 h1:yKk8qo+Di4gkmvRboK8ocCqH22FiUCR6jRy2OwtCRus=
modernc.org/libc v1.75.6/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
modernc.org/memory v1.12.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
modernc.org/sqlite v1.58.0 h1:38u40/bwkfM7f0Myhosl+SEMltSDxnGdQf8o6Kjmys0=
modernc.org/sqlite v1.58.0/go.mod h1:rsD2CckafgObKC4DhBlGBf+RiHxkc3hINGt1Xw32tVY=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+21
View File
@@ -11,6 +11,7 @@ import (
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"testing"
"time"
@@ -162,3 +163,23 @@ func TestAuthorityEndpointsFollowTheClientsResolution(t *testing.T) {
t.Errorf("a missing endpoint was kept (%v); masking it would fail the sandbox closed", got)
}
}
// TestAuthorityEndpointsCoverEveryGovernedClient pins the two answers the
// resolution has beyond the defaults: podman's per-user socket, which lives
// wherever XDG_RUNTIME_DIR points, and an authority this table does not govern,
// which resolves to nothing rather than to a guess.
func TestAuthorityEndpointsCoverEveryGovernedClient(t *testing.T) {
t.Setenv("CONTAINER_HOST", "")
t.Setenv("XDG_RUNTIME_DIR", "/run/user/4242")
got := authorityEndpoints(Podman)
want := filepath.Join("/run/user/4242", "podman", "podman.sock")
if !slices.Contains(got, want) {
t.Fatalf("podman endpoints = %v, want the per-user socket %s", got, want)
}
// An authority nothing governs has no endpoints. Returning a default here
// would mask a path on the strength of a name this table never resolved.
if got := authorityEndpoints(HostAuthority("nothing-governs-this")); got != nil {
t.Fatalf("ungoverned authority resolved to %v, want nothing", got)
}
}
+10
View File
@@ -358,3 +358,13 @@ func TestInstalledButUnusableBwrapIsUnavailable(t *testing.T) {
t.Fatalf("Command with unusable bwrap = %v, wrapped=%v; want unwrapped shell for caller fail-closed", argv, wrapped)
}
}
// TestEscapeApproverFromRefusesANilContext covers the side that fails closed.
// A nil context carries no approver, and answering "found one" there would let
// a caller that never set one act as though somebody had approved.
func TestEscapeApproverFromRefusesANilContext(t *testing.T) {
//nolint:staticcheck // the nil context is the case under test.
if approver, ok := EscapeApproverFrom(nil); ok || approver != nil {
t.Fatalf("EscapeApproverFrom(nil) = %v, %v; want no approver", approver, ok)
}
}
+45 -13
View File
@@ -38,7 +38,7 @@ func main() {
if len(globs) == 0 {
fail(fmt.Errorf("no `sensitive:` paths declared in the project's host checks"))
}
measured, err := measure(*root, globs)
measured, gaps, err := measure(*root, globs)
if err != nil {
fail(err)
}
@@ -70,6 +70,9 @@ func main() {
if len(below) > 0 {
for _, b := range below {
fmt.Fprintln(os.Stderr, "covergate: "+b)
for _, g := range topGaps(gaps[strings.SplitN(b, ":", 2)[0]], 8) {
fmt.Fprintf(os.Stderr, " %3d statements uncovered at %s\n", g.stmts, g.where)
}
}
fmt.Fprintln(os.Stderr, "\nThese paths decide what the agent may execute and reach. Restore the\ncoverage, or run `go run ./tools/covergate -update -allow-drop` and\njustify the diff in the pull request.")
os.Exit(1)
@@ -77,6 +80,16 @@ func main() {
fmt.Printf("covergate: clean (%d sensitive paths at or above their floor)\n", len(measured))
}
// topGaps is what to write a test for, largest first. A shortfall on a platform
// the reader is not on cannot be measured by hand there, and a percentage alone
// says only that something moved.
func topGaps(list []gap, n int) []gap {
if len(list) > n {
return list[:n]
}
return list
}
func fail(err error) {
fmt.Fprintln(os.Stderr, "covergate:", err)
os.Exit(2)
@@ -118,7 +131,7 @@ func packagesFor(globs []string) []string {
return pkgs
}
func measure(root string, globs []string) (map[string]float64, error) {
func measure(root string, globs []string) (map[string]float64, map[string][]gap, error) {
profile := filepath.Join(os.TempDir(), "covergate.out")
defer os.Remove(profile)
args := append([]string{"test", "-count=1", "-coverprofile=" + profile}, packagesFor(globs)...)
@@ -126,23 +139,32 @@ func measure(root string, globs []string) (map[string]float64, error) {
cmd.Dir = root
cmd.Env = append(os.Environ(), "LANG=en_US.UTF-8")
if out, err := cmd.CombinedOutput(); err != nil {
return nil, fmt.Errorf("go test: %w\n%s", err, out)
return nil, nil, fmt.Errorf("go test: %w\n%s", err, out)
}
return fromProfile(profile, globs)
}
// fromProfile aggregates statements per declared path. Per file, not per
// package: a project may call one file sensitive inside a package that is not.
func fromProfile(profile string, globs []string) (map[string]float64, error) {
// gap is one block of statements nothing executed. A floor that has slipped is
// a fact about which lines; reporting only the percentage leaves whoever has to
// restore it measuring the package by hand on a platform they may not have.
type gap struct {
where string
stmts int
}
func fromProfile(profile string, globs []string) (map[string]float64, map[string][]gap, error) {
f, err := os.Open(profile)
if err != nil {
return nil, err
return nil, nil, err
}
defer f.Close()
total := map[string][2]int{}
gaps := map[string][]gap{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
file, stmts, covered, ok := parseProfileLine(scanner.Text())
file, at, stmts, covered, ok := parseProfileLine(scanner.Text())
if !ok {
continue
}
@@ -154,12 +176,22 @@ func fromProfile(profile string, globs []string) (map[string]float64, error) {
t[0] += stmts
if covered > 0 {
t[1] += stmts
} else if stmts > 0 {
gaps[glob] = append(gaps[glob], gap{where: file + ":" + at, stmts: stmts})
}
total[glob] = t
}
}
if err := scanner.Err(); err != nil {
return nil, err
return nil, nil, err
}
for glob := range gaps {
sort.Slice(gaps[glob], func(i, j int) bool {
if gaps[glob][i].stmts != gaps[glob][j].stmts {
return gaps[glob][i].stmts > gaps[glob][j].stmts
}
return gaps[glob][i].where < gaps[glob][j].where
})
}
out := map[string]float64{}
for _, glob := range globs {
@@ -171,26 +203,26 @@ func fromProfile(profile string, globs []string) (map[string]float64, error) {
// floor held to more precision than it is written with fails on itself.
out[glob] = round1(float64(t[1]) / float64(t[0]) * 100)
}
return out, nil
return out, gaps, nil
}
func parseProfileLine(line string) (file string, stmts, covered int, ok bool) {
func parseProfileLine(line string) (file, at string, stmts, covered int, ok bool) {
// reasonix/internal/pkg/file.go:12.34,15.6 3 1
colon := strings.LastIndex(line, ":")
if colon < 0 {
return "", 0, 0, false
return "", "", 0, 0, false
}
fields := strings.Fields(line[colon+1:])
if len(fields) != 3 {
return "", 0, 0, false
return "", "", 0, 0, false
}
stmts, err1 := strconv.Atoi(fields[1])
covered, err2 := strconv.Atoi(fields[2])
if err1 != nil || err2 != nil {
return "", 0, 0, false
return "", "", 0, 0, false
}
file = strings.TrimPrefix(line[:colon], "reasonix/")
return file, stmts, covered, true
return file, fields[0], stmts, covered, true
}
// matchesGlob handles the two shapes the host checks use: a `dir/**` subtree
+31 -2
View File
@@ -59,7 +59,7 @@ func TestFromProfileAggregatesPerDeclaredPath(t *testing.T) {
if err := os.WriteFile(profile, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
got, err := fromProfile(profile, []string{"internal/control/approval.go", "internal/permission/**"})
got, _, err := fromProfile(profile, []string{"internal/control/approval.go", "internal/permission/**"})
if err != nil {
t.Fatal(err)
}
@@ -79,7 +79,7 @@ func TestMeasuredValuesAreRoundedOnce(t *testing.T) {
if err := os.WriteFile(profile, []byte("mode: set\nreasonix/internal/x/a.go:1.1,2.2 3 1\n"), 0o600); err != nil {
t.Fatal(err)
}
got, err := fromProfile(profile, []string{"internal/x/**"})
got, _, err := fromProfile(profile, []string{"internal/x/**"})
if err != nil {
t.Fatal(err)
}
@@ -128,3 +128,32 @@ func TestSensitiveGlobsComeFromTheProjectHostChecks(t *testing.T) {
t.Fatalf("declared sensitive paths not read: %v", globs)
}
}
// A floor that has slipped names the lines, largest first. A percentage alone
// leaves the reader measuring a package by hand on a platform they may not be
// on, which is how a shortfall on one GOOS goes unfixed.
func TestUncoveredBlocksAreReportedLargestFirst(t *testing.T) {
dir := t.TempDir()
profile := filepath.Join(dir, "c.out")
body := "mode: set\n" +
"reasonix/internal/x/a.go:1.1,2.2 3 1\n" +
"reasonix/internal/x/a.go:4.1,5.2 2 0\n" +
"reasonix/internal/x/b.go:7.1,9.2 5 0\n"
if err := os.WriteFile(profile, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
_, gaps, err := fromProfile(profile, []string{"internal/x/**"})
if err != nil {
t.Fatal(err)
}
got := gaps["internal/x/**"]
if len(got) != 2 {
t.Fatalf("gaps = %+v, want the two blocks nothing executed", got)
}
if got[0].stmts != 5 || got[0].where != "internal/x/b.go:7.1,9.2" {
t.Fatalf("first gap = %+v, want the largest one", got[0])
}
if n := len(topGaps(got, 1)); n != 1 {
t.Fatalf("topGaps kept %d, want the cap honoured", n)
}
}