mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-29 06:37:17 +00:00
0c3586a8ea
* Add proto array fork choice object to RPC * Add pb * Add pb * Expose proto array store object * Test * Merge branch 'forkchoice-endpoint' of github.com:prysmaticlabs/prysm into forkchoice-endpoint * s/Nodes/nodes * Remove proto from gitignore * More implementations of GetProtoArrayForkChoice * Comments * Use hex * Gazelle * GetForkChoice Test * Remove pb.go * Merge branch 'master' into forkchoice-endpoint * Typo, thanks Raul! * Merge branch 'forkchoice-endpoint' of github.com:prysmaticlabs/prysm into forkchoice-endpoint
532 lines
16 KiB
Go
532 lines
16 KiB
Go
package protoarray
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
func TestStore_Head_UnknownJustifiedRoot(t *testing.T) {
|
|
s := &Store{NodeIndices: make(map[[32]byte]uint64)}
|
|
|
|
if _, err := s.head(context.Background(), [32]byte{}); err.Error() != errUnknownJustifiedRoot.Error() {
|
|
t.Fatal("Did not get wanted error")
|
|
}
|
|
}
|
|
|
|
func TestStore_Head_UnknownJustifiedIndex(t *testing.T) {
|
|
r := [32]byte{'A'}
|
|
indices := make(map[[32]byte]uint64)
|
|
indices[r] = 1
|
|
s := &Store{NodeIndices: indices}
|
|
|
|
if _, err := s.head(context.Background(), r); err.Error() != errInvalidJustifiedIndex.Error() {
|
|
t.Fatal("Did not get wanted error")
|
|
}
|
|
}
|
|
|
|
func TestStore_Head_Itself(t *testing.T) {
|
|
r := [32]byte{'A'}
|
|
indices := make(map[[32]byte]uint64)
|
|
indices[r] = 0
|
|
|
|
// Since the justified node does not have a best descendant so the best node
|
|
// is itself.
|
|
s := &Store{NodeIndices: indices, Nodes: []*Node{{Root: r, BestDescendent: NonExistentNode}}}
|
|
h, err := s.head(context.Background(), r)
|
|
if err != nil {
|
|
t.Fatal("Did not get wanted error")
|
|
}
|
|
|
|
if h != r {
|
|
t.Error("Did not get wanted head")
|
|
}
|
|
}
|
|
|
|
func TestStore_Head_BestDescendant(t *testing.T) {
|
|
r := [32]byte{'A'}
|
|
best := [32]byte{'B'}
|
|
indices := make(map[[32]byte]uint64)
|
|
indices[r] = 0
|
|
|
|
// Since the justified node's best descendent is at index 1 and it's root is `best`,
|
|
// the head should be `best`.
|
|
s := &Store{NodeIndices: indices, Nodes: []*Node{{Root: r, BestDescendent: 1}, {Root: best}}}
|
|
h, err := s.head(context.Background(), r)
|
|
if err != nil {
|
|
t.Fatal("Did not get wanted error")
|
|
}
|
|
|
|
if h != best {
|
|
t.Error("Did not get wanted head")
|
|
}
|
|
}
|
|
|
|
func TestStore_Insert_UnknownParent(t *testing.T) {
|
|
// The new node does not have a parent.
|
|
s := &Store{NodeIndices: make(map[[32]byte]uint64)}
|
|
if err := s.insert(context.Background(), 100, [32]byte{'A'}, [32]byte{'B'}, [32]byte{}, 1, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(s.Nodes) != 1 {
|
|
t.Error("Did not insert block")
|
|
}
|
|
if len(s.NodeIndices) != 1 {
|
|
t.Error("Did not insert block")
|
|
}
|
|
if s.Nodes[0].Parent != NonExistentNode {
|
|
t.Error("Incorrect parent")
|
|
}
|
|
if s.Nodes[0].JustifiedEpoch != 1 {
|
|
t.Error("Incorrect justification")
|
|
}
|
|
if s.Nodes[0].FinalizedEpoch != 1 {
|
|
t.Error("Incorrect finalization")
|
|
}
|
|
if s.Nodes[0].Root != [32]byte{'A'} {
|
|
t.Error("Incorrect root")
|
|
}
|
|
}
|
|
|
|
func TestStore_Insert_KnownParent(t *testing.T) {
|
|
// Similar to UnknownParent test, but this time the new node has a valid parent already in store.
|
|
// The new node builds on top of the parent.
|
|
s := &Store{NodeIndices: make(map[[32]byte]uint64)}
|
|
s.Nodes = []*Node{{}}
|
|
p := [32]byte{'B'}
|
|
s.NodeIndices[p] = 0
|
|
if err := s.insert(context.Background(), 100, [32]byte{'A'}, p, [32]byte{}, 1, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(s.Nodes) != 2 {
|
|
t.Error("Did not insert block")
|
|
}
|
|
if len(s.NodeIndices) != 2 {
|
|
t.Error("Did not insert block")
|
|
}
|
|
if s.Nodes[1].Parent != 0 {
|
|
t.Error("Incorrect parent")
|
|
}
|
|
if s.Nodes[1].JustifiedEpoch != 1 {
|
|
t.Error("Incorrect justification")
|
|
}
|
|
if s.Nodes[1].FinalizedEpoch != 1 {
|
|
t.Error("Incorrect finalization")
|
|
}
|
|
if s.Nodes[1].Root != [32]byte{'A'} {
|
|
t.Error("Incorrect root")
|
|
}
|
|
}
|
|
|
|
func TestStore_ApplyScoreChanges_InvalidDeltaLength(t *testing.T) {
|
|
s := &Store{}
|
|
|
|
// This will fail because node indices has length of 0, and delta list has a length of 1.
|
|
if err := s.applyWeightChanges(context.Background(), 0, 0, []int{1}); err.Error() != errInvalidDeltaLength.Error() {
|
|
t.Error("Did not get wanted error")
|
|
}
|
|
}
|
|
|
|
func TestStore_ApplyScoreChanges_UpdateEpochs(t *testing.T) {
|
|
s := &Store{}
|
|
|
|
// The justified and finalized epochs in Store should be updated to 1 and 1 given the following input.
|
|
if err := s.applyWeightChanges(context.Background(), 1, 1, []int{}); err != nil {
|
|
t.Error("Did not get wanted error")
|
|
}
|
|
|
|
if s.JustifiedEpoch != 1 {
|
|
t.Error("Did not update justified epoch")
|
|
}
|
|
if s.FinalizedEpoch != 1 {
|
|
t.Error("Did not update justified epoch")
|
|
}
|
|
}
|
|
|
|
func TestStore_ApplyScoreChanges_UpdateWeightsPositiveDelta(t *testing.T) {
|
|
// Construct 3 Nodes with weight 100 on each node. The 3 Nodes linked to each other.
|
|
s := &Store{Nodes: []*Node{
|
|
{Root: [32]byte{'A'}, Weight: 100},
|
|
{Root: [32]byte{'A'}, Weight: 100},
|
|
{Parent: 1, Root: [32]byte{'A'}, Weight: 100}}}
|
|
|
|
// Each node gets one unique vote. The weight should look like 103 <- 102 <- 101 because
|
|
// they get propagated back.
|
|
if err := s.applyWeightChanges(context.Background(), 0, 0, []int{1, 1, 1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if s.Nodes[0].Weight != 103 {
|
|
t.Error("Did not get correct weight")
|
|
}
|
|
if s.Nodes[1].Weight != 102 {
|
|
t.Error("Did not get correct weight")
|
|
}
|
|
if s.Nodes[2].Weight != 101 {
|
|
t.Error("Did not get correct weight")
|
|
}
|
|
}
|
|
|
|
func TestStore_ApplyScoreChanges_UpdateWeightsNegativeDelta(t *testing.T) {
|
|
// Construct 3 Nodes with weight 100 on each node. The 3 Nodes linked to each other.
|
|
s := &Store{Nodes: []*Node{
|
|
{Root: [32]byte{'A'}, Weight: 100},
|
|
{Root: [32]byte{'A'}, Weight: 100},
|
|
{Parent: 1, Root: [32]byte{'A'}, Weight: 100}}}
|
|
|
|
// Each node gets one unique vote which contributes to negative delta.
|
|
// The weight should look like 97 <- 98 <- 99 because they get propagated back.
|
|
if err := s.applyWeightChanges(context.Background(), 0, 0, []int{-1, -1, -1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if s.Nodes[0].Weight != 97 {
|
|
t.Error("Did not get correct weight")
|
|
}
|
|
if s.Nodes[1].Weight != 98 {
|
|
t.Error("Did not get correct weight")
|
|
}
|
|
if s.Nodes[2].Weight != 99 {
|
|
t.Error("Did not get correct weight")
|
|
}
|
|
}
|
|
|
|
func TestStore_ApplyScoreChanges_UpdateWeightsMixedDelta(t *testing.T) {
|
|
// Construct 3 Nodes with weight 100 on each node. The 3 Nodes linked to each other.
|
|
s := &Store{Nodes: []*Node{
|
|
{Root: [32]byte{'A'}, Weight: 100},
|
|
{Root: [32]byte{'A'}, Weight: 100},
|
|
{Parent: 1, Root: [32]byte{'A'}, Weight: 100}}}
|
|
|
|
// Each node gets one mixed vote. The weight should look like 100 <- 200 <- 250.
|
|
if err := s.applyWeightChanges(context.Background(), 0, 0, []int{-100, -50, 150}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if s.Nodes[0].Weight != 100 {
|
|
t.Error("Did not get correct weight")
|
|
}
|
|
if s.Nodes[1].Weight != 200 {
|
|
t.Error("Did not get correct weight")
|
|
}
|
|
if s.Nodes[2].Weight != 250 {
|
|
t.Error("Did not get correct weight")
|
|
}
|
|
}
|
|
|
|
func TestStore_UpdateBestChildAndDescendant_RemoveChild(t *testing.T) {
|
|
// Make parent's best child equal's to input child index and child is not viable.
|
|
s := &Store{Nodes: []*Node{{BestChild: 1}, {}}, JustifiedEpoch: 1, FinalizedEpoch: 1}
|
|
|
|
if err := s.updateBestChildAndDescendant(0, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify parent's best child and best descendant are `none`.
|
|
if s.Nodes[0].BestChild != NonExistentNode {
|
|
t.Error("Did not get correct best child index")
|
|
}
|
|
if s.Nodes[0].BestDescendent != NonExistentNode {
|
|
t.Error("Did not get correct best descendant index")
|
|
}
|
|
}
|
|
|
|
func TestStore_UpdateBestChildAndDescendant_UpdateDescendant(t *testing.T) {
|
|
// Make parent's best child equal to child index and child is viable.
|
|
s := &Store{Nodes: []*Node{{BestChild: 1}, {BestDescendent: NonExistentNode}}}
|
|
|
|
if err := s.updateBestChildAndDescendant(0, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify parent's best child is the same and best descendant is not set to child index.
|
|
if s.Nodes[0].BestChild != 1 {
|
|
t.Error("Did not get correct best child index")
|
|
}
|
|
if s.Nodes[0].BestDescendent != 1 {
|
|
t.Error("Did not get correct best descendant index")
|
|
}
|
|
}
|
|
|
|
func TestStore_UpdateBestChildAndDescendant_ChangeChildByViability(t *testing.T) {
|
|
// Make parent's best child not equal to child index, child leads to viable index and
|
|
// parents best child doesnt lead to viable index.
|
|
s := &Store{
|
|
JustifiedEpoch: 1,
|
|
FinalizedEpoch: 1,
|
|
Nodes: []*Node{{BestChild: 1, JustifiedEpoch: 1, FinalizedEpoch: 1},
|
|
{BestDescendent: NonExistentNode},
|
|
{BestDescendent: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1}}}
|
|
|
|
if err := s.updateBestChildAndDescendant(0, 2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify parent's best child and best descendant are set to child index.
|
|
if s.Nodes[0].BestChild != 2 {
|
|
t.Error("Did not get correct best child index")
|
|
}
|
|
if s.Nodes[0].BestDescendent != 2 {
|
|
t.Error("Did not get correct best descendant index")
|
|
}
|
|
}
|
|
|
|
func TestStore_UpdateBestChildAndDescendant_ChangeChildByWeight(t *testing.T) {
|
|
// Make parent's best child not equal to child index, child leads to viable index and
|
|
// parents best child leads to viable index but child has more weight than parent's best child.
|
|
s := &Store{
|
|
JustifiedEpoch: 1,
|
|
FinalizedEpoch: 1,
|
|
Nodes: []*Node{{BestChild: 1, JustifiedEpoch: 1, FinalizedEpoch: 1},
|
|
{BestDescendent: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1},
|
|
{BestDescendent: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1, Weight: 1}}}
|
|
|
|
if err := s.updateBestChildAndDescendant(0, 2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify parent's best child and best descendant are set to child index.
|
|
if s.Nodes[0].BestChild != 2 {
|
|
t.Error("Did not get correct best child index")
|
|
}
|
|
if s.Nodes[0].BestDescendent != 2 {
|
|
t.Error("Did not get correct best descendant index")
|
|
}
|
|
}
|
|
|
|
func TestStore_UpdateBestChildAndDescendant_ChangeChildAtLeaf(t *testing.T) {
|
|
// Make parent's best child to none and input child leads to viable index.
|
|
s := &Store{
|
|
JustifiedEpoch: 1,
|
|
FinalizedEpoch: 1,
|
|
Nodes: []*Node{{BestChild: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1},
|
|
{BestDescendent: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1},
|
|
{BestDescendent: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1}}}
|
|
|
|
if err := s.updateBestChildAndDescendant(0, 2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify parent's best child and best descendant are set to child index.
|
|
if s.Nodes[0].BestChild != 2 {
|
|
t.Error("Did not get correct best child index")
|
|
}
|
|
if s.Nodes[0].BestDescendent != 2 {
|
|
t.Error("Did not get correct best descendant index")
|
|
}
|
|
}
|
|
|
|
func TestStore_UpdateBestChildAndDescendant_NoChangeByViability(t *testing.T) {
|
|
// Make parent's best child not equal to child index, child leads to not viable index and
|
|
// parents best child leads to viable index.
|
|
s := &Store{
|
|
JustifiedEpoch: 1,
|
|
FinalizedEpoch: 1,
|
|
Nodes: []*Node{{BestChild: 1, JustifiedEpoch: 1, FinalizedEpoch: 1},
|
|
{BestDescendent: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1},
|
|
{BestDescendent: NonExistentNode}}}
|
|
|
|
if err := s.updateBestChildAndDescendant(0, 2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify parent's best child and best descendant are not changed.
|
|
if s.Nodes[0].BestChild != 1 {
|
|
t.Error("Did not get correct best child index")
|
|
}
|
|
if s.Nodes[0].BestDescendent != 0 {
|
|
t.Error("Did not get correct best descendant index")
|
|
}
|
|
}
|
|
|
|
func TestStore_UpdateBestChildAndDescendant_NoChangeByWeight(t *testing.T) {
|
|
// Make parent's best child not equal to child index, child leads to viable index and
|
|
// parents best child leads to viable index but parent's best child has more weight.
|
|
s := &Store{
|
|
JustifiedEpoch: 1,
|
|
FinalizedEpoch: 1,
|
|
Nodes: []*Node{{BestChild: 1, JustifiedEpoch: 1, FinalizedEpoch: 1},
|
|
{BestDescendent: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1, Weight: 1},
|
|
{BestDescendent: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1}}}
|
|
|
|
if err := s.updateBestChildAndDescendant(0, 2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify parent's best child and best descendant are not changed.
|
|
if s.Nodes[0].BestChild != 1 {
|
|
t.Error("Did not get correct best child index")
|
|
}
|
|
if s.Nodes[0].BestDescendent != 0 {
|
|
t.Error("Did not get correct best descendant index")
|
|
}
|
|
}
|
|
|
|
func TestStore_UpdateBestChildAndDescendant_NoChangeAtLeaf(t *testing.T) {
|
|
// Make parent's best child to none and input child does not lead to viable index.
|
|
s := &Store{
|
|
JustifiedEpoch: 1,
|
|
FinalizedEpoch: 1,
|
|
Nodes: []*Node{{BestChild: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1},
|
|
{BestDescendent: NonExistentNode, JustifiedEpoch: 1, FinalizedEpoch: 1},
|
|
{BestDescendent: NonExistentNode}}}
|
|
|
|
if err := s.updateBestChildAndDescendant(0, 2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify parent's best child and best descendant are not changed.
|
|
if s.Nodes[0].BestChild != NonExistentNode {
|
|
t.Error("Did not get correct best child index")
|
|
}
|
|
if s.Nodes[0].BestDescendent != 0 {
|
|
t.Error("Did not get correct best descendant index")
|
|
}
|
|
}
|
|
|
|
func TestStore_Prune_LessThanThreshold(t *testing.T) {
|
|
// Define 100 Nodes in store.
|
|
numOfNodes := 100
|
|
indices := make(map[[32]byte]uint64)
|
|
nodes := make([]*Node, 0)
|
|
for i := 0; i < numOfNodes; i++ {
|
|
indices[indexToHash(uint64(i))] = uint64(i)
|
|
nodes = append(nodes, &Node{Slot: uint64(i)})
|
|
}
|
|
|
|
s := &Store{Nodes: nodes, NodeIndices: indices, PruneThreshold: 100}
|
|
|
|
// Finalized root is at index 99 so everything before 99 should be pruned,
|
|
// but PruneThreshold is at 100 so nothing will be pruned.
|
|
if err := s.prune(context.Background(), indexToHash(99)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(s.Nodes) != 100 {
|
|
t.Fatal("Incorrect Nodes count")
|
|
}
|
|
if len(s.NodeIndices) != 100 {
|
|
t.Fatal("Incorrect node indices count")
|
|
}
|
|
}
|
|
|
|
func TestStore_Prune_MoreThanThreshold(t *testing.T) {
|
|
// Define 100 Nodes in store.
|
|
numOfNodes := 100
|
|
indices := make(map[[32]byte]uint64)
|
|
nodes := make([]*Node, 0)
|
|
for i := 0; i < numOfNodes; i++ {
|
|
indices[indexToHash(uint64(i))] = uint64(i)
|
|
nodes = append(nodes, &Node{Slot: uint64(i), Root: indexToHash(uint64(i)),
|
|
BestDescendent: NonExistentNode, BestChild: NonExistentNode})
|
|
}
|
|
|
|
s := &Store{Nodes: nodes, NodeIndices: indices}
|
|
|
|
// Finalized root is at index 99 so everything before 99 should be pruned.
|
|
if err := s.prune(context.Background(), indexToHash(99)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(s.Nodes) != 1 {
|
|
t.Error("Incorrect Nodes count")
|
|
}
|
|
if len(s.NodeIndices) != 1 {
|
|
t.Error("Incorrect node indices count")
|
|
}
|
|
}
|
|
|
|
func TestStore_Prune_MoreThanOnce(t *testing.T) {
|
|
// Define 100 Nodes in store.
|
|
numOfNodes := 100
|
|
indices := make(map[[32]byte]uint64)
|
|
nodes := make([]*Node, 0)
|
|
for i := 0; i < numOfNodes; i++ {
|
|
indices[indexToHash(uint64(i))] = uint64(i)
|
|
nodes = append(nodes, &Node{Slot: uint64(i), Root: indexToHash(uint64(i)),
|
|
BestDescendent: NonExistentNode, BestChild: NonExistentNode})
|
|
}
|
|
|
|
s := &Store{Nodes: nodes, NodeIndices: indices}
|
|
|
|
// Finalized root is at index 11 so everything before 11 should be pruned.
|
|
if err := s.prune(context.Background(), indexToHash(10)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(s.Nodes) != 90 {
|
|
t.Error("Incorrect Nodes count")
|
|
}
|
|
if len(s.NodeIndices) != 90 {
|
|
t.Error("Incorrect node indices count")
|
|
}
|
|
|
|
// One more time.
|
|
if err := s.prune(context.Background(), indexToHash(20)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(s.Nodes) != 80 {
|
|
t.Log(len(s.Nodes))
|
|
t.Error("Incorrect Nodes count")
|
|
}
|
|
if len(s.NodeIndices) != 80 {
|
|
t.Error("Incorrect node indices count")
|
|
}
|
|
}
|
|
func TestStore_LeadsToViableHead(t *testing.T) {
|
|
tests := []struct {
|
|
n *Node
|
|
justifiedEpoch uint64
|
|
finalizedEpoch uint64
|
|
want bool
|
|
}{
|
|
{&Node{}, 0, 0, true},
|
|
{&Node{}, 1, 0, false},
|
|
{&Node{}, 0, 1, false},
|
|
{&Node{FinalizedEpoch: 1, JustifiedEpoch: 1}, 1, 1, true},
|
|
{&Node{FinalizedEpoch: 1, JustifiedEpoch: 1}, 2, 2, false},
|
|
{&Node{FinalizedEpoch: 3, JustifiedEpoch: 4}, 4, 3, true},
|
|
}
|
|
for _, tc := range tests {
|
|
s := &Store{
|
|
JustifiedEpoch: tc.justifiedEpoch,
|
|
FinalizedEpoch: tc.finalizedEpoch,
|
|
Nodes: []*Node{tc.n},
|
|
}
|
|
got, err := s.leadsToViableHead(tc.n)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != tc.want {
|
|
t.Errorf("viableForHead() = %v, want %v", got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStore_ViableForHead(t *testing.T) {
|
|
tests := []struct {
|
|
n *Node
|
|
justifiedEpoch uint64
|
|
finalizedEpoch uint64
|
|
want bool
|
|
}{
|
|
{&Node{}, 0, 0, true},
|
|
{&Node{}, 1, 0, false},
|
|
{&Node{}, 0, 1, false},
|
|
{&Node{FinalizedEpoch: 1, JustifiedEpoch: 1}, 1, 1, true},
|
|
{&Node{FinalizedEpoch: 1, JustifiedEpoch: 1}, 2, 2, false},
|
|
{&Node{FinalizedEpoch: 3, JustifiedEpoch: 4}, 4, 3, true},
|
|
}
|
|
for _, tc := range tests {
|
|
s := &Store{
|
|
JustifiedEpoch: tc.justifiedEpoch,
|
|
FinalizedEpoch: tc.finalizedEpoch,
|
|
}
|
|
if got := s.viableForHead(tc.n); got != tc.want {
|
|
t.Errorf("viableForHead() = %v, want %v", got, tc.want)
|
|
}
|
|
}
|
|
}
|