Files
gasm-sdk/verify/corpus_test.go
T
2026-08-30 21:42:12 +02:00

159 lines
3.8 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package verify
import (
"encoding/json"
"math/rand"
"os"
"runtime"
"strconv"
"testing"
)
func loadBasicKernel(t *testing.T) *Kernel {
t.Helper()
k, err := Load("../testdata/verify/basic_amd64.s")
if err != nil {
t.Fatalf("Load: %v", err)
}
t.Cleanup(k.Close)
return k
}
func TestReplayEntry(t *testing.T) {
k := loadBasicKernel(t)
e := CorpusEntry{Func: "add", Args: []CorpusArg{
{Kind: "int", Value: "2"},
{Kind: "int", Value: "3"},
}}
out, err := k.ReplayEntry("add", e)
if err != nil {
t.Fatalf("ReplayEntry: %v", err)
}
if got := int64(GetUint64(out, 16)); got != 5 {
t.Errorf("replay add(2, 3) = %d, want 5", got)
}
}
func TestCorpusRoundTrip(t *testing.T) {
k := loadBasicKernel(t)
e := CorpusEntry{Func: "add", Args: []CorpusArg{
{Kind: "int", Value: "20"},
{Kind: "int", Value: "22"},
}}
data, err := json.Marshal(e)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var back CorpusEntry
if err := json.Unmarshal(data, &back); err != nil {
t.Fatalf("unmarshal: %v", err)
}
out, err := k.ReplayEntry("add", back)
if err != nil {
t.Fatalf("ReplayEntry: %v", err)
}
if got := int64(GetUint64(out, 16)); got != 42 {
t.Errorf("round-trip replay = %d, want 42", got)
}
}
// TestGenDualArgsEntryReplayable checks that the entry recorded alongside a
// generated input replays to the same observable call.
func TestGenDualArgsEntryReplayable(t *testing.T) {
k := loadBasicKernel(t)
sig, ok := parseFuncSig("// func add(a, b int) int")
if !ok {
t.Fatal("parseFuncSig failed")
}
_, _, bufs, entry := genDualArgs(rand.New(rand.NewSource(1)), sig, 24)
if len(entry.Args) != 2 || entry.Args[0].Kind != "int" {
t.Fatalf("unexpected entry: %+v", entry)
}
out, err := k.ReplayEntry("add", entry)
if err != nil {
t.Fatalf("ReplayEntry: %v", err)
}
want := int64(GetUint64(out, 16))
got := entryInt(t, entry.Args[0]) + entryInt(t, entry.Args[1])
if got != want {
t.Errorf("replayed sum = %d, want %d", want, got)
}
runtime.KeepAlive(bufs)
}
func entryInt(t *testing.T, a CorpusArg) int64 {
t.Helper()
v, err := strconv.ParseUint(a.Value, 10, 64)
if err != nil {
t.Fatalf("entry value %q: %v", a.Value, err)
}
return int64(v)
}
// TestFuzzHookSavesFailures fuzzes add against the go-tool-asm build of a
// sub kernel with the same signature, so every iteration mismatches (safely:
// both kernels read only their own arguments) and the hook must record
// replayable entries.
func TestFuzzHookSavesFailures(t *testing.T) {
src := `#include "textflag.h"
// func add(a, b int) int
TEXT ·add(SB), NOSPLIT, $0-24
MOVQ a+0(FP), AX
ADDQ b+8(FP), AX
MOVQ AX, ret+16(FP)
RET
// func sub(a, b int) int
TEXT ·sub(SB), NOSPLIT, $0-24
MOVQ a+0(FP), AX
SUBQ b+8(FP), AX
MOVQ AX, ret+16(FP)
RET
`
dir := t.TempDir()
file := dir + "/addsub_test_amd64.s"
if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
t.Fatalf("write kernel: %v", err)
}
k, err := Load(file)
if err != nil {
t.Fatalf("Load: %v", err)
}
t.Cleanup(k.Close)
sig, ok := parseFuncSig("// func add(a, b int) int")
if !ok {
t.Fatal("parseFuncSig failed")
}
gt, err := GroundTruth(file)
if err != nil {
t.Skipf("go tool asm unavailable: %v", err)
}
var saved []CorpusEntry
res := k.FuzzFuncHook("add", sig, gt["sub"], 5, 42, func(e CorpusEntry) {
saved = append(saved, e)
})
if res.Mismatches == 0 {
t.Fatal("expected mismatches against the sub reference")
}
if len(saved) == 0 {
t.Fatal("hook saved no entries despite mismatches")
}
for _, e := range saved {
if e.Func != "add" || len(e.Args) != 2 {
t.Errorf("bad entry: %+v", e)
}
if _, err := k.ReplayEntry(e.Func, e); err != nil {
t.Errorf("saved entry does not replay: %v", err)
}
}
}