package testkit
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"regexp"
"strings"
"internal/trace"
"internal/trace/event"
"internal/trace/event/go122"
"internal/trace/raw"
"internal/trace/version"
"internal/txtar"
)
func Main (f func (*Trace )) {
out , err := os .Create (os .Args [1 ])
if err != nil {
panic (err .Error())
}
defer out .Close ()
trace := NewTrace ()
f (trace )
if _ , err := out .Write (trace .Generate ()); err != nil {
panic (err .Error())
}
}
type Trace struct {
ver version .Version
names map [string ]event .Type
specs []event .Spec
events []raw .Event
gens []*Generation
validTimestamps bool
bad bool
badMatch *regexp .Regexp
}
func NewTrace () *Trace {
ver := version .Go122
return &Trace {
names : event .Names (ver .Specs ()),
specs : ver .Specs (),
validTimestamps : true ,
}
}
func (t *Trace ) ExpectFailure (pattern string ) {
t .bad = true
t .badMatch = regexp .MustCompile (pattern )
}
func (t *Trace ) ExpectSuccess () {
t .bad = false
}
func (t *Trace ) RawEvent (typ event .Type , data []byte , args ...uint64 ) {
t .events = append (t .events , t .createEvent (typ , data , args ...))
}
func (t *Trace ) DisableTimestamps () {
t .validTimestamps = false
}
func (t *Trace ) Generation (gen uint64 ) *Generation {
g := &Generation {
trace : t ,
gen : gen ,
strings : make (map [string ]uint64 ),
stacks : make (map [stack ]uint64 ),
}
t .gens = append (t .gens , g )
return g
}
func (t *Trace ) Generate () []byte {
var buf bytes .Buffer
tw , err := raw .NewTextWriter (&buf , version .Go122 )
if err != nil {
panic (err .Error())
}
for _ , e := range t .events {
tw .WriteEvent (e )
}
for _ , g := range t .gens {
g .writeEventsTo (tw )
}
expect := []byte ("SUCCESS\n" )
if t .bad {
expect = []byte (fmt .Sprintf ("FAILURE %q\n" , t .badMatch ))
}
return txtar .Format (&txtar .Archive {
Files : []txtar .File {
{Name : "expect" , Data : expect },
{Name : "trace" , Data : buf .Bytes ()},
},
})
}
func (t *Trace ) createEvent (ev event .Type , data []byte , args ...uint64 ) raw .Event {
spec := t .specs [ev ]
if ev != go122 .EvStack {
if arity := len (spec .Args ); len (args ) != arity {
panic (fmt .Sprintf ("expected %d args for %s, got %d" , arity , spec .Name , len (args )))
}
}
return raw .Event {
Version : version .Go122 ,
Ev : ev ,
Args : args ,
Data : data ,
}
}
type stack struct {
stk [32 ]trace .StackFrame
len int
}
var (
NoString = ""
NoStack = []trace .StackFrame {}
)
type Generation struct {
trace *Trace
gen uint64
batches []*Batch
strings map [string ]uint64
stacks map [stack ]uint64
ignoreStringBatchSizeLimit bool
ignoreStackBatchSizeLimit bool
}
func (g *Generation ) Batch (thread trace .ThreadID , time Time ) *Batch {
if !g .trace .validTimestamps {
time = 0
}
b := &Batch {
gen : g ,
thread : thread ,
timestamp : time ,
}
g .batches = append (g .batches , b )
return b
}
func (g *Generation ) String (s string ) uint64 {
if len (s ) == 0 {
return 0
}
if id , ok := g .strings [s ]; ok {
return id
}
id := uint64 (len (g .strings ) + 1 )
g .strings [s ] = id
return id
}
func (g *Generation ) Stack (stk []trace .StackFrame ) uint64 {
if len (stk ) == 0 {
return 0
}
if len (stk ) > 32 {
panic ("stack too big for test" )
}
var stkc stack
copy (stkc .stk [:], stk )
stkc .len = len (stk )
if id , ok := g .stacks [stkc ]; ok {
return id
}
id := uint64 (len (g .stacks ) + 1 )
g .stacks [stkc ] = id
return id
}
func (g *Generation ) writeEventsTo (tw *raw .TextWriter ) {
for _ , b := range g .batches {
b .writeEventsTo (tw )
}
b := g .newStructuralBatch ()
b .RawEvent (go122 .EvFrequency , nil , 15625000 )
b .writeEventsTo (tw )
b = g .newStructuralBatch ()
b .RawEvent (go122 .EvStacks , nil )
for stk , id := range g .stacks {
stk := stk .stk [:stk .len ]
args := []uint64 {id }
for _ , f := range stk {
args = append (args , f .PC , g .String (f .Func ), g .String (f .File ), f .Line )
}
b .RawEvent (go122 .EvStack , nil , args ...)
if !g .ignoreStackBatchSizeLimit && b .size > go122 .MaxBatchSize /2 {
b .writeEventsTo (tw )
b = g .newStructuralBatch ()
}
}
b .writeEventsTo (tw )
b = g .newStructuralBatch ()
b .RawEvent (go122 .EvStrings , nil )
for s , id := range g .strings {
b .RawEvent (go122 .EvString , []byte (s ), id )
if !g .ignoreStringBatchSizeLimit && b .size > go122 .MaxBatchSize /2 {
b .writeEventsTo (tw )
b = g .newStructuralBatch ()
}
}
b .writeEventsTo (tw )
}
func (g *Generation ) newStructuralBatch () *Batch {
return &Batch {gen : g , thread : trace .NoThread }
}
type Batch struct {
gen *Generation
thread trace .ThreadID
timestamp Time
size uint64
events []raw .Event
}
func (b *Batch ) Event (name string , args ...any ) {
ev , ok := b .gen .trace .names [name ]
if !ok {
panic (fmt .Sprintf ("invalid or unknown event %s" , name ))
}
var uintArgs []uint64
argOff := 0
if b .gen .trace .specs [ev ].IsTimedEvent {
if b .gen .trace .validTimestamps {
uintArgs = []uint64 {1 }
} else {
uintArgs = []uint64 {0 }
}
argOff = 1
}
spec := b .gen .trace .specs [ev ]
if arity := len (spec .Args ) - argOff ; len (args ) != arity {
panic (fmt .Sprintf ("expected %d args for %s, got %d" , arity , spec .Name , len (args )))
}
for i , arg := range args {
uintArgs = append (uintArgs , b .uintArgFor (arg , spec .Args [i +argOff ]))
}
b .RawEvent (ev , nil , uintArgs ...)
}
func (b *Batch ) uintArgFor (arg any , argSpec string ) uint64 {
components := strings .SplitN (argSpec , "_" , 2 )
typStr := components [0 ]
if len (components ) == 2 {
typStr = components [1 ]
}
var u uint64
switch typStr {
case "value" :
u = arg .(uint64 )
case "stack" :
u = b .gen .Stack (arg .([]trace .StackFrame ))
case "seq" :
u = uint64 (arg .(Seq ))
case "pstatus" :
u = uint64 (arg .(go122 .ProcStatus ))
case "gstatus" :
u = uint64 (arg .(go122 .GoStatus ))
case "g" :
u = uint64 (arg .(trace .GoID ))
case "m" :
u = uint64 (arg .(trace .ThreadID ))
case "p" :
u = uint64 (arg .(trace .ProcID ))
case "string" :
u = b .gen .String (arg .(string ))
case "task" :
u = uint64 (arg .(trace .TaskID ))
default :
panic (fmt .Sprintf ("unsupported arg type %q for spec %q" , typStr , argSpec ))
}
return u
}
func (b *Batch ) RawEvent (typ event .Type , data []byte , args ...uint64 ) {
ev := b .gen .trace .createEvent (typ , data , args ...)
b .size += 1
var buf [binary .MaxVarintLen64 ]byte
for _ , arg := range args {
b .size += uint64 (binary .PutUvarint (buf [:], arg ))
}
if len (data ) != 0 {
b .size += uint64 (binary .PutUvarint (buf [:], uint64 (len (data ))))
b .size += uint64 (len (data ))
}
b .events = append (b .events , ev )
}
func (b *Batch ) writeEventsTo (tw *raw .TextWriter ) {
tw .WriteEvent (raw .Event {
Version : version .Go122 ,
Ev : go122 .EvEventBatch ,
Args : []uint64 {b .gen .gen , uint64 (b .thread ), uint64 (b .timestamp ), b .size },
})
for _ , e := range b .events {
tw .WriteEvent (e )
}
}
type Seq uint64
type Time uint64
The pages are generated with Golds v0.6.9-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .