summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-21 18:15:59 +0300
committerPaul Buetow <paul@buetow.org>2026-05-21 18:15:59 +0300
commita8a097102088516fdda3c25d9b1799d9e8a4ba0d (patch)
tree57891a100d96a6a6b3b344ff4d6bede3d30a803b /internal
parenta784437199c1d8bc8dfe7ae1a017779f48667844 (diff)
wb add docs drift tests for syscall coverage
Diffstat (limited to 'internal')
-rw-r--r--internal/generate/docs_drift_test.go153
-rw-r--r--internal/tracepoints/docs_drift_test.go166
2 files changed, 319 insertions, 0 deletions
diff --git a/internal/generate/docs_drift_test.go b/internal/generate/docs_drift_test.go
new file mode 100644
index 0000000..9613fe7
--- /dev/null
+++ b/internal/generate/docs_drift_test.go
@@ -0,0 +1,153 @@
+package generate
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "runtime"
+ "sort"
+ "strings"
+ "testing"
+)
+
+var bytesListItemRE = regexp.MustCompile("`([^`]+)`")
+
+func TestSyscallTracingPlanBytesClassificationStaysInSync(t *testing.T) {
+ doc, err := readSyscallTracingPlan()
+ if err != nil {
+ t.Fatalf("read syscall tracing plan: %v", err)
+ }
+
+ documented, err := parseDocListSection(doc, "## Bytes vs Non-Bytes Classification")
+ if err != nil {
+ t.Fatalf("parse bytes classification section: %v", err)
+ }
+
+ expected := map[string][]string{
+ "ReadClassified": {},
+ "WriteClassified": {},
+ "TransferClassified": {},
+ }
+ for syscall, classification := range retClassifications {
+ switch classification {
+ case ReadClassified:
+ expected["ReadClassified"] = append(expected["ReadClassified"], syscall)
+ case WriteClassified:
+ expected["WriteClassified"] = append(expected["WriteClassified"], syscall)
+ case TransferClassified:
+ expected["TransferClassified"] = append(expected["TransferClassified"], syscall)
+ default:
+ t.Fatalf("unexpected classification %q for syscall %q", classification, syscall)
+ }
+ }
+ for key, values := range expected {
+ expected[key] = normalizeValues(values)
+ }
+
+ if !reflect.DeepEqual(mapKeys(documented), mapKeys(expected)) {
+ t.Fatalf("classification groups mismatch: documented=%v expected=%v", mapKeys(documented), mapKeys(expected))
+ }
+
+ for key, expectedSyscalls := range expected {
+ got := documented[key]
+ if !reflect.DeepEqual(got, expectedSyscalls) {
+ t.Fatalf("%s mismatch:\ndocumented=%v\nexpected=%v", key, got, expectedSyscalls)
+ }
+ }
+}
+
+func readSyscallTracingPlan() (string, error) {
+ _, filename, _, ok := runtime.Caller(0)
+ if !ok {
+ return "", fmt.Errorf("runtime.Caller failed")
+ }
+ repoRoot := filepath.Clean(filepath.Join(filepath.Dir(filename), "..", ".."))
+ content, err := os.ReadFile(filepath.Join(repoRoot, "docs", "syscall-tracing-plan.md"))
+ if err != nil {
+ return "", err
+ }
+ return string(content), nil
+}
+
+func parseDocListSection(doc, heading string) (map[string][]string, error) {
+ lines := strings.Split(doc, "\n")
+ start := -1
+ for i, line := range lines {
+ if strings.TrimSpace(line) == heading {
+ start = i + 1
+ break
+ }
+ }
+ if start == -1 {
+ return nil, fmt.Errorf("heading %q not found", heading)
+ }
+
+ out := map[string][]string{}
+ for i := start; i < len(lines); i++ {
+ line := strings.TrimSpace(lines[i])
+ if strings.HasPrefix(line, "## ") {
+ break
+ }
+ if !strings.HasPrefix(line, "- ") {
+ continue
+ }
+ label, syscalls, err := parseDocBullet(line)
+ if err != nil {
+ return nil, err
+ }
+ out[label] = normalizeValues(syscalls)
+ }
+ if len(out) == 0 {
+ return nil, fmt.Errorf("heading %q had no bullet rows", heading)
+ }
+ return out, nil
+}
+
+func parseDocBullet(line string) (string, []string, error) {
+ entry := strings.TrimSpace(strings.TrimPrefix(line, "- "))
+ colon := strings.Index(entry, ":")
+ if colon <= 0 {
+ return "", nil, fmt.Errorf("invalid list entry %q", line)
+ }
+
+ label := strings.TrimSpace(entry[:colon])
+ if label == "" {
+ return "", nil, fmt.Errorf("missing label in %q", line)
+ }
+
+ matches := bytesListItemRE.FindAllStringSubmatch(entry[colon+1:], -1)
+ if len(matches) == 0 {
+ return "", nil, fmt.Errorf("no syscall list in %q", line)
+ }
+
+ items := make([]string, 0, len(matches))
+ for _, match := range matches {
+ items = append(items, match[1])
+ }
+ return label, items, nil
+}
+
+func normalizeValues(values []string) []string {
+ uniq := map[string]struct{}{}
+ for _, value := range values {
+ uniq[value] = struct{}{}
+ }
+
+ out := make([]string, 0, len(uniq))
+ for value := range uniq {
+ out = append(out, value)
+ }
+ sort.Strings(out)
+ return out
+}
+
+func mapKeys(m map[string][]string) []string {
+ out := make([]string, 0, len(m))
+ for key := range m {
+ out = append(out, key)
+ }
+ sort.Strings(out)
+ return out
+}
diff --git a/internal/tracepoints/docs_drift_test.go b/internal/tracepoints/docs_drift_test.go
new file mode 100644
index 0000000..2d0b883
--- /dev/null
+++ b/internal/tracepoints/docs_drift_test.go
@@ -0,0 +1,166 @@
+package tracepoints
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "runtime"
+ "sort"
+ "strings"
+ "testing"
+)
+
+var docListItemRE = regexp.MustCompile("`([^`]+)`")
+
+func TestSyscallTracingPlanFamiliesStayInSyncWithGeneratedMap(t *testing.T) {
+ doc, err := readSyscallTracingPlan()
+ if err != nil {
+ t.Fatalf("read syscall tracing plan: %v", err)
+ }
+
+ documented, err := parseDocListSection(doc, "## Traced Syscalls by Family")
+ if err != nil {
+ t.Fatalf("parse family section: %v", err)
+ }
+
+ generated := groupSyscallsByValue(syscallFamilies)
+ assertGroupedEqual(t, "family", documented, generated)
+}
+
+func TestSyscallTracingPlanKindsStayInSyncWithGeneratedMap(t *testing.T) {
+ doc, err := readSyscallTracingPlan()
+ if err != nil {
+ t.Fatalf("read syscall tracing plan: %v", err)
+ }
+
+ documented, err := parseDocListSection(doc, "## Traced Syscalls by TracepointKind")
+ if err != nil {
+ t.Fatalf("parse kind section: %v", err)
+ }
+
+ generated := groupSyscallsByValue(syscallKinds)
+ assertGroupedEqual(t, "kind", documented, generated)
+}
+
+func readSyscallTracingPlan() (string, error) {
+ _, filename, _, ok := runtime.Caller(0)
+ if !ok {
+ return "", fmt.Errorf("runtime.Caller failed")
+ }
+ repoRoot := filepath.Clean(filepath.Join(filepath.Dir(filename), "..", ".."))
+ content, err := os.ReadFile(filepath.Join(repoRoot, "docs", "syscall-tracing-plan.md"))
+ if err != nil {
+ return "", err
+ }
+ return string(content), nil
+}
+
+func parseDocListSection(doc, heading string) (map[string][]string, error) {
+ lines := strings.Split(doc, "\n")
+ start := -1
+ for i, line := range lines {
+ if strings.TrimSpace(line) == heading {
+ start = i + 1
+ break
+ }
+ }
+ if start == -1 {
+ return nil, fmt.Errorf("heading %q not found", heading)
+ }
+
+ out := map[string][]string{}
+ for i := start; i < len(lines); i++ {
+ line := strings.TrimSpace(lines[i])
+ if strings.HasPrefix(line, "## ") {
+ break
+ }
+ if !strings.HasPrefix(line, "- ") {
+ continue
+ }
+ label, syscalls, err := parseDocBullet(line)
+ if err != nil {
+ return nil, err
+ }
+ out[label] = normalizeValues(syscalls)
+ }
+ if len(out) == 0 {
+ return nil, fmt.Errorf("heading %q had no bullet rows", heading)
+ }
+ return out, nil
+}
+
+func parseDocBullet(line string) (string, []string, error) {
+ entry := strings.TrimSpace(strings.TrimPrefix(line, "- "))
+ colon := strings.Index(entry, ":")
+ if colon <= 0 {
+ return "", nil, fmt.Errorf("invalid list entry %q", line)
+ }
+
+ label := strings.TrimSpace(entry[:colon])
+ if label == "" {
+ return "", nil, fmt.Errorf("missing label in %q", line)
+ }
+
+ matches := docListItemRE.FindAllStringSubmatch(entry[colon+1:], -1)
+ if len(matches) == 0 {
+ return "", nil, fmt.Errorf("no syscall list in %q", line)
+ }
+
+ items := make([]string, 0, len(matches))
+ for _, match := range matches {
+ items = append(items, match[1])
+ }
+ return label, items, nil
+}
+
+func groupSyscallsByValue(values map[string]string) map[string][]string {
+ grouped := map[string][]string{}
+ for syscall, value := range values {
+ grouped[value] = append(grouped[value], syscall)
+ }
+ for key, syscalls := range grouped {
+ grouped[key] = normalizeValues(syscalls)
+ }
+ return grouped
+}
+
+func normalizeValues(values []string) []string {
+ uniq := map[string]struct{}{}
+ for _, value := range values {
+ uniq[value] = struct{}{}
+ }
+
+ out := make([]string, 0, len(uniq))
+ for value := range uniq {
+ out = append(out, value)
+ }
+ sort.Strings(out)
+ return out
+}
+
+func assertGroupedEqual(t *testing.T, section string, documented, generated map[string][]string) {
+ t.Helper()
+
+ docKeys := mapKeys(documented)
+ genKeys := mapKeys(generated)
+ if !reflect.DeepEqual(docKeys, genKeys) {
+ t.Fatalf("%s groups mismatch: documented=%v generated=%v", section, docKeys, genKeys)
+ }
+
+ for _, key := range genKeys {
+ if !reflect.DeepEqual(documented[key], generated[key]) {
+ t.Fatalf("%s %q mismatch:\ndocumented=%v\ngenerated=%v", section, key, documented[key], generated[key])
+ }
+ }
+}
+
+func mapKeys(m map[string][]string) []string {
+ out := make([]string, 0, len(m))
+ for key := range m {
+ out = append(out, key)
+ }
+ sort.Strings(out)
+ return out
+}