/* Copyright 2011 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package indextest contains the unit tests for the indexer so they // can be re-used for each specific implementation of the index // Storage interface. package indextest import ( "bytes" "fmt" "io/ioutil" "log" "net/url" "os" "path/filepath" "reflect" "testing" "time" "camlistore.org/pkg/blob" "camlistore.org/pkg/index" "camlistore.org/pkg/jsonsign" "camlistore.org/pkg/osutil" "camlistore.org/pkg/schema" "camlistore.org/pkg/test" "camlistore.org/pkg/types/camtypes" ) // An IndexDeps is a helper for populating and querying an Index for tests. type IndexDeps struct { Index *index.Index BlobSource *test.Fetcher // Following three needed for signing: PublicKeyFetcher *test.Fetcher EntityFetcher jsonsign.EntityFetcher // fetching decrypted openpgp entities SignerBlobRef blob.Ref now time.Time // fake clock, nanos since epoch Fataler // optional means of failing. } type Fataler interface { Fatalf(format string, args ...interface{}) } type logFataler struct{} func (logFataler) Fatalf(format string, args ...interface{}) { log.Fatalf(format, args...) } func (id *IndexDeps) Get(key string) string { v, _ := id.Index.Storage().Get(key) return v } func (id *IndexDeps) Set(key, value string) error { return id.Index.Storage().Set(key, value) } func (id *IndexDeps) DumpIndex(t *testing.T) { t.Logf("Begin index dump:") it := id.Index.Storage().Find("", "") for it.Next() { t.Logf(" %q = %q", it.Key(), it.Value()) } if err := it.Close(); err != nil { t.Fatalf("iterator close = %v", err) } t.Logf("End index dump.") } func (id *IndexDeps) uploadAndSign(m *schema.Builder) blob.Ref { m.SetSigner(id.SignerBlobRef) unsigned, err := m.JSON() if err != nil { id.Fatalf("uploadAndSignMap: " + err.Error()) } sr := &jsonsign.SignRequest{ UnsignedJSON: unsigned, Fetcher: id.PublicKeyFetcher, EntityFetcher: id.EntityFetcher, SignatureTime: id.now, } signed, err := sr.Sign() if err != nil { id.Fatalf("problem signing: " + err.Error()) } tb := &test.Blob{Contents: signed} _, err = id.BlobSource.ReceiveBlob(tb.BlobRef(), tb.Reader()) if err != nil { id.Fatalf("public uploading signed blob to blob source, pre-indexing: %v, %v", tb.BlobRef(), err) } _, err = id.Index.ReceiveBlob(tb.BlobRef(), tb.Reader()) if err != nil { id.Fatalf("problem indexing blob: %v\nblob was:\n%s", err, signed) } return tb.BlobRef() } // NewPermanode creates (& signs) a new permanode and adds it // to the index, returning its blobref. func (id *IndexDeps) NewPermanode() blob.Ref { unsigned := schema.NewUnsignedPermanode() return id.uploadAndSign(unsigned) } // NewPermanode creates (& signs) a new planned permanode and adds it // to the index, returning its blobref. func (id *IndexDeps) NewPlannedPermanode(key string) blob.Ref { unsigned := schema.NewPlannedPermanode(key) return id.uploadAndSign(unsigned) } func (id *IndexDeps) advanceTime() time.Time { id.now = id.now.Add(1 * time.Second) return id.now } // LastTime returns the time of the most recent mutation (claim). func (id *IndexDeps) LastTime() time.Time { return id.now } func (id *IndexDeps) SetAttribute(permaNode blob.Ref, attr, value string) blob.Ref { m := schema.NewSetAttributeClaim(permaNode, attr, value) m.SetClaimDate(id.advanceTime()) return id.uploadAndSign(m) } func (id *IndexDeps) SetAttribute_NoTimeMove(permaNode blob.Ref, attr, value string) blob.Ref { m := schema.NewSetAttributeClaim(permaNode, attr, value) m.SetClaimDate(id.LastTime()) return id.uploadAndSign(m) } func (id *IndexDeps) AddAttribute(permaNode blob.Ref, attr, value string) blob.Ref { m := schema.NewAddAttributeClaim(permaNode, attr, value) m.SetClaimDate(id.advanceTime()) return id.uploadAndSign(m) } func (id *IndexDeps) DelAttribute(permaNode blob.Ref, attr, value string) blob.Ref { m := schema.NewDelAttributeClaim(permaNode, attr, value) m.SetClaimDate(id.advanceTime()) return id.uploadAndSign(m) } func (id *IndexDeps) Delete(target blob.Ref) blob.Ref { m := schema.NewDeleteClaim(target) m.SetClaimDate(id.advanceTime()) return id.uploadAndSign(m) } var noTime = time.Time{} func (id *IndexDeps) UploadString(v string) blob.Ref { cb := &test.Blob{Contents: v} id.BlobSource.AddBlob(cb) br := cb.BlobRef() _, err := id.Index.ReceiveBlob(br, cb.Reader()) if err != nil { id.Fatalf("UploadString: %v", err) } return br } // If modTime is zero, it's not used. func (id *IndexDeps) UploadFile(fileName string, contents string, modTime time.Time) (fileRef, wholeRef blob.Ref) { wholeRef = id.UploadString(contents) m := schema.NewFileMap(fileName) m.PopulateParts(int64(len(contents)), []schema.BytesPart{ schema.BytesPart{ Size: uint64(len(contents)), BlobRef: wholeRef, }}) if !modTime.IsZero() { m.SetModTime(modTime) } fjson, err := m.JSON() if err != nil { id.Fatalf("UploadFile.JSON: %v", err) } fb := &test.Blob{Contents: fjson} id.BlobSource.AddBlob(fb) fileRef = fb.BlobRef() _, err = id.Index.ReceiveBlob(fileRef, fb.Reader()) if err != nil { panic(err) } return } // If modTime is zero, it's not used. func (id *IndexDeps) UploadDir(dirName string, children []blob.Ref, modTime time.Time) blob.Ref { // static-set entries blob ss := new(schema.StaticSet) for _, child := range children { ss.Add(child) } ssjson := ss.Blob().JSON() ssb := &test.Blob{Contents: ssjson} id.BlobSource.AddBlob(ssb) _, err := id.Index.ReceiveBlob(ssb.BlobRef(), ssb.Reader()) if err != nil { id.Fatalf("UploadDir.ReceiveBlob: %v", err) } // directory blob bb := schema.NewDirMap(dirName) bb.PopulateDirectoryMap(ssb.BlobRef()) if !modTime.IsZero() { bb.SetModTime(modTime) } dirjson, err := bb.JSON() if err != nil { id.Fatalf("UploadDir.JSON: %v", err) } dirb := &test.Blob{Contents: dirjson} id.BlobSource.AddBlob(dirb) _, err = id.Index.ReceiveBlob(dirb.BlobRef(), dirb.Reader()) if err != nil { id.Fatalf("UploadDir.ReceiveBlob: %v", err) } return dirb.BlobRef() } // NewIndexDeps returns an IndexDeps helper for populating and working // with the provided index for tests. func NewIndexDeps(index *index.Index) *IndexDeps { camliRootPath, err := osutil.GoPackagePath("camlistore.org") if err != nil { log.Fatal("Package camlistore.org no found in $GOPATH or $GOPATH not defined") } secretRingFile := filepath.Join(camliRootPath, "pkg", "jsonsign", "testdata", "test-secring.gpg") pubKey := &test.Blob{Contents: `-----BEGIN PGP PUBLIC KEY BLOCK----- xsBNBEzgoVsBCAC/56aEJ9BNIGV9FVP+WzenTAkg12k86YqlwJVAB/VwdMlyXxvi bCT1RVRfnYxscs14LLfcMWF3zMucw16mLlJCBSLvbZ0jn4h+/8vK5WuAdjw2YzLs WtBcjWn3lV6tb4RJz5gtD/o1w8VWxwAnAVIWZntKAWmkcChCRgdUeWso76+plxE5 aRYBJqdT1mctGqNEISd/WYPMgwnWXQsVi3x4z1dYu2tD9uO1dkAff12z1kyZQIBQ rexKYRRRh9IKAayD4kgS0wdlULjBU98aeEaMz1ckuB46DX3lAYqmmTEL/Rl9cOI0 Enpn/oOOfYFa5h0AFndZd1blMvruXfdAobjVABEBAAE= =28/7 -----END PGP PUBLIC KEY BLOCK-----`} id := &IndexDeps{ Index: index, BlobSource: new(test.Fetcher), PublicKeyFetcher: new(test.Fetcher), EntityFetcher: &jsonsign.CachingEntityFetcher{ Fetcher: &jsonsign.FileEntityFetcher{File: secretRingFile}, }, SignerBlobRef: pubKey.BlobRef(), now: test.ClockOrigin, Fataler: logFataler{}, } // Add dev client test key public key, keyid 26F5ABDA, // blobref sha1-ad87ca5c78bd0ce1195c46f7c98e6025abbaf007 if g, w := id.SignerBlobRef.String(), "sha1-ad87ca5c78bd0ce1195c46f7c98e6025abbaf007"; g != w { id.Fatalf("unexpected signer blobref; got signer = %q; want %q", g, w) } id.PublicKeyFetcher.AddBlob(pubKey) id.Index.KeyFetcher = id.PublicKeyFetcher id.Index.BlobSource = id.BlobSource return id } func Index(t *testing.T, initIdx func() *index.Index) { id := NewIndexDeps(initIdx()) id.Fataler = t defer id.DumpIndex(t) pn := id.NewPermanode() t.Logf("uploaded permanode %q", pn) br1 := id.SetAttribute(pn, "tag", "foo1") br1Time := id.LastTime() t.Logf("set attribute %q", br1) br2 := id.SetAttribute(pn, "tag", "foo2") br2Time := id.LastTime() t.Logf("set attribute %q", br2) rootClaim := id.SetAttribute(pn, "camliRoot", "rootval") rootClaimTime := id.LastTime() t.Logf("set attribute %q", rootClaim) pnChild := id.NewPermanode() br3 := id.SetAttribute(pnChild, "tag", "bar") br3Time := id.LastTime() t.Logf("set attribute %q", br3) memberRef := id.AddAttribute(pn, "camliMember", pnChild.String()) t.Logf("add-attribute claim %q points to member permanode %q", memberRef, pnChild) memberRefTime := id.LastTime() // TODO(bradfitz): add EXIF tests here, once that stuff is ready. if false { camliRootPath, err := osutil.GoPackagePath("camlistore.org") if err != nil { t.Fatal("Package camlistore.org no found in $GOPATH or $GOPATH not defined") } for i := 1; i <= 8; i++ { fileBase := fmt.Sprintf("f%d-exif.jpg", i) fileName := filepath.Join(camliRootPath, "pkg", "images", "testdata", fileBase) contents, err := ioutil.ReadFile(fileName) if err != nil { t.Fatal(err) } id.UploadFile(fileBase, string(contents), noTime) } } // Upload some files. var jpegFileRef, exifFileRef, mediaFileRef, mediaWholeRef blob.Ref { camliRootPath, err := osutil.GoPackagePath("camlistore.org") if err != nil { t.Fatal("Package camlistore.org no found in $GOPATH or $GOPATH not defined") } uploadFile := func(file string, modTime time.Time) (fileRef, wholeRef blob.Ref) { fileName := filepath.Join(camliRootPath, "pkg", "index", "indextest", "testdata", file) contents, err := ioutil.ReadFile(fileName) if err != nil { t.Fatal(err) } fileRef, wholeRef = id.UploadFile(file, string(contents), modTime) return } jpegFileRef, _ = uploadFile("dude.jpg", noTime) exifFileRef, _ = uploadFile("dude-exif.jpg", time.Unix(1361248796, 0)) mediaFileRef, mediaWholeRef = uploadFile("0s.mp3", noTime) } // Upload the dir containing the previous files. imagesDirRef := id.UploadDir( "testdata", []blob.Ref{jpegFileRef, exifFileRef, mediaFileRef}, time.Now(), ) lastPermanodeMutation := id.LastTime() key := "signerkeyid:sha1-ad87ca5c78bd0ce1195c46f7c98e6025abbaf007" if g, e := id.Get(key), "2931A67C26F5ABDA"; g != e { t.Fatalf("%q = %q, want %q", key, g, e) } key = "imagesize|" + jpegFileRef.String() if g, e := id.Get(key), "50|100"; g != e { t.Errorf("JPEG dude.jpg key %q = %q; want %q", key, g, e) } key = "filetimes|" + jpegFileRef.String() if g, e := id.Get(key), ""; g != e { t.Errorf("JPEG dude.jpg key %q = %q; want %q", key, g, e) } key = "filetimes|" + exifFileRef.String() if g, e := id.Get(key), "2013-02-18T01%3A11%3A20Z%2C2013-02-19T04%3A39%3A56Z"; g != e { t.Errorf("EXIF dude-exif.jpg key %q = %q; want %q", key, g, e) } key = "have:" + pn.String() pnSizeStr := id.Get(key) if pnSizeStr == "" { t.Fatalf("missing key %q", key) } key = "meta:" + pn.String() if g, e := id.Get(key), pnSizeStr+"|application/json; camliType=permanode"; g != e { t.Errorf("key %q = %q, want %q", key, g, e) } key = "recpn|2931A67C26F5ABDA|rt7988-88-71T98:67:62.999876543Z|" + br1.String() if g, e := id.Get(key), pn.String(); g != e { t.Fatalf("%q = %q, want %q (permanode)", key, g, e) } key = "recpn|2931A67C26F5ABDA|rt7988-88-71T98:67:61.999876543Z|" + br2.String() if g, e := id.Get(key), pn.String(); g != e { t.Fatalf("%q = %q, want %q (permanode)", key, g, e) } key = fmt.Sprintf("edgeback|%s|%s|%s", pnChild, pn, memberRef) if g, e := id.Get(key), "permanode|"; g != e { t.Fatalf("edgeback row %q = %q, want %q", key, g, e) } mediaTests := []struct { prop, exp string }{ {"title", "Zero Seconds"}, {"artist", "Test Artist"}, {"album", "Test Album"}, {"genre", "(20)Alternative"}, {"musicbrainzalbumid", "00000000-0000-0000-0000-000000000000"}, {"year", "1992"}, {"track", "1"}, {"disc", "2"}, {"mediaref", "sha1-fefac74a1d5928316d7131747107c8a61b71ffe4"}, {"durationms", "26"}, } for _, tt := range mediaTests { key = fmt.Sprintf("mediatag|%s|%s", mediaWholeRef.String(), tt.prop) if g, _ := url.QueryUnescape(id.Get(key)); g != tt.exp { t.Errorf("0s.mp3 key %q = %q; want %q", key, g, tt.exp) } } // PermanodeOfSignerAttrValue { gotPN, err := id.Index.PermanodeOfSignerAttrValue(id.SignerBlobRef, "camliRoot", "rootval") if err != nil { t.Fatalf("id.Index.PermanodeOfSignerAttrValue = %v", err) } if gotPN.String() != pn.String() { t.Errorf("id.Index.PermanodeOfSignerAttrValue = %q, want %q", gotPN, pn) } _, err = id.Index.PermanodeOfSignerAttrValue(id.SignerBlobRef, "camliRoot", "MISSING") if err == nil { t.Errorf("expected an error from PermanodeOfSignerAttrValue on missing value") } } // SearchPermanodesWithAttr - match attr type "tag" and value "foo1" { ch := make(chan blob.Ref, 10) req := &camtypes.PermanodeByAttrRequest{ Signer: id.SignerBlobRef, Attribute: "tag", Query: "foo1", } err := id.Index.SearchPermanodesWithAttr(ch, req) if err != nil { t.Fatalf("SearchPermanodesWithAttr = %v", err) } var got []blob.Ref for r := range ch { got = append(got, r) } want := []blob.Ref{pn} if len(got) < 1 || got[0].String() != want[0].String() { t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want) } } // SearchPermanodesWithAttr - match all with attr type "tag" { ch := make(chan blob.Ref, 10) req := &camtypes.PermanodeByAttrRequest{ Signer: id.SignerBlobRef, Attribute: "tag", } err := id.Index.SearchPermanodesWithAttr(ch, req) if err != nil { t.Fatalf("SearchPermanodesWithAttr = %v", err) } var got []blob.Ref for r := range ch { got = append(got, r) } want := []blob.Ref{pn, pnChild} if len(got) != len(want) { t.Errorf("SearchPermanodesWithAttr results differ.\n got: %q\nwant: %q", got, want) } for _, w := range want { found := false for _, g := range got { if g.String() == w.String() { found = true break } } if !found { t.Errorf("SearchPermanodesWithAttr: %v was not found.\n", w) } } } // Delete value "pony" of type "title" (which does not actually exist) for pn br4 := id.DelAttribute(pn, "title", "pony") br4Time := id.LastTime() // and verify it is not found when searching by attr { ch := make(chan blob.Ref, 10) req := &camtypes.PermanodeByAttrRequest{ Signer: id.SignerBlobRef, Attribute: "title", Query: "pony", } err := id.Index.SearchPermanodesWithAttr(ch, req) if err != nil { t.Fatalf("SearchPermanodesWithAttr = %v", err) } var got []blob.Ref for r := range ch { got = append(got, r) } want := []blob.Ref{} if len(got) != len(want) { t.Errorf("SearchPermanodesWithAttr results differ.\n got: %q\nwant: %q", got, want) } } // GetRecentPermanodes { verify := func(prefix string, want []camtypes.RecentPermanode, before time.Time) { ch := make(chan camtypes.RecentPermanode, 10) // expect 2 results, but maybe more if buggy. err := id.Index.GetRecentPermanodes(ch, id.SignerBlobRef, 50, before) if err != nil { t.Fatalf("[%s] GetRecentPermanodes = %v", prefix, err) } got := []camtypes.RecentPermanode{} for r := range ch { got = append(got, r) } if len(got) != len(want) { t.Errorf("[%s] GetRecentPermanode results differ.\n got: %v\nwant: %v", prefix, searchResults(got), searchResults(want)) } for _, w := range want { found := false for _, g := range got { if g.Equal(w) { found = true break } } if !found { t.Errorf("[%s] GetRecentPermanode: %v was not found.\n got: %v\nwant: %v", prefix, w, searchResults(got), searchResults(want)) } } } want := []camtypes.RecentPermanode{ { Permanode: pn, Signer: id.SignerBlobRef, LastModTime: br4Time, }, { Permanode: pnChild, Signer: id.SignerBlobRef, LastModTime: br3Time, }, } before := time.Time{} verify("Zero before", want, before) before = lastPermanodeMutation t.Log("lastPermanodeMutation", lastPermanodeMutation, lastPermanodeMutation.Unix()) verify("Non-zero before", want[1:], before) } // GetDirMembers { ch := make(chan blob.Ref, 10) // expect 2 results err := id.Index.GetDirMembers(imagesDirRef, ch, 50) if err != nil { t.Fatalf("GetDirMembers = %v", err) } got := []blob.Ref{} for r := range ch { got = append(got, r) } want := []blob.Ref{jpegFileRef, exifFileRef, mediaFileRef} if len(got) != len(want) { t.Errorf("GetDirMembers results differ.\n got: %v\nwant: %v", got, want) } for _, w := range want { found := false for _, g := range got { if w == g { found = true break } } if !found { t.Errorf("GetDirMembers: %v was not found.", w) } } } // GetBlobMeta { meta, err := id.Index.GetBlobMeta(pn) if err != nil { t.Errorf("GetBlobMeta(%q) = %v", pn, err) } else { if e := "permanode"; meta.CamliType != e { t.Errorf("GetBlobMeta(%q) mime = %q, want %q", pn, meta.CamliType, e) } if meta.Size == 0 { t.Errorf("GetBlobMeta(%q) size is zero", pn) } } _, err = id.Index.GetBlobMeta(blob.ParseOrZero("abc-123")) if err != os.ErrNotExist { t.Errorf("GetBlobMeta(dummy blobref) = %v; want os.ErrNotExist", err) } } // AppendClaims { claims, err := id.Index.AppendClaims(nil, pn, id.SignerBlobRef, "") if err != nil { t.Errorf("AppendClaims = %v", err) } else { want := []camtypes.Claim{ { BlobRef: br1, Permanode: pn, Signer: id.SignerBlobRef, Date: br1Time.UTC(), Type: "set-attribute", Attr: "tag", Value: "foo1", }, { BlobRef: br2, Permanode: pn, Signer: id.SignerBlobRef, Date: br2Time.UTC(), Type: "set-attribute", Attr: "tag", Value: "foo2", }, { BlobRef: rootClaim, Permanode: pn, Signer: id.SignerBlobRef, Date: rootClaimTime.UTC(), Type: "set-attribute", Attr: "camliRoot", Value: "rootval", }, { BlobRef: memberRef, Permanode: pn, Signer: id.SignerBlobRef, Date: memberRefTime.UTC(), Type: "add-attribute", Attr: "camliMember", Value: pnChild.String(), }, { BlobRef: br4, Permanode: pn, Signer: id.SignerBlobRef, Date: br4Time.UTC(), Type: "del-attribute", Attr: "title", Value: "pony", }, } if !reflect.DeepEqual(claims, want) { t.Errorf("AppendClaims results differ.\n got: %v\nwant: %v", claims, want) } } } } func PathsOfSignerTarget(t *testing.T, initIdx func() *index.Index) { id := NewIndexDeps(initIdx()) id.Fataler = t defer id.DumpIndex(t) signer := id.SignerBlobRef pn := id.NewPermanode() t.Logf("uploaded permanode %q", pn) claim1 := id.SetAttribute(pn, "camliPath:somedir", "targ-123") claim1Time := id.LastTime().UTC() claim2 := id.SetAttribute(pn, "camliPath:with|pipe", "targ-124") claim2Time := id.LastTime().UTC() t.Logf("made path claims %q and %q", claim1, claim2) type test struct { blobref string want int } tests := []test{ {"targ-123", 1}, {"targ-124", 1}, {"targ-125", 0}, } for _, tt := range tests { paths, err := id.Index.PathsOfSignerTarget(signer, blob.ParseOrZero(tt.blobref)) if err != nil { t.Fatalf("PathsOfSignerTarget(%q): %v", tt.blobref, err) } if len(paths) != tt.want { t.Fatalf("PathsOfSignerTarget(%q) got %d results; want %d", tt.blobref, len(paths), tt.want) } if tt.blobref == "targ-123" { p := paths[0] want := fmt.Sprintf( "Path{Claim: %s, %v; Base: %s + Suffix \"somedir\" => Target targ-123}", claim1, claim1Time, pn) if g := p.String(); g != want { t.Errorf("claim wrong.\n got: %s\nwant: %s", g, want) } } } tests = []test{ {"somedir", 1}, {"with|pipe", 1}, {"void", 0}, } for _, tt := range tests { paths, err := id.Index.PathsLookup(id.SignerBlobRef, pn, tt.blobref) if err != nil { t.Fatalf("PathsLookup(%q): %v", tt.blobref, err) } if len(paths) != tt.want { t.Fatalf("PathsLookup(%q) got %d results; want %d", tt.blobref, len(paths), tt.want) } if tt.blobref == "with|pipe" { p := paths[0] want := fmt.Sprintf( "Path{Claim: %s, %s; Base: %s + Suffix \"with|pipe\" => Target targ-124}", claim2, claim2Time, pn) if g := p.String(); g != want { t.Errorf("claim wrong.\n got: %s\nwant: %s", g, want) } } } // now test deletions // Delete an existing value claim3 := id.Delete(claim2) t.Logf("claim %q deletes path claim %q", claim3, claim2) tests = []test{ {"targ-123", 1}, {"targ-124", 0}, {"targ-125", 0}, } for _, tt := range tests { signer := id.SignerBlobRef paths, err := id.Index.PathsOfSignerTarget(signer, blob.ParseOrZero(tt.blobref)) if err != nil { t.Fatalf("PathsOfSignerTarget(%q): %v", tt.blobref, err) } if len(paths) != tt.want { t.Fatalf("PathsOfSignerTarget(%q) got %d results; want %d", tt.blobref, len(paths), tt.want) } } tests = []test{ {"somedir", 1}, {"with|pipe", 0}, {"void", 0}, } for _, tt := range tests { paths, err := id.Index.PathsLookup(id.SignerBlobRef, pn, tt.blobref) if err != nil { t.Fatalf("PathsLookup(%q): %v", tt.blobref, err) } if len(paths) != tt.want { t.Fatalf("PathsLookup(%q) got %d results; want %d", tt.blobref, len(paths), tt.want) } } // recreate second path, and test if the previous deletion of it // is indeed ignored. claim4 := id.Delete(claim3) t.Logf("delete claim %q deletes claim %q, which should undelete %q", claim4, claim3, claim2) tests = []test{ {"targ-123", 1}, {"targ-124", 1}, {"targ-125", 0}, } for _, tt := range tests { signer := id.SignerBlobRef paths, err := id.Index.PathsOfSignerTarget(signer, blob.ParseOrZero(tt.blobref)) if err != nil { t.Fatalf("PathsOfSignerTarget(%q): %v", tt.blobref, err) } if len(paths) != tt.want { t.Fatalf("PathsOfSignerTarget(%q) got %d results; want %d", tt.blobref, len(paths), tt.want) } // and check the modtime too if tt.blobref == "targ-124" { p := paths[0] want := fmt.Sprintf( "Path{Claim: %s, %v; Base: %s + Suffix \"with|pipe\" => Target targ-124}", claim2, claim2Time, pn) if g := p.String(); g != want { t.Errorf("claim wrong.\n got: %s\nwant: %s", g, want) } } } tests = []test{ {"somedir", 1}, {"with|pipe", 1}, {"void", 0}, } for _, tt := range tests { paths, err := id.Index.PathsLookup(id.SignerBlobRef, pn, tt.blobref) if err != nil { t.Fatalf("PathsLookup(%q): %v", tt.blobref, err) } if len(paths) != tt.want { t.Fatalf("PathsLookup(%q) got %d results; want %d", tt.blobref, len(paths), tt.want) } // and check that modtime is now claim4Time if tt.blobref == "with|pipe" { p := paths[0] want := fmt.Sprintf( "Path{Claim: %s, %s; Base: %s + Suffix \"with|pipe\" => Target targ-124}", claim2, claim2Time, pn) if g := p.String(); g != want { t.Errorf("claim wrong.\n got: %s\nwant: %s", g, want) } } } } func Files(t *testing.T, initIdx func() *index.Index) { id := NewIndexDeps(initIdx()) id.Fataler = t fileTime := time.Unix(1361250375, 0) fileRef, wholeRef := id.UploadFile("foo.html", "I am an html file.", fileTime) t.Logf("uploaded fileref %q, wholeRef %q", fileRef, wholeRef) id.DumpIndex(t) // ExistingFileSchemas { key := fmt.Sprintf("wholetofile|%s|%s", wholeRef, fileRef) if g, e := id.Get(key), "1"; g != e { t.Fatalf("%q = %q, want %q", key, g, e) } refs, err := id.Index.ExistingFileSchemas(wholeRef) if err != nil { t.Fatalf("ExistingFileSchemas = %v", err) } want := []blob.Ref{fileRef} if !reflect.DeepEqual(refs, want) { t.Errorf("ExistingFileSchemas got = %#v, want %#v", refs, want) } } // FileInfo { key := fmt.Sprintf("fileinfo|%s", fileRef) if g, e := id.Get(key), "31|foo.html|text%2Fhtml"; g != e { t.Fatalf("%q = %q, want %q", key, g, e) } fi, err := id.Index.GetFileInfo(fileRef) if err != nil { t.Fatalf("GetFileInfo = %v", err) } if g, e := fi.Size, int64(31); g != e { t.Errorf("Size = %d, want %d", g, e) } if g, e := fi.FileName, "foo.html"; g != e { t.Errorf("FileName = %q, want %q", g, e) } if g, e := fi.MIMEType, "text/html"; g != e { t.Errorf("MIMEType = %q, want %q", g, e) } if g, e := fi.Time, fileTime; !g.Time().Equal(e) { t.Errorf("Time = %v; want %v", g, e) } } } func EdgesTo(t *testing.T, initIdx func() *index.Index) { idx := initIdx() id := NewIndexDeps(idx) id.Fataler = t defer id.DumpIndex(t) // pn1 ---member---> pn2 pn1 := id.NewPermanode() pn2 := id.NewPermanode() claim1 := id.AddAttribute(pn1, "camliMember", pn2.String()) t.Logf("edge %s --> %s", pn1, pn2) // Look for pn1 { edges, err := idx.EdgesTo(pn2, nil) if err != nil { t.Fatal(err) } if len(edges) != 1 { t.Fatalf("num edges = %d; want 1", len(edges)) } wantEdge := &camtypes.Edge{ From: pn1, To: pn2, FromType: "permanode", } if got, want := edges[0].String(), wantEdge.String(); got != want { t.Errorf("Wrong edge.\n GOT: %v\nWANT: %v", got, want) } } // Delete claim -> break edge relationship. del1 := id.Delete(claim1) t.Logf("del claim %q deletes claim %q, breaks link between p1 and p2", del1, claim1) // test that we can't find anymore pn1 from pn2 { edges, err := idx.EdgesTo(pn2, nil) if err != nil { t.Fatal(err) } if len(edges) != 0 { t.Fatalf("num edges = %d; want 0", len(edges)) } } // Undelete, should restore the link. del2 := id.Delete(del1) t.Logf("del claim %q deletes del claim %q, restores link between p1 and p2", del2, del1) { edges, err := idx.EdgesTo(pn2, nil) if err != nil { t.Fatal(err) } if len(edges) != 1 { t.Fatalf("num edges = %d; want 1", len(edges)) } wantEdge := &camtypes.Edge{ From: pn1, To: pn2, FromType: "permanode", } if got, want := edges[0].String(), wantEdge.String(); got != want { t.Errorf("Wrong edge.\n GOT: %v\nWANT: %v", got, want) } } } func Delete(t *testing.T, initIdx func() *index.Index) { idx := initIdx() id := NewIndexDeps(idx) id.Fataler = t defer id.DumpIndex(t) pn1 := id.NewPermanode() t.Logf("uploaded permanode %q", pn1) cl1 := id.SetAttribute(pn1, "tag", "foo1") cl1Time := id.LastTime() t.Logf("set attribute %q", cl1) // delete pn1 delpn1 := id.Delete(pn1) t.Logf("del claim %q deletes %q", delpn1, pn1) deleted := idx.IsDeleted(pn1) if !deleted { t.Fatal("pn1 should be deleted") } // and try to find it with SearchPermanodesWithAttr (which should not work) { ch := make(chan blob.Ref, 10) req := &camtypes.PermanodeByAttrRequest{ Signer: id.SignerBlobRef, Attribute: "tag", Query: "foo1"} err := id.Index.SearchPermanodesWithAttr(ch, req) if err != nil { t.Fatalf("SearchPermanodesWithAttr = %v", err) } var got []blob.Ref for r := range ch { got = append(got, r) } want := []blob.Ref{} if len(got) != len(want) { t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want) } } // delete pn1 again with another claim delpn1bis := id.Delete(pn1) t.Logf("del claim %q deletes %q a second time", delpn1bis, pn1) deleted = idx.IsDeleted(pn1) if !deleted { t.Fatal("pn1 should be deleted") } // verify that deleting delpn1 is not enough to make pn1 undeleted del2 := id.Delete(delpn1) t.Logf("delete claim %q deletes %q, which should not yet revive %q", del2, delpn1, pn1) deleted = idx.IsDeleted(pn1) if !deleted { t.Fatal("pn1 should not yet be undeleted") } // we should not yet be able to find it again with SearchPermanodesWithAttr { ch := make(chan blob.Ref, 10) req := &camtypes.PermanodeByAttrRequest{ Signer: id.SignerBlobRef, Attribute: "tag", Query: "foo1"} err := id.Index.SearchPermanodesWithAttr(ch, req) if err != nil { t.Fatalf("SearchPermanodesWithAttr = %v", err) } var got []blob.Ref for r := range ch { got = append(got, r) } want := []blob.Ref{} if len(got) != len(want) { t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want) } } // delete delpn1bis as well -> should undelete pn1 del2bis := id.Delete(delpn1bis) t.Logf("delete claim %q deletes %q, which should revive %q", del2bis, delpn1bis, pn1) deleted = idx.IsDeleted(pn1) if deleted { t.Fatal("pn1 should be undeleted") } // we should now be able to find it again with SearchPermanodesWithAttr { ch := make(chan blob.Ref, 10) req := &camtypes.PermanodeByAttrRequest{ Signer: id.SignerBlobRef, Attribute: "tag", Query: "foo1"} err := id.Index.SearchPermanodesWithAttr(ch, req) if err != nil { t.Fatalf("SearchPermanodesWithAttr = %v", err) } var got []blob.Ref for r := range ch { got = append(got, r) } want := []blob.Ref{pn1} if len(got) < 1 || got[0].String() != want[0].String() { t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want) } } // Delete cl1 del3 := id.Delete(cl1) t.Logf("del claim %q deletes claim %q", del3, cl1) deleted = idx.IsDeleted(cl1) if !deleted { t.Fatal("cl1 should be deleted") } // we should not find anything with SearchPermanodesWithAttr { ch := make(chan blob.Ref, 10) req := &camtypes.PermanodeByAttrRequest{ Signer: id.SignerBlobRef, Attribute: "tag", Query: "foo1"} err := id.Index.SearchPermanodesWithAttr(ch, req) if err != nil { t.Fatalf("SearchPermanodesWithAttr = %v", err) } var got []blob.Ref for r := range ch { got = append(got, r) } want := []blob.Ref{} if len(got) != len(want) { t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want) } } // and now check that AppendClaims finds nothing for pn { claims, err := id.Index.AppendClaims(nil, pn1, id.SignerBlobRef, "") if err != nil { t.Errorf("AppendClaims = %v", err) } else { want := []camtypes.Claim{} if len(claims) != len(want) { t.Errorf("id.Index.AppendClaims gives %q, want %q", claims, want) } } } // undelete cl1 del4 := id.Delete(del3) t.Logf("del claim %q deletes del claim %q, which should undelete %q", del4, del3, cl1) // We should now be able to find it again with both methods { ch := make(chan blob.Ref, 10) req := &camtypes.PermanodeByAttrRequest{ Signer: id.SignerBlobRef, Attribute: "tag", Query: "foo1"} err := id.Index.SearchPermanodesWithAttr(ch, req) if err != nil { t.Fatalf("SearchPermanodesWithAttr = %v", err) } var got []blob.Ref for r := range ch { got = append(got, r) } want := []blob.Ref{pn1} if len(got) < 1 || got[0].String() != want[0].String() { t.Errorf("id.Index.SearchPermanodesWithAttr gives %q, want %q", got, want) } } // and check that AppendClaims finds cl1, with the right modtime too { claims, err := id.Index.AppendClaims(nil, pn1, id.SignerBlobRef, "") if err != nil { t.Errorf("AppendClaims = %v", err) } else { want := []camtypes.Claim{ camtypes.Claim{ BlobRef: cl1, Permanode: pn1, Signer: id.SignerBlobRef, Date: cl1Time.UTC(), Type: "set-attribute", Attr: "tag", Value: "foo1", }, } if !reflect.DeepEqual(claims, want) { t.Errorf("GetOwnerClaims results differ.\n got: %v\nwant: %v", claims, want) } } } } type searchResults []camtypes.RecentPermanode func (s searchResults) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "[%d search results: ", len(s)) for _, r := range s { fmt.Fprintf(&buf, "{BlobRef: %s, Signer: %s, LastModTime: %d}", r.Permanode, r.Signer, r.LastModTime.Unix()) } buf.WriteString("]") return buf.String() }