internal/testutil/snake: Make a more advanced (recursive) snake package
This commit is contained in:
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) }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user