mirror of https://github.com/perkeep/perkeep.git
442 lines
10 KiB
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)
|
|
}
|
|
}
|