~whereswaldon/forest-go

ref: 972f2afc043771da05b4e13bd7386f39f7f5e125 forest-go/store/store_test.go -rw-r--r-- 8.5 KiB
972f2afcChris Waldon fix: ensure archive DescendantsOf does not return the root 3 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package store_test

import (
	"testing"

	forest "git.sr.ht/~whereswaldon/forest-go"
	"git.sr.ht/~whereswaldon/forest-go/fields"
	"git.sr.ht/~whereswaldon/forest-go/store"
	"git.sr.ht/~whereswaldon/forest-go/testutil"
)

func TestMemoryStore(t *testing.T) {
	s := store.NewMemoryStore()
	testStandardStoreInterface(t, s, "MemoryStore")
}

func testStandardStoreInterface(t *testing.T, s forest.Store, storeImplName string) {
	// create three test nodes, one of each type
	identity, _, community, reply := testutil.MakeReplyOrSkip(t)
	nodes := []forest.Node{identity, community, reply}

	// create a set of functions that perform different "Get" operations on nodes
	getFuncs := map[string]func(*fields.QualifiedHash) (forest.Node, bool, error){
		"get":       s.Get,
		"identity":  s.GetIdentity,
		"community": s.GetCommunity,
		"conversation": func(id *fields.QualifiedHash) (forest.Node, bool, error) {
			return s.GetConversation(community.ID(), id)
		},
		"reply": func(id *fields.QualifiedHash) (forest.Node, bool, error) {
			return s.GetReply(community.ID(), reply.ID(), id)
		},
	}

	// ensure no getter functions succeed on an empty store
	for _, i := range nodes {
		for _, get := range getFuncs {
			if node, has, err := get(i.ID()); has {
				t.Errorf("Empty %s should not contain element %v", storeImplName, i.ID())
			} else if err != nil {
				t.Errorf("Empty %s Get() should not err with %s", storeImplName, err)
			} else if node != nil {
				t.Errorf("Empty %s Get() should return none-nil node %v", storeImplName, node)
			}
		}
	}

	// add each node
	for _, i := range nodes {
		if err := s.Add(i); err != nil {
			t.Errorf("%s Add() should not err on Add(): %s", storeImplName, err)
		}
	}

	// map each node to the getters that should be successful in fetching it
	nodesToGetters := []struct {
		forest.Node
		funcs []string
	}{
		{identity, []string{"get", "identity"}},
		{community, []string{"get", "community"}},
		{reply, []string{"get", "conversation", "reply"}},
	}

	// ensure all getters work for each node
	for _, getterConfig := range nodesToGetters {
		currentNode := getterConfig.Node
		for _, getterName := range getterConfig.funcs {
			if node, has, err := getFuncs[getterName](currentNode.ID()); !has {
				t.Errorf("%s should contain element %v", storeImplName, currentNode.ID())
			} else if err != nil {
				t.Errorf("%s Has() should not err with %s", storeImplName, err)
			} else if !currentNode.Equals(node) {
				t.Errorf("%s Get() should return a node equal to the one that was Add()ed. Got %v, expected %v", storeImplName, node, currentNode)
			}
		}
	}

	// map nodes to the children that they ought to have within the store
	nodesToChildren := []struct {
		forest.Node
		children []*fields.QualifiedHash
	}{
		{identity, []*fields.QualifiedHash{}},
		{community, []*fields.QualifiedHash{reply.ID()}},
		{reply, []*fields.QualifiedHash{}},
	}

	// check each node has its proper children
	for _, childConfig := range nodesToChildren {
		if children, err := s.Children(childConfig.ID()); err != nil {
			t.Errorf("%s should not error fetching children of %v", storeImplName, childConfig.ID())
		} else {
			for _, child := range childConfig.children {
				if !containsID(children, child) {
					t.Errorf("%s should have %v as a child of %v", storeImplName, child, childConfig.ID())
				}
			}
		}
	}

	// add some more nodes so that we can test the Recent method
	identity2, _, community2, reply2 := testutil.MakeReplyOrSkip(t)
	for _, i := range []forest.Node{identity2, community2, reply2} {
		if err := s.Add(i); err != nil {
			t.Errorf("%s Add() should not err on Add(): %s", storeImplName, err)
		}
	}
	// try recent on each node type and ensure that it returns the right
	// number and order of results
	type recentRun struct {
		fields.NodeType
		atZero forest.Node
		atOne  forest.Node
	}
	for _, run := range []recentRun{
		{fields.NodeTypeIdentity, identity2, identity},
		{fields.NodeTypeCommunity, community2, community},
		{fields.NodeTypeReply, reply2, reply},
	} {
		recentNodes, err := s.Recent(run.NodeType, 1)
		if err != nil {
			t.Errorf("Recent failed on valid input: %v", err)
		} else if len(recentNodes) < 1 {
			t.Errorf("Recent on store with data returned too few results")
		} else if !recentNodes[0].Equals(run.atZero) {
			t.Errorf("Expected most recent node to be the newly created one")
		}
		recentNodes, err = s.Recent(run.NodeType, 2)
		if err != nil {
			t.Errorf("Recent failed on valid input: %v", err)
		} else if len(recentNodes) < 2 {
			t.Errorf("Recent on store with data returned too few results")
		} else if !recentNodes[0].Equals(run.atZero) {
			t.Errorf("Expected most recent node to be the newly created one")
		} else if !recentNodes[1].Equals(run.atOne) {
			t.Errorf("Expected first node to be the older one")
		}
		recentNodes, err = s.Recent(run.NodeType, 3)
		if err != nil {
			t.Errorf("Recent failed on valid input: %v", err)
		} else if len(recentNodes) > 2 {
			t.Errorf("Recent on store with only two matching nodes returned more than 2 results")
		}
	}
}

func containsID(ids []*fields.QualifiedHash, id *fields.QualifiedHash) bool {
	for _, current := range ids {
		if current.Equals(id) {
			return true
		}
	}
	return false
}

func TestCacheStore(t *testing.T) {
	s1 := store.NewMemoryStore()
	s2 := store.NewMemoryStore()
	c, err := store.NewCacheStore(s1, s2)
	if err != nil {
		t.Errorf("Unexpected error constructing CacheStore: %v", err)
	}
	testStandardStoreInterface(t, c, "CacheStore")
}

func TestCacheStoreDownPropagation(t *testing.T) {
	s1 := store.NewMemoryStore()
	id, _, com, rep := testutil.MakeReplyOrSkip(t)
	nodes := []forest.Node{id, com, rep}
	subrange := nodes[:len(nodes)-1]
	for _, node := range subrange {
		if err := s1.Add(node); err != nil {
			t.Skipf("Failed adding %v to %v", node, s1)
		}
	}
	s2 := store.NewMemoryStore()
	if _, err := store.NewCacheStore(s1, s2); err != nil {
		t.Errorf("Unexpected error when constructing CacheStore: %v", err)
	}

	for _, node := range subrange {
		if n2, has, err := s2.Get(node.ID()); err != nil {
			t.Errorf("Unexpected error getting node from cache base layer: %s", err)
		} else if !has {
			t.Errorf("Expected cache base layer to contain %v", node.ID())
		} else if !n2.Equals(node) {
			t.Errorf("Expected cache base layer to contain the same value for ID %v", node.ID())
		}
	}
}

func TestCacheStoreUpPropagation(t *testing.T) {
	base := store.NewMemoryStore()
	id, _, com, rep := testutil.MakeReplyOrSkip(t)
	nodes := []forest.Node{id, com, rep}
	subrange := nodes[:len(nodes)-1]
	for _, node := range subrange {
		if err := base.Add(node); err != nil {
			t.Skipf("Failed adding %v to %v", node, base)
		}
	}
	cache := store.NewMemoryStore()
	combined, err := store.NewCacheStore(cache, base)
	if err != nil {
		t.Errorf("Unexpected error when constructing CacheStore: %v", err)
	}

	for _, node := range subrange {
		if _, has, err := cache.Get(node.ID()); err != nil {
			t.Errorf("Unexpected error getting node from cache layer: %s", err)
		} else if has {
			t.Errorf("Expected cache layer not to contain %v", node.ID())
		}
		if n2, has, err := combined.Get(node.ID()); err != nil {
			t.Errorf("Unexpected error getting node from cache store: %s", err)
		} else if !has {
			t.Errorf("Expected cache store to contain %v", node.ID())
		} else if !n2.Equals(node) {
			t.Errorf("Expected cache store to contain the same value for ID %v", node.ID())
		}
		if n2, has, err := cache.Get(node.ID()); err != nil {
			t.Errorf("Unexpected error getting node from cache layer: %s", err)
		} else if !has {
			t.Errorf("Expected cache layer to contain %v after warming cache", node.ID())
		} else if !n2.Equals(node) {
			t.Errorf("Expected cache layer to contain the same value for ID %v after warming cache", node.ID())
		}
	}
}

func TestArchiveDescendantsOf(t *testing.T) {
	// create three test nodes, one of each type
	identity, _, community, reply := testutil.MakeReplyOrSkip(t)
	nodes := []forest.Node{identity, community, reply}

	m := store.NewMemoryStore()
	for _, n := range nodes {
		m.Add(n)
	}

	a := store.NewArchive(m)

	try := func(t *testing.T, id *fields.QualifiedHash, expectedLen int) {
		descendants, err := a.DescendantsOf(id)
		if err != nil {
			t.Fatalf("should not fail to return descendants: %v", err)
		}
		for _, d := range descendants {
			if d.Equals(id) {
				t.Fatalf("%s should not be returned as its own descendant", id)
			}
		}
		if len(descendants) != expectedLen {
			t.Fatalf("expected %d descendants, got %d", expectedLen, len(descendants))
		}
	}
	try(t, reply.ID(), 0)
	try(t, community.ID(), 1)
	try(t, identity.ID(), 0)
}