internal/testutil/snake: Make a more advanced (recursive) snake package
This commit is contained in:
parent
1b43b92687
commit
fb374c5cd5
214
internal/testutil/snake/snake.go
Normal file
214
internal/testutil/snake/snake.go
Normal file
@ -0,0 +1,214 @@
|
||||
// Package 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.
|
||||
package snake
|
||||
|
||||
import "fmt"
|
||||
import "strings"
|
||||
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
||||
|
||||
var _ Node = Order { }
|
||||
var _ Node = Snake { }
|
||||
var _ Node = Leaf { }
|
||||
|
||||
// Check checks the data against the specified node. If the data doesn't satisfy
|
||||
// the node, or the comparison succeded but didn't consume all the data, this
|
||||
// function returns false, and the index of the byte where the inequality is.
|
||||
func Check(node Node, data []byte) (ok bool, n int) {
|
||||
ok, n = node.Check(data)
|
||||
if !ok {
|
||||
return false, n
|
||||
}
|
||||
if n != len(data) {
|
||||
return false, n
|
||||
}
|
||||
return true, n
|
||||
}
|
||||
|
||||
// O returns a new order given a vararg node slice.
|
||||
func O(nodes ...Node) Order {
|
||||
return Order(nodes)
|
||||
}
|
||||
|
||||
// S returns a new snake given a vararg node slice.
|
||||
func S(nodes ...Node) Snake {
|
||||
return Snake(nodes)
|
||||
}
|
||||
|
||||
// L returns a new leaf given a vararg byte slice.
|
||||
func L(data ...byte) Leaf {
|
||||
return Leaf([]byte(data))
|
||||
}
|
||||
|
||||
// Order is satisfied when the data satisfies each of its nodes in the order
|
||||
// that they are specified in the slice.
|
||||
type Order []Node
|
||||
|
||||
// Check determines if the data satisfies the Order.
|
||||
func (this Order) Check(data []byte) (ok bool, n int) {
|
||||
left := data
|
||||
for _, node := range this {
|
||||
ok, nn := node.Check(left)
|
||||
n += nn; if !ok { return false, n }
|
||||
left = left[nn:]
|
||||
}
|
||||
return true, n
|
||||
}
|
||||
|
||||
// Flatten returns the Order flattened to a byte array. The result of this
|
||||
// function always satisfies the Order.
|
||||
func (this Order) Flatten() []byte {
|
||||
flat := []byte { }
|
||||
for _, node := range this {
|
||||
flat = append(flat, node.Flatten()...)
|
||||
}
|
||||
return flat
|
||||
}
|
||||
|
||||
func (this Order) String() string {
|
||||
out := strings.Builder { }
|
||||
for index, node := range this {
|
||||
if index > 0 {
|
||||
fmt.Fprint(&out, " :")
|
||||
}
|
||||
fmt.Fprintf(&out, " %v", node)
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Add returns a new order with the given nodes appended to it.
|
||||
func (this Order) Add(nodes ...Node) Order {
|
||||
newOrder := make(Order, len(this) + len(nodes))
|
||||
copy(newOrder, this)
|
||||
copy(newOrder[len(this):], Order(nodes))
|
||||
return newOrder
|
||||
}
|
||||
|
||||
// AddO returns a new order with the given order appended to it.
|
||||
func (this Order) AddO(nodes ...Node) Order {
|
||||
return this.Add(O(nodes...))
|
||||
}
|
||||
|
||||
// AddS returns a new order with the given snake appended to it.
|
||||
func (this Order) AddS(nodes ...Node) Order {
|
||||
return this.Add(S(nodes...))
|
||||
}
|
||||
|
||||
// AddL returns a new order with the given leaf appended to it.
|
||||
func (this Order) AddL(data ...byte) Order {
|
||||
return this.Add(L(data...))
|
||||
}
|
||||
|
||||
// Snake is satisfied when the data satisfies each of its nodes in no particular
|
||||
// order.
|
||||
type Snake []Node
|
||||
|
||||
// Check determines if the data satisfies the snake.
|
||||
func (this Snake) Check(data []byte) (ok bool, n int) {
|
||||
fmt.Println("CHECKING SNAKE")
|
||||
left := data
|
||||
nodes := map[int] Node { }
|
||||
for key, node := range this {
|
||||
nodes[key] = node
|
||||
}
|
||||
for len(nodes) > 0 {
|
||||
found := false
|
||||
for key, node := range nodes {
|
||||
fmt.Println(left, key, node)
|
||||
ok, nn := node.Check(left)
|
||||
fmt.Println(ok, nn)
|
||||
if !ok { continue }
|
||||
n += nn
|
||||
left = data[n:]
|
||||
delete(nodes, key)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
if !found { return false, n }
|
||||
}
|
||||
return true, n
|
||||
}
|
||||
|
||||
// Flatten returns the snake flattened to a byte array. The result of this
|
||||
// function always satisfies the snake.
|
||||
func (this Snake) Flatten() []byte {
|
||||
flat := []byte { }
|
||||
for _, node := range this {
|
||||
flat = append(flat, node.Flatten()...)
|
||||
}
|
||||
return flat
|
||||
}
|
||||
|
||||
func (this Snake) String() string {
|
||||
out := strings.Builder { }
|
||||
out.WriteString("[")
|
||||
for index, node := range this {
|
||||
if index > 0 {
|
||||
fmt.Fprint(&out, " /")
|
||||
}
|
||||
fmt.Fprintf(&out, " %v", node)
|
||||
}
|
||||
out.WriteString(" ]")
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Add returns a new snake with the given nodes appended to it.
|
||||
func (this Snake) Add(nodes ...Node) Snake {
|
||||
newSnake := make(Snake, len(this) + len(nodes))
|
||||
copy(newSnake, this)
|
||||
copy(newSnake[len(this):], Snake(nodes))
|
||||
return newSnake
|
||||
}
|
||||
|
||||
// AddO returns a new snake with the given order appended to it.
|
||||
func (this Snake) AddO(nodes ...Node) Snake {
|
||||
return this.Add(O(nodes...))
|
||||
}
|
||||
|
||||
// AddS returns a new snake with the given snake appended to it.
|
||||
func (this Snake) AddS(nodes ...Node) Snake {
|
||||
return this.Add(S(nodes...))
|
||||
}
|
||||
|
||||
// AddL returns a new snake with the given leaf appended to it.
|
||||
func (this Snake) AddL(data ... byte) Snake {
|
||||
return this.Add(L(data...))
|
||||
}
|
||||
|
||||
// Leaf is satisfied when the data matches it exactly.
|
||||
type Leaf []byte
|
||||
|
||||
// Check determines if the data is equal to the leaf.
|
||||
func (this Leaf) Check(data []byte) (ok bool, n int) {
|
||||
if len(data) < len(this) {
|
||||
return false, len(data)
|
||||
}
|
||||
for index, byt := range this {
|
||||
if byt != data[index] {
|
||||
return false, index
|
||||
}
|
||||
}
|
||||
return true, len(this)
|
||||
}
|
||||
|
||||
// This one's easy.
|
||||
func (this Leaf) Flatten() []byte {
|
||||
return []byte(this)
|
||||
}
|
||||
|
||||
func (this Leaf) String() string {
|
||||
return tu.HexBytes([]byte(this))
|
||||
}
|
||||
|
||||
// Node represents a snake node.
|
||||
type Node interface {
|
||||
// Check determines if the data satisfies the node. If satisfied, the function
|
||||
// will return true, and the index at which it stopped. If not, the
|
||||
// function will return false, and the index of the first byte that
|
||||
// didn't match. As long as the start of the data satisfies the node,
|
||||
// whatever comes after it doesn't matter.
|
||||
Check(data []byte) (ok bool, n int)
|
||||
// Flatten returns the node flattened to a byte array. The result of
|
||||
// this function always satisfies the node.
|
||||
Flatten() []byte
|
||||
}
|
||||
66
internal/testutil/snake/snake_test.go
Normal file
66
internal/testutil/snake/snake_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
package snake
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSnakeA(test *testing.T) {
|
||||
snake := O().AddL(1, 6).AddS(
|
||||
L(1),
|
||||
L(2),
|
||||
L(3),
|
||||
L(4),
|
||||
L(5),
|
||||
).AddL(9)
|
||||
|
||||
test.Log(snake)
|
||||
|
||||
ok, n := Check(snake, []byte { 1, 6, 1, 2, 3, 4, 5, 9 })
|
||||
if !ok { test.Fatal("false negative:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 5, 4, 3, 2, 1, 9 })
|
||||
if !ok { test.Fatal("false negative:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 3, 1, 4, 2, 5, 9 })
|
||||
if !ok { test.Fatal("false negative:", n) }
|
||||
|
||||
ok, n = Check(snake, []byte { 1, 6, 9 })
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 1, 2, 3, 4, 5, 6, 9 })
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 0, 2, 3, 4, 5, 6, 9 })
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 7, 1, 4, 2, 5, 9 })
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 7, 3, 1, 4, 2, 5, 9 })
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 7, 3, 1, 4, 2, 5, 9 })
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 1, 2, 3, 4, 5, 9, 10})
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
}
|
||||
|
||||
func TestSnakeB(test *testing.T) {
|
||||
snake := O().AddL(1, 6).AddS(
|
||||
L(1),
|
||||
L(2),
|
||||
).AddL(9).AddS(
|
||||
L(3, 2),
|
||||
L(0),
|
||||
L(1, 1, 2, 3),
|
||||
)
|
||||
|
||||
test.Log(snake)
|
||||
|
||||
ok, n := Check(snake, []byte { 1, 6, 1, 2, 9, 3, 2, 0, 1, 1, 2, 3})
|
||||
if !ok { test.Fatal("false negative:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 2, 1, 9, 0, 1, 1, 2, 3, 3, 2})
|
||||
if !ok { test.Fatal("false negative:", n) }
|
||||
|
||||
ok, n = Check(snake, []byte { 1, 6, 9 })
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 1, 2, 9 })
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 9, 3, 2, 0, 1, 1, 2, 3})
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 2, 9, 0, 1, 1, 2, 3, 3, 2})
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
ok, n = Check(snake, []byte { 1, 6, 1, 2, 9, 3, 2, 1, 1, 2, 3})
|
||||
if ok { test.Fatal("false positive:", n) }
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user