1//  Copyright (c) 2014 Couchbase, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// 		http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package test
16
17import (
18	"encoding/json"
19	"flag"
20	"io/ioutil"
21	"math"
22	"os"
23	"path/filepath"
24	"reflect"
25	"regexp"
26	"testing"
27
28	"github.com/blevesearch/bleve"
29	"github.com/blevesearch/bleve/mapping"
30
31	// allow choosing alternate kvstores
32	_ "github.com/blevesearch/bleve/config"
33)
34
35var dataset = flag.String("dataset", "", "only test datasets matching this regex")
36var onlynum = flag.Int("testnum", -1, "only run the test with this number")
37var keepIndex = flag.Bool("keepIndex", false, "keep the index after testing")
38
39var indexType = flag.String("indexType", bleve.Config.DefaultIndexType, "index type to build")
40var kvType = flag.String("kvType", bleve.Config.DefaultKVStore, "kv store type to build")
41
42func TestIntegration(t *testing.T) {
43
44	flag.Parse()
45
46	bleve.Config.DefaultIndexType = *indexType
47	bleve.Config.DefaultKVStore = *kvType
48	t.Logf("using index type %s and kv type %s", *indexType, *kvType)
49
50	var err error
51	var datasetRegexp *regexp.Regexp
52	if *dataset != "" {
53		datasetRegexp, err = regexp.Compile(*dataset)
54		if err != nil {
55			t.Fatal(err)
56		}
57	}
58
59	fis, err := ioutil.ReadDir("tests")
60	if err != nil {
61		t.Fatal(err)
62	}
63	for _, fi := range fis {
64		if datasetRegexp != nil {
65			if !datasetRegexp.MatchString(fi.Name()) {
66				continue
67			}
68		}
69		if fi.IsDir() {
70			t.Logf("Running test: %s", fi.Name())
71			runTestDir(t, "tests"+string(filepath.Separator)+fi.Name(), fi.Name())
72		}
73	}
74}
75
76func runTestDir(t *testing.T, dir, datasetName string) {
77	// read the mapping
78	mappingBytes, err := ioutil.ReadFile(dir + string(filepath.Separator) + "mapping.json")
79	if err != nil {
80		t.Errorf("error reading mapping: %v", err)
81		return
82	}
83	var mapping mapping.IndexMappingImpl
84	err = json.Unmarshal(mappingBytes, &mapping)
85	if err != nil {
86		t.Errorf("error unmarshalling mapping: %v", err)
87		return
88	}
89
90	// open new index
91	if !*keepIndex {
92		defer func() {
93			err := os.RemoveAll("test.bleve")
94			if err != nil {
95				t.Fatal(err)
96			}
97		}()
98	}
99	index, err := bleve.New("test.bleve", &mapping)
100	if err != nil {
101		t.Errorf("error creating new index: %v", err)
102		return
103	}
104	// set a custom index name
105	index.SetName(datasetName)
106	defer func() {
107		err := index.Close()
108		if err != nil {
109			t.Fatal(err)
110		}
111	}()
112
113	// index data
114	fis, err := ioutil.ReadDir(dir + string(filepath.Separator) + "data")
115	if err != nil {
116		t.Errorf("error reading data dir: %v", err)
117		return
118	}
119	for _, fi := range fis {
120		fileBytes, err := ioutil.ReadFile(dir + string(filepath.Separator) + "data" + string(filepath.Separator) + fi.Name())
121		if err != nil {
122			t.Errorf("error reading data file: %v", err)
123			return
124		}
125		var fileDoc interface{}
126		err = json.Unmarshal(fileBytes, &fileDoc)
127		if err != nil {
128			t.Errorf("error parsing data file as json: %v", err)
129		}
130		filename := fi.Name()
131		ext := filepath.Ext(filename)
132		id := filename[0 : len(filename)-len(ext)]
133		err = index.Index(id, fileDoc)
134		if err != nil {
135			t.Errorf("error indexing data: %v", err)
136			return
137		}
138	}
139
140	// read the searches
141	searchBytes, err := ioutil.ReadFile(dir + string(filepath.Separator) + "searches.json")
142	if err != nil {
143		t.Errorf("error reading searches: %v", err)
144		return
145	}
146	var searches SearchTests
147	err = json.Unmarshal(searchBytes, &searches)
148	if err != nil {
149		t.Errorf("error unmarshalling searches: %v", err)
150		return
151	}
152
153	// run the searches
154	for testNum, search := range searches {
155		if *onlynum < 0 || (*onlynum > 0 && testNum == *onlynum) {
156			res, err := index.Search(search.Search)
157			if err != nil {
158				t.Errorf("error running search: %v", err)
159			}
160			if res.Total != search.Result.Total {
161				t.Errorf("test error - %s", search.Comment)
162				t.Errorf("test %d - expected total: %d got %d", testNum, search.Result.Total, res.Total)
163				continue
164			}
165			if len(res.Hits) != len(search.Result.Hits) {
166				t.Errorf("test error - %s", search.Comment)
167				t.Errorf("test %d - expected hits len: %d got %d", testNum, len(search.Result.Hits), len(res.Hits))
168				continue
169			}
170			for hi, hit := range search.Result.Hits {
171				if hit.ID != res.Hits[hi].ID {
172					t.Errorf("test error - %s", search.Comment)
173					t.Errorf("test %d - expected hit %d to have ID %s got %s", testNum, hi, hit.ID, res.Hits[hi].ID)
174				}
175				if hit.Fields != nil {
176					if !reflect.DeepEqual(hit.Fields, res.Hits[hi].Fields) {
177						t.Errorf("test error - %s", search.Comment)
178						t.Errorf("test  %d - expected hit %d to have fields %#v got %#v", testNum, hi, hit.Fields, res.Hits[hi].Fields)
179					}
180				}
181				if hit.Fragments != nil {
182					if !reflect.DeepEqual(hit.Fragments, res.Hits[hi].Fragments) {
183						t.Errorf("test error - %s", search.Comment)
184						t.Errorf("test %d - expected hit %d to have fragments %#v got %#v", testNum, hi, hit.Fragments, res.Hits[hi].Fragments)
185					}
186				}
187				if hit.Locations != nil {
188					if !reflect.DeepEqual(hit.Locations, res.Hits[hi].Locations) {
189						t.Errorf("test error - %s", search.Comment)
190						t.Errorf("test %d - expected hit %d to have locations %v got %v", testNum, hi, hit.Locations, res.Hits[hi].Locations)
191					}
192				}
193				// assert that none of the scores were NaN,+Inf,-Inf
194				if math.IsInf(res.Hits[hi].Score, 0) || math.IsNaN(res.Hits[hi].Score) {
195					t.Errorf("test error - %s", search.Comment)
196					t.Errorf("test %d - invalid score %f", testNum, res.Hits[hi].Score)
197				}
198			}
199			if search.Result.Facets != nil {
200				if !reflect.DeepEqual(search.Result.Facets, res.Facets) {
201					t.Errorf("test error - %s", search.Comment)
202					t.Errorf("test %d - expected facets: %#v got %#v", testNum, search.Result.Facets, res.Facets)
203				}
204			}
205			// check that custom index name is in results
206			for _, hit := range res.Hits {
207				if hit.Index != datasetName {
208					t.Fatalf("expected name: %s, got: %s", datasetName, hit.Index)
209				}
210			}
211		}
212	}
213}
214