hopp/internal/testutil/testutil.go

162 lines
4.2 KiB
Go

package testutil
import "fmt"
import "slices"
import "strings"
import "reflect"
// Snake lets you compare blocks of data where the ordering of certain parts may
// be swapped every which way. It is designed for comparing the encoding of
// maps where the ordering of individual elements is inconsistent.
//
// The snake is divided into sectors, which hold a number of variations. For a
// sector to be satisfied by some data, some ordering of it must match the data
// exactly. for the snake to be satisfied by some data, its sectors must match
// the data in order, but the internal ordering of each sector doesn't matter.
type Snake [] [] []byte
// snake sector variation
// S returns a new snake.
func S(data ...byte) Snake {
return (Snake { }).Add(data...)
}
// AddVar returns a new snake with the given sector added on to it. Successive
// calls of this method can be chained together to create a big ass snake.
func (sn Snake) AddVar(sector ...[]byte) Snake {
slice := make(Snake, len(sn) + 1)
copy(slice, sn)
slice[len(slice) - 1] = sector
return slice
}
// Add is like AddVar, but adds a sector with only one variation, which means it
// does not vary, hence why the method is called that.
func (sn Snake) Add(data ...byte) Snake {
return sn.AddVar(data)
}
// Check determines if the data satisfies the snake.
func (sn Snake) Check(data []byte) (ok bool, n int) {
left := data
variations := map[int] []byte { }
for _, sector := range sn {
clear(variations)
for key, variation := range sector {
variations[key] = variation
}
for len(variations) > 0 {
found := false
for key, variation := range variations {
if len(left) < len(variation) { continue }
if !slices.Equal(left[:len(variation)], variation) { continue }
n += len(variation)
left = data[n:]
delete(variations, key)
found = true
}
if !found { return false, n }
}
}
if n < len(data) {
return false, n
}
return true, n
}
func (sn Snake) String() string {
if len(sn) == 0 || len(sn[0]) == 0 || len(sn[0][0]) == 0 {
return "EMPTY"
}
out := strings.Builder { }
for index, sector := range sn {
if index > 0 { out.WriteString(" : ") }
out.WriteRune('[')
for index, variation := range sector {
if index > 0 { out.WriteString(" / ") }
for _, byt := range variation {
fmt.Fprintf(&out, "%02x", byt)
}
}
out.WriteRune(']')
}
return out.String()
}
// HexBytes formats bytes into a hexadecimal string.
func HexBytes(data []byte) string {
if len(data) == 0 { return "EMPTY" }
out := strings.Builder { }
for _, byt := range data {
fmt.Fprintf(&out, "%02x", byt)
}
return out.String()
}
// Describe returns a string representing the type and data of the given value.
func Describe(value any) string {
desc := describer { }
desc.describe(reflect.ValueOf(value))
return desc.String()
}
type describer struct {
strings.Builder
indent int
}
func (this *describer) describe(value reflect.Value) {
value = reflect.ValueOf(value.Interface())
switch value.Kind() {
case reflect.Array, reflect.Slice:
this.printf("[\n")
this.indent += 1
for index := 0; index < value.Len(); index ++ {
this.iprintf("")
this.describe(value.Index(index))
this.iprintf("\n")
}
this.indent -= 1
this.iprintf("]")
case reflect.Struct:
this.printf("struct {\n")
this.indent += 1
typ := value.Type()
for index := range typ.NumField() {
indexBuffer := [1]int { index }
this.iprintf("%s: ", typ.Field(index).Name)
this.describe(value.FieldByIndex(indexBuffer[:]))
this.iprintf("\n")
}
this.indent -= 1
this.iprintf("}\n")
case reflect.Map:
this.printf("map {\n")
this.indent += 1
iter := value.MapRange()
for iter.Next() {
this.iprintf("")
this.describe(iter.Key())
this.printf(": ")
this.describe(iter.Value())
this.iprintf("\n")
}
this.indent -= 1
this.iprintf("}\n")
case reflect.Pointer:
this.printf("& ")
this.describe(value.Elem())
default:
this.printf("<%v %v>", value.Type(), value.Interface())
}
}
func (this *describer) printf(format string, v ...any) {
fmt.Fprintf(this, format, v...)
}
func (this *describer) iprintf(format string, v ...any) {
fmt.Fprintf(this, strings.Repeat("\t", this.indent) + format, v...)
}