1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17
18
19
20 package indextest
21
22 import (
23 "bytes"
24 "context"
25 "errors"
26 "fmt"
27 "log"
28 "net/url"
29 "os"
30 "path/filepath"
31 "reflect"
32 "strings"
33 "testing"
34 "time"
35
36 "perkeep.org/internal/osutil"
37 "perkeep.org/pkg/blob"
38 "perkeep.org/pkg/index"
39 "perkeep.org/pkg/jsonsign"
40 "perkeep.org/pkg/schema"
41 "perkeep.org/pkg/sorted"
42 "perkeep.org/pkg/test"
43 "perkeep.org/pkg/types/camtypes"
44 )
45
46 var ctxbg = context.Background()
47
48
49 type IndexDeps struct {
50 Index *index.Index
51
52 BlobSource *test.Fetcher
53
54
55 PublicKeyFetcher *test.Fetcher
56 EntityFetcher jsonsign.EntityFetcher
57 SignerBlobRef blob.Ref
58
59 now time.Time
60
61 Fataler
62 }
63
64 type Fataler interface {
65 Fatalf(format string, args ...interface{})
66 }
67
68 type logFataler struct{}
69
70 func (logFataler) Fatalf(format string, args ...interface{}) {
71 log.Fatalf(format, args...)
72 }
73
74 func (id *IndexDeps) Get(key string) string {
75 v, _ := id.Index.Storage().Get(key)
76 return v
77 }
78
79 func (id *IndexDeps) Set(key, value string) error {
80 return id.Index.Storage().Set(key, value)
81 }
82
83 func (id *IndexDeps) DumpIndex(t *testing.T) {
84 t.Logf("Begin index dump:")
85 it := id.Index.Storage().Find("", "")
86 for it.Next() {
87 t.Logf(" %q = %q", it.Key(), it.Value())
88 }
89 if err := it.Close(); err != nil {
90 t.Fatalf("iterator close = %v", err)
91 }
92 t.Logf("End index dump.")
93 }
94
95 func (id *IndexDeps) Sign(m *schema.Builder) *test.Blob {
96 m.SetSigner(id.SignerBlobRef)
97 unsigned, err := m.JSON()
98 if err != nil {
99 id.Fatalf("uploadAndSignMap: " + err.Error())
100 }
101 sr := &jsonsign.SignRequest{
102 UnsignedJSON: unsigned,
103 Fetcher: id.PublicKeyFetcher,
104 EntityFetcher: id.EntityFetcher,
105 SignatureTime: id.now,
106 }
107 signed, err := sr.Sign(ctxbg)
108 if err != nil {
109 id.Fatalf("problem signing: " + err.Error())
110 }
111 tb := &test.Blob{Contents: signed}
112 return tb
113 }
114
115 func (id *IndexDeps) Upload(tb *test.Blob) blob.Ref {
116 _, err := id.BlobSource.ReceiveBlob(ctxbg, tb.BlobRef(), tb.Reader())
117 if err != nil {
118 id.Fatalf("public uploading signed blob to blob source, pre-indexing: %v, %v", tb.BlobRef(), err)
119 }
120 _, err = id.Index.ReceiveBlob(ctxbg, tb.BlobRef(), tb.Reader())
121 if err != nil {
122 id.Fatalf("problem indexing blob: %v\nblob was:\n%s", err, tb.Contents)
123 }
124 return tb.BlobRef()
125 }
126
127 func (id *IndexDeps) uploadAndSign(m *schema.Builder) blob.Ref {
128 return id.Upload(id.Sign(m))
129 }
130
131
132
133 func (id *IndexDeps) NewPermanode() blob.Ref {
134 unsigned := schema.NewUnsignedPermanode()
135 return id.uploadAndSign(unsigned)
136 }
137
138
139
140 func (id *IndexDeps) NewPlannedPermanode(key string) blob.Ref {
141 unsigned := schema.NewPlannedPermanode(key)
142 return id.uploadAndSign(unsigned)
143 }
144
145 func (id *IndexDeps) advanceTime() time.Time {
146 id.now = id.now.Add(1 * time.Second)
147 return id.now
148 }
149
150
151 func (id *IndexDeps) LastTime() time.Time {
152 return id.now
153 }
154
155 func (id *IndexDeps) SetAttribute(permaNode blob.Ref, attr, value string) blob.Ref {
156 m := schema.NewSetAttributeClaim(permaNode, attr, value)
157 m.SetClaimDate(id.advanceTime())
158 return id.uploadAndSign(m)
159 }
160
161 func (id *IndexDeps) SetAttribute_NoTimeMove(permaNode blob.Ref, attr, value string) blob.Ref {
162 m := schema.NewSetAttributeClaim(permaNode, attr, value)
163 m.SetClaimDate(id.LastTime())
164 return id.uploadAndSign(m)
165 }
166
167 func (id *IndexDeps) AddAttribute(permaNode blob.Ref, attr, value string) blob.Ref {
168 m := schema.NewAddAttributeClaim(permaNode, attr, value)
169 m.SetClaimDate(id.advanceTime())
170 return id.uploadAndSign(m)
171 }
172
173 func (id *IndexDeps) DelAttribute(permaNode blob.Ref, attr, value string) blob.Ref {
174 m := schema.NewDelAttributeClaim(permaNode, attr, value)
175 m.SetClaimDate(id.advanceTime())
176 return id.uploadAndSign(m)
177 }
178
179 func (id *IndexDeps) Delete(target blob.Ref) blob.Ref {
180 m := schema.NewDeleteClaim(target)
181 m.SetClaimDate(id.advanceTime())
182 return id.uploadAndSign(m)
183 }
184
185 var noTime = time.Time{}
186
187 func (id *IndexDeps) UploadString(v string) blob.Ref {
188 cb := &test.Blob{Contents: v}
189 id.BlobSource.AddBlob(cb)
190 br := cb.BlobRef()
191 _, err := id.Index.ReceiveBlob(ctxbg, br, cb.Reader())
192 if err != nil {
193 id.Fatalf("UploadString: %v", err)
194 }
195 return br
196 }
197
198
199 func (id *IndexDeps) UploadFile(fileName string, contents string, modTime time.Time) (fileRef, wholeRef blob.Ref) {
200 wholeRef = id.UploadString(contents)
201
202 m := schema.NewFileMap(fileName)
203 m.PopulateParts(int64(len(contents)), []schema.BytesPart{
204 {
205 Size: uint64(len(contents)),
206 BlobRef: wholeRef,
207 }})
208 if !modTime.IsZero() {
209 m.SetModTime(modTime)
210 }
211 fjson, err := m.JSON()
212 if err != nil {
213 id.Fatalf("UploadFile.JSON: %v", err)
214 }
215 fb := &test.Blob{Contents: fjson}
216 id.BlobSource.AddBlob(fb)
217 fileRef = fb.BlobRef()
218 _, err = id.Index.ReceiveBlob(ctxbg, fileRef, fb.Reader())
219 if err != nil {
220 panic(err)
221 }
222 return
223 }
224
225
226 func (id *IndexDeps) UploadDir(dirName string, children []blob.Ref, modTime time.Time) blob.Ref {
227
228 ss := schema.NewStaticSet()
229 ss.SetStaticSetMembers(children)
230 ssjson := ss.Blob().JSON()
231 ssb := &test.Blob{Contents: ssjson}
232 id.BlobSource.AddBlob(ssb)
233 _, err := id.Index.ReceiveBlob(ctxbg, ssb.BlobRef(), ssb.Reader())
234 if err != nil {
235 id.Fatalf("UploadDir.ReceiveBlob: %v", err)
236 }
237
238
239 bb := schema.NewDirMap(dirName)
240 bb.PopulateDirectoryMap(ssb.BlobRef())
241 if !modTime.IsZero() {
242 bb.SetModTime(modTime)
243 }
244 dirjson, err := bb.JSON()
245 if err != nil {
246 id.Fatalf("UploadDir.JSON: %v", err)
247 }
248 dirb := &test.Blob{Contents: dirjson}
249 id.BlobSource.AddBlob(dirb)
250 _, err = id.Index.ReceiveBlob(ctxbg, dirb.BlobRef(), dirb.Reader())
251 if err != nil {
252 id.Fatalf("UploadDir.ReceiveBlob: %v", err)
253 }
254 return dirb.BlobRef()
255 }
256
257 var (
258 PubKey = &test.Blob{Contents: `-----BEGIN PGP PUBLIC KEY BLOCK-----
259
260 xsBNBEzgoVsBCAC/56aEJ9BNIGV9FVP+WzenTAkg12k86YqlwJVAB/VwdMlyXxvi
261 bCT1RVRfnYxscs14LLfcMWF3zMucw16mLlJCBSLvbZ0jn4h+/8vK5WuAdjw2YzLs
262 WtBcjWn3lV6tb4RJz5gtD/o1w8VWxwAnAVIWZntKAWmkcChCRgdUeWso76+plxE5
263 aRYBJqdT1mctGqNEISd/WYPMgwnWXQsVi3x4z1dYu2tD9uO1dkAff12z1kyZQIBQ
264 rexKYRRRh9IKAayD4kgS0wdlULjBU98aeEaMz1ckuB46DX3lAYqmmTEL/Rl9cOI0
265 Enpn/oOOfYFa5h0AFndZd1blMvruXfdAobjVABEBAAE=
266 =28/7
267 -----END PGP PUBLIC KEY BLOCK-----`}
268 KeyID = "2931A67C26F5ABDA"
269 )
270
271
272
273 func NewIndexDeps(index *index.Index) *IndexDeps {
274 camliRootPath, err := osutil.GoPackagePath("perkeep.org")
275 if err != nil {
276 log.Fatal("Package perkeep.org not found in $GOPATH or $GOPATH not defined")
277 }
278 secretRingFile := filepath.Join(camliRootPath, "pkg", "jsonsign", "testdata", "test-secring.gpg")
279
280 id := &IndexDeps{
281 Index: index,
282 BlobSource: new(test.Fetcher),
283 PublicKeyFetcher: new(test.Fetcher),
284 EntityFetcher: &jsonsign.CachingEntityFetcher{
285 Fetcher: &jsonsign.FileEntityFetcher{File: secretRingFile},
286 },
287 SignerBlobRef: PubKey.BlobRef(),
288 now: test.ClockOrigin,
289 Fataler: logFataler{},
290 }
291
292
293 if g, w := id.SignerBlobRef.String(), "sha224-a794846212ff67acdd00c6b90eee492baf674d41da8a621d2e8042dd"; g != w {
294 id.Fatalf("unexpected signer blobref; got signer = %q; want %q", g, w)
295 }
296 id.PublicKeyFetcher.AddBlob(PubKey)
297 id.Index.KeyFetcher = id.PublicKeyFetcher
298 id.Index.InitBlobSource(id.BlobSource)
299 return id
300 }
301
302 func Index(t *testing.T, initIdx func() *index.Index) {
303 ctx := context.Background()
304 oldLocal := time.Local
305 time.Local = time.UTC
306 defer func() { time.Local = oldLocal }()
307
308 id := NewIndexDeps(initIdx())
309 id.Fataler = t
310 defer id.DumpIndex(t)
311 pn := id.NewPermanode()
312 t.Logf("uploaded permanode %q", pn)
313 br1 := id.SetAttribute(pn, "tag", "foo1")
314 br1Time := id.LastTime()
315 t.Logf("set attribute %q", br1)
316 br2 := id.SetAttribute(pn, "tag", "foo2")
317 br2Time := id.LastTime()
318 t.Logf("set attribute %q", br2)
319 rootClaim := id.SetAttribute(pn, "camliRoot", "rootval")
320 rootClaimTime := id.LastTime()
321 t.Logf("set attribute %q", rootClaim)
322
323 pnChild := id.NewPermanode()
324 id.SetAttribute(pnChild, "unindexed", "lost in time and space")
325 br3 := id.SetAttribute(pnChild, "tag", "bar")
326 br3Time := id.LastTime()
327 t.Logf("set attribute %q", br3)
328 memberRef := id.AddAttribute(pn, "camliMember", pnChild.String())
329 t.Logf("add-attribute claim %q points to member permanode %q", memberRef, pnChild)
330 memberRefTime := id.LastTime()
331
332
333 if false {
334 camliRootPath, err := osutil.GoPackagePath("perkeep.org")
335 if err != nil {
336 t.Fatal("Package perkeep.org not found in $GOPATH or $GOPATH not defined")
337 }
338 for i := 1; i <= 8; i++ {
339 fileBase := fmt.Sprintf("f%d-exif.jpg", i)
340 fileName := filepath.Join(camliRootPath, "pkg", "images", "testdata", fileBase)
341 contents, err := os.ReadFile(fileName)
342 if err != nil {
343 t.Fatal(err)
344 }
345 id.UploadFile(fileBase, string(contents), noTime)
346 }
347 }
348
349
350 var jpegFileRef, exifFileRef, exifWholeRef, badExifWholeRef, nanExifWholeRef, mediaFileRef, mediaWholeRef, heicEXIFWholeRef blob.Ref
351 {
352 camliRootPath, err := osutil.GoPackagePath("perkeep.org")
353 if err != nil {
354 t.Fatal("Package perkeep.org not found in $GOPATH or $GOPATH not defined")
355 }
356 uploadFile := func(file string, modTime time.Time) (fileRef, wholeRef blob.Ref) {
357 fileName := filepath.Join(camliRootPath, "pkg", "index", "indextest", "testdata", file)
358 contents, err := os.ReadFile(fileName)
359 if err != nil {
360 t.Fatal(err)
361 }
362 fileRef, wholeRef = id.UploadFile(file, string(contents), modTime)
363 return
364 }
365 jpegFileRef, _ = uploadFile("dude.jpg", noTime)
366 exifFileRef, exifWholeRef = uploadFile("dude-exif.jpg", time.Unix(1361248796, 0))
367 _, badExifWholeRef = uploadFile("bad-exif.jpg", time.Unix(1361248796, 0))
368 _, nanExifWholeRef = uploadFile("nan-exif.jpg", time.Unix(1361248796, 0))
369 mediaFileRef, mediaWholeRef = uploadFile("0s.mp3", noTime)
370 _, heicEXIFWholeRef = uploadFile("black-seattle-truncated.heic", time.Unix(1361248796, 0))
371 }
372
373
374 imagesDirRef := id.UploadDir(
375 "testdata",
376 []blob.Ref{jpegFileRef, exifFileRef, mediaFileRef},
377 time.Now(),
378 )
379
380 lastPermanodeMutation := id.LastTime()
381
382 key := "signerkeyid:sha224-a794846212ff67acdd00c6b90eee492baf674d41da8a621d2e8042dd"
383 if g, e := id.Get(key), "2931A67C26F5ABDA"; g != e {
384 t.Fatalf("%q = %q, want %q", key, g, e)
385 }
386
387 key = "imagesize|" + jpegFileRef.String()
388 if g, e := id.Get(key), "50|100"; g != e {
389 t.Errorf("JPEG dude.jpg key %q = %q; want %q", key, g, e)
390 }
391
392 key = "filetimes|" + jpegFileRef.String()
393 if g, e := id.Get(key), ""; g != e {
394 t.Errorf("JPEG dude.jpg key %q = %q; want %q", key, g, e)
395 }
396
397 key = "filetimes|" + exifFileRef.String()
398 if g, e := id.Get(key), "2013-02-18T01%3A11%3A20Z%2C2013-02-19T04%3A39%3A56Z"; g != e {
399 t.Errorf("EXIF dude-exif.jpg key %q = %q; want %q", key, g, e)
400 }
401
402 key = "exifgps|" + exifWholeRef.String()
403
404 if g, e := id.Get(key), "-0.0000010|-120.0000000"; g != e {
405 t.Errorf("EXIF dude-exif.jpg key %q = %q; want %q", key, g, e)
406 }
407
408
409 key = "exifgps|" + badExifWholeRef.String()
410 if g, e := id.Get(key), ""; g != e {
411 t.Errorf("EXIF bad-exif.jpg key %q = %q; want %q", key, g, e)
412 }
413
414
415 key = "exifgps|" + nanExifWholeRef.String()
416 if g, e := id.Get(key), ""; g != e {
417 t.Errorf("EXIF nan-exif.jpg key %q = %q; want %q", key, g, e)
418 }
419
420
421 key = "exifgps|" + heicEXIFWholeRef.String()
422 if g, e := id.Get(key), "47.6496056|-122.3512806"; g != e {
423 t.Errorf("EXIF black-seattle-truncated.heic key %q = %q; want %q", key, g, e)
424 }
425
426 key = "have:" + pn.String()
427 pnSizeStr := strings.TrimSuffix(id.Get(key), "|indexed")
428 if pnSizeStr == "" {
429 t.Fatalf("missing key %q", key)
430 }
431
432 key = "meta:" + pn.String()
433 if g, e := id.Get(key), pnSizeStr+"|application/json; camliType=permanode"; g != e {
434 t.Errorf("key %q = %q, want %q", key, g, e)
435 }
436
437 key = "recpn|2931A67C26F5ABDA|rt7988-88-71T98:67:62.999876543Z|" + br1.String()
438 if g, e := id.Get(key), pn.String(); g != e {
439 t.Fatalf("%q = %q, want %q (permanode)", key, g, e)
440 }
441
442 key = "recpn|2931A67C26F5ABDA|rt7988-88-71T98:67:61.999876543Z|" + br2.String()
443 if g, e := id.Get(key), pn.String(); g != e {
444 t.Fatalf("%q = %q, want %q (permanode)", key, g, e)
445 }
446
447 key = fmt.Sprintf("edgeback|%s|%s|%s", pnChild, pn, memberRef)
448 if g, e := id.Get(key), "permanode|"; g != e {
449 t.Fatalf("edgeback row %q = %q, want %q", key, g, e)
450 }
451
452 mediaTests := []struct {
453 prop, exp string
454 }{
455 {"title", "Zero Seconds"},
456 {"artist", "Test Artist"},
457 {"album", "Test Album"},
458 {"genre", "(20)Alternative"},
459 {"musicbrainzalbumid", "00000000-0000-0000-0000-000000000000"},
460 {"year", "1992"},
461 {"track", "1"},
462 {"disc", "2"},
463 {"mediaref", "sha224-c4ebd5b557419a68ba6e0af716deeb9196e71e0dca65a7f805e3f723"},
464 {"durationms", "26"},
465 }
466 for _, tt := range mediaTests {
467 key = fmt.Sprintf("mediatag|%s|%s", mediaWholeRef.String(), tt.prop)
468 if g, _ := url.QueryUnescape(id.Get(key)); g != tt.exp {
469 t.Errorf("0s.mp3 key %q = %q; want %q", key, g, tt.exp)
470 }
471 }
472
473
474 {
475 gotPN, err := id.Index.PermanodeOfSignerAttrValue(ctx, id.SignerBlobRef, "camliRoot", "rootval")
476 if err != nil {
477 t.Fatalf("id.Index.PermanodeOfSignerAttrValue = %v", err)
478 }
479 if gotPN.String() != pn.String() {
480 t.Errorf("id.Index.PermanodeOfSignerAttrValue = %q, want %q", gotPN, pn)
481 }
482 _, err = id.Index.PermanodeOfSignerAttrValue(ctx, id.SignerBlobRef, "camliRoot", "MISSING")
483 if err == nil {
484 t.Errorf("expected an error from PermanodeOfSignerAttrValue on missing value")
485 }
486 }
487
488
489 {
490 ch := make(chan blob.Ref, 10)
491 req := &camtypes.PermanodeByAttrRequest{
492 Signer: id.SignerBlobRef,
493 Attribute: "tag",
494 Query: "foo1",
495 }
496 err := id.Index.SearchPermanodesWithAttr(ctx, ch, req)
497 if err != nil {
498 t.Fatalf("SearchPermanodesWithAttr = %v", err)
499 }
500 var got []blob.Ref
501 for r := range ch {
502 got = append(got, r)
503 }
504 want := []blob.Ref{pn}
505 if len(got) < 1 || got[0].String() != want[0].String() {
506 t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want)
507 }
508 }
509
510
511 {
512 ch := make(chan blob.Ref, 10)
513 req := &camtypes.PermanodeByAttrRequest{
514 Signer: id.SignerBlobRef,
515 Attribute: "tag",
516 }
517 err := id.Index.SearchPermanodesWithAttr(ctx, ch, req)
518 if err != nil {
519 t.Fatalf("SearchPermanodesWithAttr = %v", err)
520 }
521 var got []blob.Ref
522 for r := range ch {
523 got = append(got, r)
524 }
525 want := []blob.Ref{pn, pnChild}
526 if len(got) != len(want) {
527 t.Errorf("SearchPermanodesWithAttr results differ.\n got: %q\nwant: %q",
528 got, want)
529 }
530 for _, w := range want {
531 found := false
532 for _, g := range got {
533 if g.String() == w.String() {
534 found = true
535 break
536 }
537 }
538 if !found {
539 t.Errorf("SearchPermanodesWithAttr: %v was not found.\n", w)
540 }
541 }
542 }
543
544
545 {
546 ch := make(chan blob.Ref, 10)
547 req := &camtypes.PermanodeByAttrRequest{
548 Signer: id.SignerBlobRef,
549 Attribute: "tag",
550 Query: "nosuchtag",
551 }
552 err := id.Index.SearchPermanodesWithAttr(ctx, ch, req)
553 if err != nil {
554 t.Fatalf("SearchPermanodesWithAttr = %v", err)
555 }
556 var got []blob.Ref
557 for r := range ch {
558 got = append(got, r)
559 }
560 want := []blob.Ref{}
561 if len(got) != len(want) {
562 t.Errorf("SearchPermanodesWithAttr results differ.\n got: %q\nwant: %q",
563 got, want)
564 }
565 }
566
567 {
568 ch := make(chan blob.Ref, 10)
569 req := &camtypes.PermanodeByAttrRequest{
570 Signer: id.SignerBlobRef,
571 Attribute: "unindexed",
572 }
573 err := id.Index.SearchPermanodesWithAttr(ctx, ch, req)
574 if err == nil {
575 t.Fatalf("SearchPermanodesWithAttr with unindexed attribute should return an error")
576 }
577 }
578
579
580 br4 := id.DelAttribute(pn, "title", "pony")
581 br4Time := id.LastTime()
582
583 {
584 ch := make(chan blob.Ref, 10)
585 req := &camtypes.PermanodeByAttrRequest{
586 Signer: id.SignerBlobRef,
587 Attribute: "title",
588 Query: "pony",
589 }
590 err := id.Index.SearchPermanodesWithAttr(ctx, ch, req)
591 if err != nil {
592 t.Fatalf("SearchPermanodesWithAttr = %v", err)
593 }
594 var got []blob.Ref
595 for r := range ch {
596 got = append(got, r)
597 }
598 want := []blob.Ref{}
599 if len(got) != len(want) {
600 t.Errorf("SearchPermanodesWithAttr results differ.\n got: %q\nwant: %q",
601 got, want)
602 }
603 }
604
605
606 {
607 verify := func(prefix string, want []camtypes.RecentPermanode, before time.Time) {
608 ch := make(chan camtypes.RecentPermanode, 10)
609 err := id.Index.GetRecentPermanodes(ctx, ch, id.SignerBlobRef, 50, before)
610 if err != nil {
611 t.Fatalf("[%s] GetRecentPermanodes = %v", prefix, err)
612 }
613 got := []camtypes.RecentPermanode{}
614 for r := range ch {
615 got = append(got, r)
616 }
617 if len(got) != len(want) {
618 t.Errorf("[%s] GetRecentPermanode results differ.\n got: %v\nwant: %v",
619 prefix, searchResults(got), searchResults(want))
620 }
621 for _, w := range want {
622 found := false
623 for _, g := range got {
624 if g.Equal(w) {
625 found = true
626 break
627 }
628 }
629 if !found {
630 t.Errorf("[%s] GetRecentPermanode: %v was not found.\n got: %v\nwant: %v",
631 prefix, w, searchResults(got), searchResults(want))
632 }
633 }
634 }
635
636 want := []camtypes.RecentPermanode{
637 {
638 Permanode: pn,
639 Signer: id.SignerBlobRef,
640 LastModTime: br4Time,
641 },
642 {
643 Permanode: pnChild,
644 Signer: id.SignerBlobRef,
645 LastModTime: br3Time,
646 },
647 }
648
649 before := time.Time{}
650 verify("Zero before", want, before)
651
652 before = lastPermanodeMutation
653 t.Log("lastPermanodeMutation", lastPermanodeMutation,
654 lastPermanodeMutation.Unix())
655 verify("Non-zero before", want[1:], before)
656 }
657
658 {
659 ch := make(chan blob.Ref, 10)
660 err := id.Index.GetDirMembers(ctx, imagesDirRef, ch, 50)
661 if err != nil {
662 t.Fatalf("GetDirMembers = %v", err)
663 }
664 got := []blob.Ref{}
665 for r := range ch {
666 got = append(got, r)
667 }
668 want := []blob.Ref{jpegFileRef, exifFileRef, mediaFileRef}
669 if len(got) != len(want) {
670 t.Errorf("GetDirMembers results differ.\n got: %v\nwant: %v",
671 got, want)
672 }
673 for _, w := range want {
674 found := false
675 for _, g := range got {
676 if w == g {
677 found = true
678 break
679 }
680 }
681 if !found {
682 t.Errorf("GetDirMembers: %v was not found.", w)
683 }
684 }
685 }
686
687
688 {
689 meta, err := id.Index.GetBlobMeta(ctx, pn)
690 if err != nil {
691 t.Errorf("GetBlobMeta(%q) = %v", pn, err)
692 } else {
693 if e := schema.TypePermanode; meta.CamliType != e {
694 t.Errorf("GetBlobMeta(%q) mime = %q, want %q", pn, meta.CamliType, e)
695 }
696 if meta.Size == 0 {
697 t.Errorf("GetBlobMeta(%q) size is zero", pn)
698 }
699 }
700 _, err = id.Index.GetBlobMeta(ctx, blob.ParseOrZero("abc-123"))
701 if err != os.ErrNotExist {
702 t.Errorf("GetBlobMeta(dummy blobref) = %v; want os.ErrNotExist", err)
703 }
704 }
705
706
707 {
708 claims, err := id.Index.AppendClaims(ctx, nil, pn, KeyID, "")
709 if err != nil {
710 t.Errorf("AppendClaims = %v", err)
711 } else {
712 want := []camtypes.Claim{
713 {
714 BlobRef: br1,
715 Permanode: pn,
716 Signer: id.SignerBlobRef,
717 Date: br1Time.UTC(),
718 Type: "set-attribute",
719 Attr: "tag",
720 Value: "foo1",
721 },
722 {
723 BlobRef: br2,
724 Permanode: pn,
725 Signer: id.SignerBlobRef,
726 Date: br2Time.UTC(),
727 Type: "set-attribute",
728 Attr: "tag",
729 Value: "foo2",
730 },
731 {
732 BlobRef: rootClaim,
733 Permanode: pn,
734 Signer: id.SignerBlobRef,
735 Date: rootClaimTime.UTC(),
736 Type: "set-attribute",
737 Attr: "camliRoot",
738 Value: "rootval",
739 },
740 {
741 BlobRef: memberRef,
742 Permanode: pn,
743 Signer: id.SignerBlobRef,
744 Date: memberRefTime.UTC(),
745 Type: "add-attribute",
746 Attr: "camliMember",
747 Value: pnChild.String(),
748 },
749 {
750 BlobRef: br4,
751 Permanode: pn,
752 Signer: id.SignerBlobRef,
753 Date: br4Time.UTC(),
754 Type: "del-attribute",
755 Attr: "title",
756 Value: "pony",
757 },
758 }
759 if !reflect.DeepEqual(claims, want) {
760 t.Errorf("AppendClaims results differ.\n got: %v\nwant: %v",
761 claims, want)
762 }
763 }
764 }
765 }
766
767 func PathsOfSignerTarget(t *testing.T, initIdx func() *index.Index) {
768 ctx := context.Background()
769 id := NewIndexDeps(initIdx())
770 id.Fataler = t
771 defer id.DumpIndex(t)
772 signer := id.SignerBlobRef
773 pn := id.NewPermanode()
774 t.Logf("uploaded permanode %q", pn)
775
776 claim1 := id.SetAttribute(pn, "camliPath:somedir", "targ-123")
777 claim1Time := id.LastTime().UTC()
778 claim2 := id.SetAttribute(pn, "camliPath:with|pipe", "targ-124")
779 claim2Time := id.LastTime().UTC()
780 t.Logf("made path claims %q and %q", claim1, claim2)
781
782 type test struct {
783 blobref string
784 want int
785 }
786 tests := []test{
787 {"targ-123", 1},
788 {"targ-124", 1},
789 {"targ-125", 0},
790 }
791 for _, tt := range tests {
792 paths, err := id.Index.PathsOfSignerTarget(ctx, signer, blob.ParseOrZero(tt.blobref))
793 if err != nil {
794 t.Fatalf("PathsOfSignerTarget(%q): %v", tt.blobref, err)
795 }
796 if len(paths) != tt.want {
797 t.Fatalf("PathsOfSignerTarget(%q) got %d results; want %d",
798 tt.blobref, len(paths), tt.want)
799 }
800 if tt.blobref == "targ-123" {
801 p := paths[0]
802 want := fmt.Sprintf(
803 "Path{Claim: %s, %v; Base: %s + Suffix \"somedir\" => Target targ-123}",
804 claim1, claim1Time, pn)
805 if g := p.String(); g != want {
806 t.Errorf("claim wrong.\n got: %s\nwant: %s", g, want)
807 }
808 }
809 }
810 tests = []test{
811 {"somedir", 1},
812 {"with|pipe", 1},
813 {"void", 0},
814 }
815 for _, tt := range tests {
816 paths, err := id.Index.PathsLookup(ctx, id.SignerBlobRef, pn, tt.blobref)
817 if err != nil {
818 t.Fatalf("PathsLookup(%q): %v", tt.blobref, err)
819 }
820 if len(paths) != tt.want {
821 t.Fatalf("PathsLookup(%q) got %d results; want %d",
822 tt.blobref, len(paths), tt.want)
823 }
824 if tt.blobref == "with|pipe" {
825 p := paths[0]
826 want := fmt.Sprintf(
827 "Path{Claim: %s, %s; Base: %s + Suffix \"with|pipe\" => Target targ-124}",
828 claim2, claim2Time, pn)
829 if g := p.String(); g != want {
830 t.Errorf("claim wrong.\n got: %s\nwant: %s", g, want)
831 }
832 }
833 }
834
835
836
837 claim3 := id.Delete(claim2)
838 t.Logf("claim %q deletes path claim %q", claim3, claim2)
839 tests = []test{
840 {"targ-123", 1},
841 {"targ-124", 0},
842 {"targ-125", 0},
843 }
844 for _, tt := range tests {
845 signer := id.SignerBlobRef
846 paths, err := id.Index.PathsOfSignerTarget(ctx, signer, blob.ParseOrZero(tt.blobref))
847 if err != nil {
848 t.Fatalf("PathsOfSignerTarget(%q): %v", tt.blobref, err)
849 }
850 if len(paths) != tt.want {
851 t.Fatalf("PathsOfSignerTarget(%q) got %d results; want %d",
852 tt.blobref, len(paths), tt.want)
853 }
854 }
855 tests = []test{
856 {"somedir", 1},
857 {"with|pipe", 0},
858 {"void", 0},
859 }
860 for _, tt := range tests {
861 paths, err := id.Index.PathsLookup(ctx, id.SignerBlobRef, pn, tt.blobref)
862 if err != nil {
863 t.Fatalf("PathsLookup(%q): %v", tt.blobref, err)
864 }
865 if len(paths) != tt.want {
866 t.Fatalf("PathsLookup(%q) got %d results; want %d",
867 tt.blobref, len(paths), tt.want)
868 }
869 }
870
871
872
873 claim4 := id.Delete(claim3)
874 t.Logf("delete claim %q deletes claim %q, which should undelete %q", claim4, claim3, claim2)
875 tests = []test{
876 {"targ-123", 1},
877 {"targ-124", 1},
878 {"targ-125", 0},
879 }
880 for _, tt := range tests {
881 signer := id.SignerBlobRef
882 paths, err := id.Index.PathsOfSignerTarget(ctx, signer, blob.ParseOrZero(tt.blobref))
883 if err != nil {
884 t.Fatalf("PathsOfSignerTarget(%q): %v", tt.blobref, err)
885 }
886 if len(paths) != tt.want {
887 t.Fatalf("PathsOfSignerTarget(%q) got %d results; want %d",
888 tt.blobref, len(paths), tt.want)
889 }
890
891 if tt.blobref == "targ-124" {
892 p := paths[0]
893 want := fmt.Sprintf(
894 "Path{Claim: %s, %v; Base: %s + Suffix \"with|pipe\" => Target targ-124}",
895 claim2, claim2Time, pn)
896 if g := p.String(); g != want {
897 t.Errorf("claim wrong.\n got: %s\nwant: %s", g, want)
898 }
899 }
900 }
901 tests = []test{
902 {"somedir", 1},
903 {"with|pipe", 1},
904 {"void", 0},
905 }
906 for _, tt := range tests {
907 paths, err := id.Index.PathsLookup(ctx, id.SignerBlobRef, pn, tt.blobref)
908 if err != nil {
909 t.Fatalf("PathsLookup(%q): %v", tt.blobref, err)
910 }
911 if len(paths) != tt.want {
912 t.Fatalf("PathsLookup(%q) got %d results; want %d",
913 tt.blobref, len(paths), tt.want)
914 }
915
916 if tt.blobref == "with|pipe" {
917 p := paths[0]
918 want := fmt.Sprintf(
919 "Path{Claim: %s, %s; Base: %s + Suffix \"with|pipe\" => Target targ-124}",
920 claim2, claim2Time, pn)
921 if g := p.String(); g != want {
922 t.Errorf("claim wrong.\n got: %s\nwant: %s", g, want)
923 }
924 }
925 }
926 }
927
928 func Files(t *testing.T, initIdx func() *index.Index) {
929 ctx := context.Background()
930 id := NewIndexDeps(initIdx())
931 id.Fataler = t
932 fileTime := time.Unix(1361250375, 0)
933 fileRef, wholeRef := id.UploadFile("foo.html", "<html>I am an html file.</html>", fileTime)
934 t.Logf("uploaded fileref %q, wholeRef %q", fileRef, wholeRef)
935 id.DumpIndex(t)
936
937
938 {
939 key := fmt.Sprintf("wholetofile|%s|%s", wholeRef, fileRef)
940 if g, e := id.Get(key), "1"; g != e {
941 t.Fatalf("%q = %q, want %q", key, g, e)
942 }
943
944 refs, err := id.Index.ExistingFileSchemas(wholeRef)
945 if err != nil {
946 t.Fatalf("ExistingFileSchemas = %v", err)
947 }
948 want := index.WholeRefToFile{wholeRef.String(): []blob.Ref{fileRef}}
949 if !reflect.DeepEqual(refs, want) {
950 t.Errorf("ExistingFileSchemas got = %#v, want %#v", refs, want)
951 }
952 }
953
954
955 {
956 key := fmt.Sprintf("fileinfo|%s", fileRef)
957 if g, e := id.Get(key), "31|foo.html|text%2Fhtml|sha224-35ef6689bf1b7981fb44a033ddd704ff28440779a11d927f2322bac7"; g != e {
958 t.Fatalf("%q = %q, want %q", key, g, e)
959 }
960
961 fi, err := id.Index.GetFileInfo(ctx, fileRef)
962 if err != nil {
963 t.Fatalf("GetFileInfo = %v", err)
964 }
965 if got, want := fi.Size, int64(31); got != want {
966 t.Errorf("Size = %d, want %d", got, want)
967 }
968 if got, want := fi.FileName, "foo.html"; got != want {
969 t.Errorf("FileName = %q, want %q", got, want)
970 }
971 if got, want := fi.MIMEType, "text/html"; got != want {
972 t.Errorf("MIMEType = %q, want %q", got, want)
973 }
974 if got, want := fi.Time, fileTime; !got.Time().Equal(want) {
975 t.Errorf("Time = %v; want %v", got, want)
976 }
977 if got, want := fi.WholeRef, blob.MustParse("sha224-35ef6689bf1b7981fb44a033ddd704ff28440779a11d927f2322bac7"); got != want {
978 t.Errorf("WholeRef = %v; want %v", got, want)
979 }
980 }
981 }
982
983 func EdgesTo(t *testing.T, initIdx func() *index.Index) {
984 idx := initIdx()
985 id := NewIndexDeps(idx)
986 id.Fataler = t
987 defer id.DumpIndex(t)
988
989
990 pn1 := id.NewPermanode()
991 pn2 := id.NewPermanode()
992 claim1 := id.AddAttribute(pn1, "camliMember", pn2.String())
993
994 t.Logf("edge %s --> %s", pn1, pn2)
995
996
997 {
998 edges, err := idx.EdgesTo(pn2, nil)
999 if err != nil {
1000 t.Fatal(err)
1001 }
1002 if len(edges) != 1 {
1003 t.Fatalf("num edges = %d; want 1", len(edges))
1004 }
1005 wantEdge := &camtypes.Edge{
1006 From: pn1,
1007 To: pn2,
1008 FromType: "permanode",
1009 }
1010 if got, want := edges[0].String(), wantEdge.String(); got != want {
1011 t.Errorf("Wrong edge.\n GOT: %v\nWANT: %v", got, want)
1012 }
1013 }
1014
1015
1016 del1 := id.Delete(claim1)
1017 t.Logf("del claim %q deletes claim %q, breaks link between p1 and p2", del1, claim1)
1018
1019 {
1020 edges, err := idx.EdgesTo(pn2, nil)
1021 if err != nil {
1022 t.Fatal(err)
1023 }
1024 if len(edges) != 0 {
1025 t.Fatalf("num edges = %d; want 0", len(edges))
1026 }
1027 }
1028
1029
1030 del2 := id.Delete(del1)
1031 t.Logf("del claim %q deletes del claim %q, restores link between p1 and p2", del2, del1)
1032 {
1033 edges, err := idx.EdgesTo(pn2, nil)
1034 if err != nil {
1035 t.Fatal(err)
1036 }
1037 if len(edges) != 1 {
1038 t.Fatalf("num edges = %d; want 1", len(edges))
1039 }
1040 wantEdge := &camtypes.Edge{
1041 From: pn1,
1042 To: pn2,
1043 FromType: "permanode",
1044 }
1045 if got, want := edges[0].String(), wantEdge.String(); got != want {
1046 t.Errorf("Wrong edge.\n GOT: %v\nWANT: %v", got, want)
1047 }
1048 }
1049 }
1050
1051 func Delete(t *testing.T, initIdx func() *index.Index) {
1052 ctx := context.Background()
1053 idx := initIdx()
1054 id := NewIndexDeps(idx)
1055 id.Fataler = t
1056 defer id.DumpIndex(t)
1057 pn1 := id.NewPermanode()
1058 t.Logf("uploaded permanode %q", pn1)
1059 cl1 := id.SetAttribute(pn1, "tag", "foo1")
1060 cl1Time := id.LastTime()
1061 t.Logf("set attribute %q", cl1)
1062
1063
1064 delpn1 := id.Delete(pn1)
1065 t.Logf("del claim %q deletes %q", delpn1, pn1)
1066 deleted := idx.IsDeleted(pn1)
1067 if !deleted {
1068 t.Fatal("pn1 should be deleted")
1069 }
1070
1071
1072 {
1073 ch := make(chan blob.Ref, 10)
1074 req := &camtypes.PermanodeByAttrRequest{
1075 Signer: id.SignerBlobRef,
1076 Attribute: "tag",
1077 Query: "foo1"}
1078 err := id.Index.SearchPermanodesWithAttr(ctx, ch, req)
1079 if err != nil {
1080 t.Fatalf("SearchPermanodesWithAttr = %v", err)
1081 }
1082 var got []blob.Ref
1083 for r := range ch {
1084 got = append(got, r)
1085 }
1086 want := []blob.Ref{}
1087 if len(got) != len(want) {
1088 t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want)
1089 }
1090 }
1091
1092
1093 delpn1bis := id.Delete(pn1)
1094 t.Logf("del claim %q deletes %q a second time", delpn1bis, pn1)
1095 deleted = idx.IsDeleted(pn1)
1096 if !deleted {
1097 t.Fatal("pn1 should be deleted")
1098 }
1099
1100
1101 del2 := id.Delete(delpn1)
1102 t.Logf("delete claim %q deletes %q, which should not yet revive %q", del2, delpn1, pn1)
1103 deleted = idx.IsDeleted(pn1)
1104 if !deleted {
1105 t.Fatal("pn1 should not yet be undeleted")
1106 }
1107
1108 {
1109 ch := make(chan blob.Ref, 10)
1110 req := &camtypes.PermanodeByAttrRequest{
1111 Signer: id.SignerBlobRef,
1112 Attribute: "tag",
1113 Query: "foo1"}
1114 err := id.Index.SearchPermanodesWithAttr(ctx, ch, req)
1115 if err != nil {
1116 t.Fatalf("SearchPermanodesWithAttr = %v", err)
1117 }
1118 var got []blob.Ref
1119 for r := range ch {
1120 got = append(got, r)
1121 }
1122 want := []blob.Ref{}
1123 if len(got) != len(want) {
1124 t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want)
1125 }
1126 }
1127
1128
1129 del2bis := id.Delete(delpn1bis)
1130 t.Logf("delete claim %q deletes %q, which should revive %q", del2bis, delpn1bis, pn1)
1131 deleted = idx.IsDeleted(pn1)
1132 if deleted {
1133 t.Fatal("pn1 should be undeleted")
1134 }
1135
1136 {
1137 ch := make(chan blob.Ref, 10)
1138 req := &camtypes.PermanodeByAttrRequest{
1139 Signer: id.SignerBlobRef,
1140 Attribute: "tag",
1141 Query: "foo1"}
1142 err := id.Index.SearchPermanodesWithAttr(ctx, ch, req)
1143 if err != nil {
1144 t.Fatalf("SearchPermanodesWithAttr = %v", err)
1145 }
1146 var got []blob.Ref
1147 for r := range ch {
1148 got = append(got, r)
1149 }
1150 want := []blob.Ref{pn1}
1151 if len(got) < 1 || got[0].String() != want[0].String() {
1152 t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want)
1153 }
1154 }
1155
1156
1157 del3 := id.Delete(cl1)
1158 t.Logf("del claim %q deletes claim %q", del3, cl1)
1159 deleted = idx.IsDeleted(cl1)
1160 if !deleted {
1161 t.Fatal("cl1 should be deleted")
1162 }
1163
1164 {
1165 ch := make(chan blob.Ref, 10)
1166 req := &camtypes.PermanodeByAttrRequest{
1167 Signer: id.SignerBlobRef,
1168 Attribute: "tag",
1169 Query: "foo1"}
1170 err := id.Index.SearchPermanodesWithAttr(ctx, ch, req)
1171 if err != nil {
1172 t.Fatalf("SearchPermanodesWithAttr = %v", err)
1173 }
1174 var got []blob.Ref
1175 for r := range ch {
1176 got = append(got, r)
1177 }
1178 want := []blob.Ref{}
1179 if len(got) != len(want) {
1180 t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want)
1181 }
1182 }
1183
1184 {
1185 claims, err := id.Index.AppendClaims(ctx, nil, pn1, KeyID, "")
1186 if err != nil {
1187 t.Errorf("AppendClaims = %v", err)
1188 } else {
1189 want := []camtypes.Claim{}
1190 if len(claims) != len(want) {
1191 t.Errorf("id.Index.AppendClaims gives %q, want %q", claims, want)
1192 }
1193 }
1194 }
1195
1196
1197 del4 := id.Delete(del3)
1198 t.Logf("del claim %q deletes del claim %q, which should undelete %q", del4, del3, cl1)
1199
1200 {
1201 ch := make(chan blob.Ref, 10)
1202 req := &camtypes.PermanodeByAttrRequest{
1203 Signer: id.SignerBlobRef,
1204 Attribute: "tag",
1205 Query: "foo1"}
1206 err := id.Index.SearchPermanodesWithAttr(ctx, ch, req)
1207 if err != nil {
1208 t.Fatalf("SearchPermanodesWithAttr = %v", err)
1209 }
1210 var got []blob.Ref
1211 for r := range ch {
1212 got = append(got, r)
1213 }
1214 want := []blob.Ref{pn1}
1215 if len(got) < 1 || got[0].String() != want[0].String() {
1216 t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want)
1217 }
1218 }
1219
1220 {
1221 claims, err := id.Index.AppendClaims(ctx, nil, pn1, KeyID, "")
1222 if err != nil {
1223 t.Errorf("AppendClaims = %v", err)
1224 } else {
1225 want := []camtypes.Claim{
1226 {
1227 BlobRef: cl1,
1228 Permanode: pn1,
1229 Signer: id.SignerBlobRef,
1230 Date: cl1Time.UTC(),
1231 Type: "set-attribute",
1232 Attr: "tag",
1233 Value: "foo1",
1234 },
1235 }
1236 if !reflect.DeepEqual(claims, want) {
1237 t.Errorf("GetOwnerClaims results differ.\n got: %v\nwant: %v",
1238 claims, want)
1239 }
1240 }
1241 }
1242 }
1243
1244 type searchResults []camtypes.RecentPermanode
1245
1246 func (s searchResults) String() string {
1247 var buf bytes.Buffer
1248 fmt.Fprintf(&buf, "[%d search results: ", len(s))
1249 for _, r := range s {
1250 fmt.Fprintf(&buf, "{BlobRef: %s, Signer: %s, LastModTime: %d}",
1251 r.Permanode, r.Signer, r.LastModTime.Unix())
1252 }
1253 buf.WriteString("]")
1254 return buf.String()
1255 }
1256
1257 func Reindex(t *testing.T, initIdx func() *index.Index) {
1258 idx := initIdx()
1259 id := NewIndexDeps(idx)
1260 id.Fataler = t
1261 defer id.DumpIndex(t)
1262
1263 pn1 := id.NewPlannedPermanode("foo1")
1264 t.Logf("uploaded permanode %q", pn1)
1265
1266
1267 delpn1 := id.Delete(pn1)
1268 t.Logf("del claim %q deletes %q", delpn1, pn1)
1269 if deleted := idx.IsDeleted(pn1); !deleted {
1270 t.Fatal("pn1 should be deleted")
1271 }
1272
1273 if err := id.Index.Reindex(); err != nil {
1274 t.Fatalf("reindexing failed: %v", err)
1275 }
1276
1277 if deleted := idx.IsDeleted(pn1); !deleted {
1278 t.Fatal("pn1 should be deleted after reindexing")
1279 }
1280 }
1281
1282 type enumArgs struct {
1283 ctx context.Context
1284 dest chan blob.SizedRef
1285 after string
1286 limit int
1287 }
1288
1289 func checkEnumerate(idx *index.Index, want []blob.SizedRef, args *enumArgs) error {
1290 if args == nil {
1291 args = &enumArgs{}
1292 }
1293 if args.ctx == nil {
1294 args.ctx = context.TODO()
1295 }
1296 if args.dest == nil {
1297 args.dest = make(chan blob.SizedRef)
1298 }
1299 if args.limit == 0 {
1300 args.limit = 5000
1301 }
1302 errCh := make(chan error)
1303 go func() {
1304 errCh <- idx.EnumerateBlobs(args.ctx, args.dest, args.after, args.limit)
1305 }()
1306 for k, sbr := range want {
1307 got, ok := <-args.dest
1308 if !ok {
1309 return fmt.Errorf("could not enumerate blob %d", k)
1310 }
1311 if got != sbr {
1312 return fmt.Errorf("enumeration %d: got %v, wanted %v", k, got, sbr)
1313 }
1314 }
1315 _, ok := <-args.dest
1316 if ok {
1317 return errors.New("chan was not closed after enumeration")
1318 }
1319 return <-errCh
1320 }
1321
1322 func checkStat(idx *index.Index, want []blob.SizedRef) error {
1323 pos := make(map[blob.Ref]int)
1324 need := make(map[blob.Ref]bool)
1325 for i, sb := range want {
1326 pos[sb.Ref] = i
1327 need[sb.Ref] = true
1328 }
1329
1330 input := make([]blob.Ref, len(want))
1331 for _, sbr := range want {
1332 input = append(input, sbr.Ref)
1333 }
1334 err := idx.StatBlobs(context.Background(), input, func(sb blob.SizedRef) error {
1335 if !sb.Valid() {
1336 return errors.New("StatBlobs func called with invalid/zero blob.SizedRef")
1337 }
1338 wantPos, ok := pos[sb.Ref]
1339 if !ok {
1340 return fmt.Errorf("StatBlobs func called with unrequested ref %v (size %d)", sb.Ref, sb.Size)
1341 }
1342 if !need[sb.Ref] {
1343 return fmt.Errorf("StatBlobs func called with ref %v multiple times", sb.Ref)
1344 }
1345 delete(need, sb.Ref)
1346 w := want[wantPos]
1347 if sb != w {
1348 return fmt.Errorf("StatBlobs returned %v; want %v", sb, w)
1349 }
1350 return nil
1351 })
1352 if err != nil {
1353 return err
1354 }
1355 for br := range need {
1356 return fmt.Errorf("didn't get stat result for %v", br)
1357 }
1358 return nil
1359 }
1360
1361 func EnumStat(t *testing.T, initIdx func() *index.Index) {
1362 idx := initIdx()
1363 id := NewIndexDeps(idx)
1364 id.Fataler = t
1365
1366 type step func() error
1367
1368
1369 added := make(map[string]blob.Ref)
1370
1371 stepAdd := func(contents string) step {
1372 return func() error {
1373 pn := id.NewPlannedPermanode(contents)
1374 t.Logf("uploaded permanode %q", pn)
1375 added[contents] = pn
1376 return nil
1377 }
1378 }
1379
1380 stepEnumCheck := func(want []blob.SizedRef, args *enumArgs) step {
1381 return func() error {
1382 if err := checkEnumerate(idx, want, args); err != nil {
1383 return err
1384 }
1385 return nil
1386 }
1387 }
1388
1389 missingBlob := blob.MustParse("sha224-00000000000000000000000000000000000000000000000000000000")
1390 stepDelete := func(toDelete blob.Ref) step {
1391 return func() error {
1392 del := id.Delete(missingBlob)
1393 t.Logf("added del claim %v to delete %v", del, toDelete)
1394 return nil
1395 }
1396 }
1397
1398 stepStatCheck := func(want []blob.SizedRef) step {
1399 return func() error {
1400 if err := checkStat(idx, want); err != nil {
1401 return err
1402 }
1403 return nil
1404 }
1405 }
1406
1407 for _, v := range []string{
1408 "foo",
1409 "barr",
1410 "bazzz",
1411 } {
1412 stepAdd(v)()
1413 }
1414 foo := blob.SizedRef{
1415 Ref: blob.MustParse(added["foo"].String()),
1416 Size: 552,
1417 }
1418 bar := blob.SizedRef{
1419 Ref: blob.MustParse(added["barr"].String()),
1420 Size: 553,
1421 }
1422 baz := blob.SizedRef{
1423 Ref: blob.MustParse(added["bazzz"].String()),
1424 Size: 554,
1425 }
1426 delMissing := blob.SizedRef{
1427 Ref: blob.MustParse("sha224-7fcb38ca16606814881678d30874f1b59953bc522570be8645da1d14"),
1428 Size: 685,
1429 }
1430
1431 if err := stepEnumCheck([]blob.SizedRef{bar, foo, baz}, nil)(); err != nil {
1432 t.Fatalf("first enum, testing order: %v", err)
1433 }
1434
1435
1436 if err := stepEnumCheck([]blob.SizedRef{foo, baz},
1437 &enumArgs{
1438 after: added["barr"].String(),
1439 },
1440 )(); err != nil {
1441 t.Fatalf("second enum, testing skipping with after: %v", err)
1442 }
1443
1444
1445
1446 stepDelete(missingBlob)()
1447 if err := stepEnumCheck([]blob.SizedRef{delMissing, bar, foo, baz}, nil)(); err != nil {
1448 t.Fatalf("third enum, testing old \"have\" row compat: %v", err)
1449 }
1450
1451 if err := stepStatCheck([]blob.SizedRef{delMissing, foo, bar, baz})(); err != nil {
1452 t.Fatalf("stat check: %v", err)
1453 }
1454 }
1455
1456
1457
1458 func MustNew(t *testing.T, s sorted.KeyValue) *index.Index {
1459 ix, err := index.New(s)
1460 if err != nil {
1461 t.Fatalf("Error creating index: %v", err)
1462 }
1463 return ix
1464 }