testcapture: refuse golden captures that name no tool commit

It walks every testdata folder, so a new folder is covered too. Captures
taken since tscap refuses such builds are listed until taken again.
This commit is contained in:
Kristoffer Dalby committed 2026-10-29 14:00:02 +01:00
1 parent 208043c88d
commit 729464091e
1 file changed
+141
@@ -0,0 +1,141 @@
package testcapture_test
import (
"bufio"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
)
// traceableSince is when tscap and saasprobe began refusing builds that
// name no commit. Older captures predate that, and many say tscap/dev.
var traceableSince = time.Date(2026, 10, 29, 0, 0, 0, 0, time.UTC)
// untraceable lists, by path under hscontrol/, captures taken since
// traceableSince that name no commit. Capture one again with a build of a
// pushed tscap commit, then delete its entry: a listed file that names a
// commit, or is gone, fails.
var untraceable = map[string]bool{
"policy/v2/testdata/ssh_results/forwarding/ssh-forwarding-accept-agent.hujson": true,
"policy/v2/testdata/ssh_results/forwarding/ssh-forwarding-accept-default.hujson": true,
"policy/v2/testdata/ssh_results/forwarding/ssh-forwarding-accept-local.hujson": true,
"policy/v2/testdata/ssh_results/forwarding/ssh-forwarding-accept-remote.hujson": true,
"policy/v2/testdata/ssh_results/forwarding/ssh-forwarding-check-agent.hujson": true,
"policy/v2/testdata/ssh_results/forwarding/ssh-forwarding-check-default.hujson": true,
"policy/v2/testdata/ssh_results/forwarding/ssh-forwarding-check-local.hujson": true,
"policy/v2/testdata/ssh_results/forwarding/ssh-forwarding-check-remote.hujson": true,
}
var (
// toolCommit is tool_version as a build of a commit stamps it.
toolCommit = regexp.MustCompile(`^(tscap/|saasprobe )[0-9a-f]{40}$`)
capturedAtField = regexp.MustCompile(`^\s*"captured_at":\s*"([^"]+)"`)
toolVersionField = regexp.MustCompile(`^\s*"tool_version":\s*"([^"]*)"`)
)
// TestCapturesNameTheirCommit keeps every golden capture traceable to the
// code that made it, so it can be made again. It walks every testdata folder,
// so a capture in a new folder is checked too.
func TestCapturesNameTheirCommit(t *testing.T) {
root := filepath.Join("..", "..")
listed := map[string]bool{}
captures := 0
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
rel, err := filepath.Rel(root, path)
if err != nil {
return err
}
rel = filepath.ToSlash(rel)
if d.IsDir() || filepath.Ext(path) != ".hujson" || !strings.Contains("/"+rel, "/testdata/") {
return nil
}
capturedAt, tool, ok := provenance(t, path)
if !ok {
// A policy or scenario file, not a capture.
return nil
}
captures++
switch {
case untraceable[rel]:
listed[rel] = true
if toolCommit.MatchString(tool) {
t.Errorf("%s names a commit now; delete its untraceable entry", rel)
}
case !capturedAt.Before(traceableSince) && !toolCommit.MatchString(tool):
t.Errorf("%s: tool_version %q names no commit; capture it again with a build of a pushed tscap commit", rel, tool)
}
return nil
})
if err != nil {
t.Fatal(err)
}
if captures == 0 {
t.Fatal("no golden captures found")
}
for rel := range untraceable {
if !listed[rel] {
t.Errorf("%s is listed as untraceable but is no capture; delete its entry", rel)
}
}
}
// provenance reads captured_at and tool_version from the top of a file,
// and reports whether it is a capture; the whole corpus is too large to
// decode in a unit test.
func provenance(t *testing.T, path string) (time.Time, string, bool) {
t.Helper()
f, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer f.Close()
var (
capturedAt time.Time
tool string
haveAt, haveV bool
)
s := bufio.NewScanner(f)
s.Buffer(make([]byte, 0, 64<<10), 4<<20)
for (!haveAt || !haveV) && s.Scan() {
if m := capturedAtField.FindStringSubmatch(s.Text()); m != nil {
capturedAt, err = time.Parse(time.RFC3339Nano, m[1])
if err != nil {
t.Fatalf("%s: captured_at: %v", path, err)
}
haveAt = true
}
if m := toolVersionField.FindStringSubmatch(s.Text()); m != nil {
tool, haveV = m[1], true
}
}
if haveAt != haveV {
t.Fatalf("%s: has only one of captured_at and tool_version (scan: %v)", path, s.Err())
}
return capturedAt, tool, haveAt
}