mirror of https://github.com/golang/go.git
191 lines
4.1 KiB
Go
191 lines
4.1 KiB
Go
// Copyright 2024 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package pgo
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// equal returns an error if got and want are not equal.
|
|
func equal(got, want *Profile) error {
|
|
if got.TotalWeight != want.TotalWeight {
|
|
return fmt.Errorf("got.TotalWeight %d != want.TotalWeight %d", got.TotalWeight, want.TotalWeight)
|
|
}
|
|
if !reflect.DeepEqual(got.NamedEdgeMap.ByWeight, want.NamedEdgeMap.ByWeight) {
|
|
return fmt.Errorf("got.NamedEdgeMap.ByWeight != want.NamedEdgeMap.ByWeight\ngot = %+v\nwant = %+v", got.NamedEdgeMap.ByWeight, want.NamedEdgeMap.ByWeight)
|
|
}
|
|
if !reflect.DeepEqual(got.NamedEdgeMap.Weight, want.NamedEdgeMap.Weight) {
|
|
return fmt.Errorf("got.NamedEdgeMap.Weight != want.NamedEdgeMap.Weight\ngot = %+v\nwant = %+v", got.NamedEdgeMap.Weight, want.NamedEdgeMap.Weight)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func testRoundTrip(t *testing.T, d *Profile) []byte {
|
|
var buf bytes.Buffer
|
|
n, err := d.WriteTo(&buf)
|
|
if err != nil {
|
|
t.Fatalf("WriteTo got err %v want nil", err)
|
|
}
|
|
if n != int64(buf.Len()) {
|
|
t.Errorf("WriteTo got n %d want %d", n, int64(buf.Len()))
|
|
}
|
|
|
|
b := buf.Bytes()
|
|
|
|
got, err := FromSerialized(&buf)
|
|
if err != nil {
|
|
t.Fatalf("processSerialized got err %v want nil", err)
|
|
}
|
|
if err := equal(got, d); err != nil {
|
|
t.Errorf("processSerialized output does not match input: %v", err)
|
|
}
|
|
|
|
return b
|
|
}
|
|
|
|
func TestEmpty(t *testing.T) {
|
|
d := emptyProfile()
|
|
b := testRoundTrip(t, d)
|
|
|
|
// Contents should consist of only a header.
|
|
if string(b) != serializationHeader {
|
|
t.Errorf("WriteTo got %q want %q", string(b), serializationHeader)
|
|
}
|
|
}
|
|
|
|
func TestRoundTrip(t *testing.T) {
|
|
d := &Profile{
|
|
TotalWeight: 3,
|
|
NamedEdgeMap: NamedEdgeMap{
|
|
ByWeight: []NamedCallEdge{
|
|
{
|
|
CallerName: "a",
|
|
CalleeName: "b",
|
|
CallSiteOffset: 14,
|
|
},
|
|
{
|
|
CallerName: "c",
|
|
CalleeName: "d",
|
|
CallSiteOffset: 15,
|
|
},
|
|
},
|
|
Weight: map[NamedCallEdge]int64{
|
|
{
|
|
CallerName: "a",
|
|
CalleeName: "b",
|
|
CallSiteOffset: 14,
|
|
}: 2,
|
|
{
|
|
CallerName: "c",
|
|
CalleeName: "d",
|
|
CallSiteOffset: 15,
|
|
}: 1,
|
|
},
|
|
},
|
|
}
|
|
|
|
testRoundTrip(t, d)
|
|
}
|
|
|
|
func constructFuzzProfile(t *testing.T, b []byte) *Profile {
|
|
// The fuzzer can't construct an arbitrary structure, so instead we
|
|
// consume bytes from b to act as our edge data.
|
|
r := bytes.NewReader(b)
|
|
consumeString := func() (string, bool) {
|
|
// First byte: how many bytes to read for this string? We only
|
|
// use a byte to avoid making humongous strings.
|
|
length, err := r.ReadByte()
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
if length == 0 {
|
|
return "", false
|
|
}
|
|
|
|
b := make([]byte, length)
|
|
_, err = r.Read(b)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
|
|
return string(b), true
|
|
}
|
|
consumeInt64 := func() (int64, bool) {
|
|
b := make([]byte, 8)
|
|
_, err := r.Read(b)
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
|
|
return int64(binary.LittleEndian.Uint64(b)), true
|
|
}
|
|
|
|
d := emptyProfile()
|
|
|
|
for {
|
|
caller, ok := consumeString()
|
|
if !ok {
|
|
break
|
|
}
|
|
if strings.ContainsAny(caller, " \r\n") {
|
|
t.Skip("caller contains space or newline")
|
|
}
|
|
|
|
callee, ok := consumeString()
|
|
if !ok {
|
|
break
|
|
}
|
|
if strings.ContainsAny(callee, " \r\n") {
|
|
t.Skip("callee contains space or newline")
|
|
}
|
|
|
|
line, ok := consumeInt64()
|
|
if !ok {
|
|
break
|
|
}
|
|
weight, ok := consumeInt64()
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
edge := NamedCallEdge{
|
|
CallerName: caller,
|
|
CalleeName: callee,
|
|
CallSiteOffset: int(line),
|
|
}
|
|
|
|
if _, ok := d.NamedEdgeMap.Weight[edge]; ok {
|
|
t.Skip("duplicate edge")
|
|
}
|
|
|
|
d.NamedEdgeMap.Weight[edge] = weight
|
|
d.TotalWeight += weight
|
|
}
|
|
|
|
byWeight := make([]NamedCallEdge, 0, len(d.NamedEdgeMap.Weight))
|
|
for namedEdge := range d.NamedEdgeMap.Weight {
|
|
byWeight = append(byWeight, namedEdge)
|
|
}
|
|
sortByWeight(byWeight, d.NamedEdgeMap.Weight)
|
|
d.NamedEdgeMap.ByWeight = byWeight
|
|
|
|
return d
|
|
}
|
|
|
|
func FuzzRoundTrip(f *testing.F) {
|
|
f.Add([]byte("")) // empty profile
|
|
|
|
f.Fuzz(func(t *testing.T, b []byte) {
|
|
d := constructFuzzProfile(t, b)
|
|
testRoundTrip(t, d)
|
|
})
|
|
}
|