perkeep/pkg/search/describe_test.go

442 lines
10 KiB
Go

/*
Copyright 2014 The Camlistore Authors
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 search_test
import (
"fmt"
"testing"
"camlistore.org/pkg/blob"
"camlistore.org/pkg/index"
"camlistore.org/pkg/index/indextest"
"camlistore.org/pkg/search"
"camlistore.org/pkg/test"
"camlistore.org/pkg/types/camtypes"
"golang.org/x/net/context"
)
func addPermanode(fi *test.FakeIndex, pnStr string, attrs ...string) {
pn := blob.MustParse(pnStr)
fi.AddMeta(pn, "permanode", 123)
for len(attrs) > 0 {
k, v := attrs[0], attrs[1]
attrs = attrs[2:]
fi.AddClaim(owner, pn, "add-attribute", k, v)
}
}
func addFileWithLocation(fi *test.FakeIndex, fileStr string, lat, long float64) {
fileRef := blob.MustParse(fileStr)
fi.AddFileLocation(fileRef, camtypes.Location{Latitude: lat, Longitude: long})
fi.AddMeta(fileRef, "file", 123)
}
func searchDescribeSetup(fi *test.FakeIndex) index.Interface {
addPermanode(fi, "abc-123",
"camliContent", "abc-123c",
"camliImageContent", "abc-888",
)
addPermanode(fi, "abc-123c",
"camliContent", "abc-123cc",
"camliImageContent", "abc-123c1",
)
addPermanode(fi, "abc-123c1",
"some", "image",
)
addPermanode(fi, "abc-123cc",
"name", "leaf",
)
addPermanode(fi, "abc-888",
"camliContent", "abc-8881",
)
addPermanode(fi, "abc-8881",
"name", "leaf8881",
)
addPermanode(fi, "fourcheckin-0",
"camliNodeType", "foursquare.com:checkin",
"foursquareVenuePermanode", "fourvenue-123",
)
addPermanode(fi, "fourvenue-123",
"camliNodeType", "foursquare.com:venue",
"camliPath:photos", "venuepicset-123",
"latitude", "12",
"longitude", "34",
)
addPermanode(fi, "venuepicset-123",
"camliPath:1.jpg", "venuepic-1",
)
addPermanode(fi, "venuepic-1",
"camliContent", "somevenuepic-0",
)
addPermanode(fi, "somevenuepic-0",
"foo", "bar",
)
addPermanode(fi, "venuepic-2",
"camliContent", "somevenuepic-2",
)
addPermanode(fi, "somevenuepic-2",
"foo", "baz",
)
addPermanode(fi, "homedir-0",
"camliPath:subdir.1", "homedir-1",
)
addPermanode(fi, "homedir-1",
"camliPath:subdir.2", "homedir-2",
)
addPermanode(fi, "homedir-2",
"foo", "bar",
)
addPermanode(fi, "set-0",
"camliMember", "venuepic-1",
"camliMember", "venuepic-2",
)
addFileWithLocation(fi, "filewithloc-0", 45, 56)
addPermanode(fi, "location-0",
"camliContent", "filewithloc-0",
)
addPermanode(fi, "locationpriority-1",
"latitude", "67",
"longitude", "78",
"camliNodeType", "foursquare.com:checkin",
"foursquareVenuePermanode", "fourvenue-123",
"camliContent", "filewithloc-0",
)
addPermanode(fi, "locationpriority-2",
"camliNodeType", "foursquare.com:checkin",
"foursquareVenuePermanode", "fourvenue-123",
"camliContent", "filewithloc-0",
)
addPermanode(fi, "locationoverride-1",
"latitude", "67",
"longitude", "78",
"camliContent", "filewithloc-0",
)
addPermanode(fi, "locationoverride-2",
"latitude", "67",
"longitude", "78",
"camliNodeType", "foursquare.com:checkin",
"foursquareVenuePermanode", "fourvenue-123",
)
return fi
}
var searchDescribeTests = []handlerTest{
{
name: "null",
postBody: marshalJSON(&search.DescribeRequest{}),
want: jmap(&search.DescribeResponse{
Meta: search.MetaMap{},
}),
},
{
name: "single",
postBody: marshalJSON(&search.DescribeRequest{
BlobRef: blob.MustParse("abc-123"),
}),
wantDescribed: []string{"abc-123"},
},
{
name: "follow all camliContent",
postBody: marshalJSON(&search.DescribeRequest{
BlobRef: blob.MustParse("abc-123"),
Rules: []*search.DescribeRule{
{
Attrs: []string{"camliContent"},
},
},
}),
wantDescribed: []string{"abc-123", "abc-123c", "abc-123cc"},
},
{
name: "follow only root camliContent",
postBody: marshalJSON(&search.DescribeRequest{
BlobRef: blob.MustParse("abc-123"),
Rules: []*search.DescribeRule{
{
IfResultRoot: true,
Attrs: []string{"camliContent"},
},
},
}),
wantDescribed: []string{"abc-123", "abc-123c"},
},
{
name: "follow all root, substring",
postBody: marshalJSON(&search.DescribeRequest{
BlobRef: blob.MustParse("abc-123"),
Rules: []*search.DescribeRule{
{
IfResultRoot: true,
Attrs: []string{"camli*"},
},
},
}),
wantDescribed: []string{"abc-123", "abc-123c", "abc-888"},
},
{
name: "two rules, two attrs",
postBody: marshalJSON(&search.DescribeRequest{
BlobRef: blob.MustParse("abc-123"),
Rules: []*search.DescribeRule{
{
IfResultRoot: true,
Attrs: []string{"camliContent", "camliImageContent"},
},
{
Attrs: []string{"camliContent"},
},
},
}),
wantDescribed: []string{"abc-123", "abc-123c", "abc-123cc", "abc-888", "abc-8881"},
},
{
name: "foursquare venue photos, but not recursive camliPath explosion",
postBody: marshalJSON(&search.DescribeRequest{
BlobRefs: []blob.Ref{
blob.MustParse("homedir-0"),
blob.MustParse("fourcheckin-0"),
},
Rules: []*search.DescribeRule{
{
Attrs: []string{"camliContent", "camliContentImage"},
},
{
IfCamliNodeType: "foursquare.com:checkin",
Attrs: []string{"foursquareVenuePermanode"},
},
{
IfCamliNodeType: "foursquare.com:venue",
Attrs: []string{"camliPath:photos"},
Rules: []*search.DescribeRule{
{
Attrs: []string{"camliPath:*"},
},
},
},
},
}),
wantDescribed: []string{"homedir-0", "fourcheckin-0", "fourvenue-123", "venuepicset-123", "venuepic-1", "somevenuepic-0"},
},
{
name: "home dirs forever",
postBody: marshalJSON(&search.DescribeRequest{
BlobRefs: []blob.Ref{
blob.MustParse("homedir-0"),
},
Rules: []*search.DescribeRule{
{
Attrs: []string{"camliPath:*"},
},
},
}),
wantDescribed: []string{"homedir-0", "homedir-1", "homedir-2"},
},
{
name: "find members",
postBody: marshalJSON(&search.DescribeRequest{
BlobRef: blob.MustParse("set-0"),
Rules: []*search.DescribeRule{
{
IfResultRoot: true,
Attrs: []string{"camliMember"},
Rules: []*search.DescribeRule{
{Attrs: []string{"camliContent"}},
},
},
},
}),
wantDescribed: []string{"set-0", "venuepic-1", "venuepic-2", "somevenuepic-0", "somevenuepic-2"},
},
}
func init() {
checkNoDups("searchDescribeTests", searchDescribeTests)
}
func TestSearchDescribe(t *testing.T) {
for _, ht := range searchDescribeTests {
if ht.setup == nil {
ht.setup = searchDescribeSetup
}
if ht.query == "" {
ht.query = "describe"
}
ht.test(t)
}
}
// should be run with -race
func TestDescribeRace(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
idx := index.NewMemoryIndex()
idxd := indextest.NewIndexDeps(idx)
idxd.Fataler = t
corpus, err := idxd.Index.KeepInMemory()
if err != nil {
t.Fatalf("error slurping index to memory: %v", err)
}
h := search.NewHandler(idx, idxd.SignerBlobRef)
h.SetCorpus(corpus)
donec := make(chan struct{})
headstart := 500
blobrefs := make([]blob.Ref, headstart)
headstartc := make(chan struct{})
go func() {
for i := 0; i < headstart*2; i++ {
nth := fmt.Sprintf("%d", i)
// No need to lock the index here. It is already done within NewPlannedPermanode,
// because it calls idxd.Index.ReceiveBlob.
pn := idxd.NewPlannedPermanode(nth)
idxd.SetAttribute(pn, "tag", nth)
if i > headstart {
continue
}
if i == headstart {
headstartc <- struct{}{}
continue
}
blobrefs[i] = pn
}
}()
<-headstartc
ctx := context.Background()
go func() {
for i := 0; i < headstart; i++ {
br := blobrefs[i]
res, err := h.Describe(ctx, &search.DescribeRequest{
BlobRef: br,
Depth: 1,
})
if err != nil {
t.Fatal(err)
}
_, ok := res.Meta[br.String()]
if !ok {
t.Errorf("permanode %v wasn't in Describe response", br)
}
}
donec <- struct{}{}
}()
<-donec
}
func TestDescribeLocation(t *testing.T) {
tests := []struct {
ref string
lat, long float64
hasNoLoc bool
}{
{ref: "filewithloc-0", lat: 45, long: 56},
{ref: "location-0", lat: 45, long: 56},
{ref: "locationpriority-1", lat: 67, long: 78},
{ref: "locationpriority-2", lat: 12, long: 34},
{ref: "locationoverride-1", lat: 67, long: 78},
{ref: "locationoverride-2", lat: 67, long: 78},
{ref: "homedir-0", hasNoLoc: true},
}
ix := searchDescribeSetup(test.NewFakeIndex())
ctx := context.Background()
h := search.NewHandler(ix, owner)
ix.RLock()
defer ix.RUnlock()
for _, tt := range tests {
var err error
br := blob.MustParse(tt.ref)
res, err := h.Describe(ctx, &search.DescribeRequest{
BlobRef: br,
Depth: 1,
})
if err != nil {
t.Errorf("Describe for %v failed: %v", br, err)
continue
}
db := res.Meta[br.String()]
if db == nil {
t.Errorf("Describe result for %v is missing", br)
continue
}
loc := db.Location
if tt.hasNoLoc {
if loc != nil {
t.Errorf("got location for %v, should have no location", br)
}
} else {
if loc == nil {
t.Errorf("no location in result for %v", br)
continue
}
if loc.Latitude != tt.lat || loc.Longitude != tt.long {
t.Errorf("location for %v invalid, got %f,%f want %f,%f",
tt.ref, loc.Latitude, loc.Longitude, tt.lat, tt.long)
}
}
}
}
// To make sure we don't regress into issue 881: i.e. a permanode with no attr
// should not lead us to call index.claimsIntfAttrValue with a nil claims argument.
func TestDescribePermNoAttr(t *testing.T) {
ix := index.NewMemoryIndex()
ctx := context.Background()
h := search.NewHandler(ix, owner)
corpus, err := ix.KeepInMemory()
if err != nil {
t.Fatal(err)
}
h.SetCorpus(corpus)
id := indextest.NewIndexDeps(ix)
br := id.NewPlannedPermanode("noattr-0")
ix.RLock()
defer ix.RUnlock()
res, err := h.Describe(ctx, &search.DescribeRequest{
BlobRef: br,
Depth: 1,
})
if err != nil {
t.Fatalf("Describe for %v failed: %v", br, err)
}
db := res.Meta[br.String()]
if db == nil {
t.Fatalf("Describe result for %v is missing", br)
}
}