comparison sites/all/modules/custom/solrconnect/tests/solr_index_and_search.test @ 0:015d06b10d37 default tip

initial
author dwinter
date Wed, 31 Jul 2013 13:49:13 +0200
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:015d06b10d37
1 <?php
2
3 abstract class AbstractDrupalSolrOnlineWebTestCase extends DrupalWebTestCase {
4
5 protected $solr;
6
7 protected $solr_available = FALSE; // workaround for drupal.org test bot
8
9 /**
10 * Implementation of setUp().
11 */
12 function setUp() {
13 // Install modules needed for this test. This could have been passed in as
14 // either a single array argument or a variable number of string arguments.
15 $modules = func_get_args();
16 if (isset($modules[0]) && is_array($modules[0])) {
17 $modules = $modules[0];
18 }
19 $modules[] = 'apachesolr';
20 $modules[] = 'apachesolr_search';
21 $modules[] = 'search';
22
23 parent::setUp($modules);
24 }
25
26 function setUpSolr() {
27 // Load the default server.
28 $env_id = apachesolr_default_environment();
29 $environment = apachesolr_environment_load($env_id);
30 $this->base_solr_url = $environment['url'];
31
32 // Because we are in a clean environment, this will always be the default
33 // http://localhost:8983/solr
34 $this->core_admin_url = "{$this->base_solr_url}/admin/cores";
35
36 // The core admin url will give a valid response if the
37 // Solr server is running locally.
38 if ($this->coreAdminAvailable()) {
39
40 // We will use a core named after the simpletest prefix.
41 $environment['url'] .= '/' . $this->databasePrefix;
42
43 $filesdir = file_directory_temp();
44 // Create our Solr core directory.
45 drupal_mkdir("{$filesdir}/solr", 0777, TRUE);
46 // Our temporary core is located here.
47 $instancedir = realpath($filesdir . "/solr");
48
49 // use the Solr version confs where appropriate.
50 $version = $this->getSolrVersion();
51 if (isset($version) && $version == 3) {
52 $conf_path = dirname(__FILE__) . '/../solr-conf/solr-3.x/*';
53 }
54 elseif (isset($version) && $version == 4) {
55 $conf_path = dirname(__FILE__) . '/../solr-conf/solr-4.x/*';
56 }
57 else {
58 $conf_path = dirname(__FILE__) . '/../solr-conf/solr-1.4/*';
59 }
60
61 $patterns = array(
62 $conf_path,
63 dirname(__FILE__) . '/conf/*',
64 );
65
66 // Copy all files in solr-conf dir to our temporary solr core.
67 drupal_mkdir("{$instancedir}/conf", 0777, TRUE);
68 foreach ($patterns as $pattern) {
69 foreach (glob($pattern) as $conf_file) {
70 copy($conf_file, "$instancedir/conf/" . basename($conf_file));
71 }
72 }
73
74 $contents = file_get_contents("$instancedir/conf/solrconfig.xml");
75
76 // Change the autoCommit time down to 1 second.
77 // @todo - use solrcore.properties file for 3.x.
78 file_put_contents("$instancedir/conf/solrconfig.xml", preg_replace('@<maxTime>[0-9]+</maxTime>@', '<maxTime>1000</maxTime>', $contents));
79
80 // hard chmod -R because it seems drupal dirs are too restricted in a
81 // testing environment
82 system("chmod -R 777 {$instancedir}");
83
84 $query['name'] = $this->databasePrefix;
85 $query['instanceDir'] = $instancedir;
86 $created = $this->coreAdmin('CREATE', $query);
87
88 if ($created && apachesolr_server_status($environment['url'])) {
89 $this->instancedir = $instancedir;
90 $this->solr_url = $environment['url'];
91 apachesolr_environment_save($environment);
92 $this->solr = apachesolr_get_solr($env_id);
93 $this->solr_available = TRUE;
94 $this->checkCoreStatus($this->databasePrefix);
95 }
96 }
97 // Workaround for drupal.org test bot.
98 // The tests succeed but further tests will not run because $this->solr_available is FALSE.
99 if (!$this->solr_available) {
100 $this->pass(t('Warning : The solr instance could not be found. Please enable a multicore one on http://localhost:8983/solr'));
101 }
102 }
103
104 protected function coreAdminAvailable() {
105 $url = url($this->core_admin_url, array('query' => array('action' => 'STATUS')));
106 $options['timeout'] = 2;
107 $result = drupal_http_request($url, $options);
108 return ($result->code == 200 && empty($result->error));
109 }
110
111 protected function getSolrVersion() {
112 $status = $this->coreAdmin('STATUS');
113 foreach($status['status'] as $core_id => $core) {
114 $solr = new DrupalApacheSolrService($this->base_solr_url . '/' . $core_id);
115 $version = $solr->getSolrVersion();
116 if (!empty($version)) {
117 return $version;
118 }
119 else {
120 return "1";
121 }
122 }
123 }
124
125 /**
126 * Helper function to invoke core admin actions.
127 */
128 protected function coreAdmin($action, $query = array()) {
129 $query['action'] = $action;
130 $query['wt'] = 'json';
131 $url = url($this->core_admin_url, array('query' => $query));
132 $options['timeout'] = 2;
133 $result = drupal_http_request($url, $options);
134
135 if ($result->code == 200) {
136 return json_decode($result->data, TRUE);
137 }
138 else {
139 return FALSE;
140 }
141 }
142
143 /**
144 * Helper function to verify that the expected core exists.
145 */
146 protected function checkCoreStatus($core_name) {
147 $response = $this->coreAdmin('STATUS', array('core' => $core_name));
148 $this->assertTrue(isset($response['status'][$core_name]['index']), 'Found Solr test core index status');
149 }
150
151 function tearDown() {
152 // Workaround for drupal.org test bot
153 if ($this->solr_available) {
154 // Unload the Solr core & delete all files
155 $query = array(
156 'core' => $this->databasePrefix,
157 'deleteIndex' => 'true',
158 'deleteDataDir' => 'true',
159 'deleteInstanceDir' => 'true'
160 );
161 // This is currently broken due to
162 // https://issues.apache.org/jira/browse/SOLR-3586
163 $this->coreAdmin('UNLOAD', $query);
164
165 }
166 parent::tearDown();
167 }
168 }
169
170
171 class DrupalSolrOnlineWebTestCase extends AbstractDrupalSolrOnlineWebTestCase {
172 /**
173 * Implementation of setUp().
174 */
175 function setUp() {
176 parent::setUp();
177 parent::setUpSolr();
178 }
179 }
180
181
182 class DrupalSolrMatchTestCase extends DrupalSolrOnlineWebTestCase {
183 public static function getInfo() {
184 return array(
185 'name' => 'Solr Index Data and test live queries',
186 'description' => 'Indexes content and queries it.',
187 'group' => 'ApacheSolr',
188 );
189 }
190
191 /**
192 * Test search indexing.
193 */
194 function testMatching() {
195 if ($this->solr_available) { // workaround for drupal.org test bot
196 $this->assertTrue($this->solr->ping(), "The Server could be Pinged");
197 $response = $this->solr->search("*:*", array());
198 $response = $response->response;
199 $this->assertEqual($response->numFound, 0, "There should not be any documents in the index");
200 $this->populateIndex(7);
201 $response = $this->solr->search("*:*", array());
202 $response = $response->response;
203 $this->assertEqual($response->numFound, 7, "There should be 7 documents in the index");
204 $this->_testQueries();
205 }
206 }
207
208 /**
209 * Set up a small index of items to test against.
210 */
211 protected function populateIndex($count) {
212
213 variable_set('minimum_word_size', 3);
214 for ($i = 1; $i <= $count; ++$i) {
215 $documents[] = $this->buildDocument(array('entity_id' => $i, 'content' => $this->getText($i)));
216 }
217 $this->solr->addDocuments($documents);
218 $this->solr->commit();
219 }
220
221 protected function buildDocument($values = array()) {
222 $document = new ApacheSolrDocument();
223 if (!isset($values['entity_type'])) {
224 $values['entity_type'] = 'fake.';
225 }
226 $document->id = apachesolr_document_id($values['entity_id'], $values['entity_type']);
227 foreach ($values as $key => $value) {
228 $document->$key = $value;
229 }
230 return $document;
231 }
232
233 /**
234 * Helper method for generating snippets of content.
235 *
236 * Generated items to test against:
237 * 1 ipsum
238 * 2 dolore sit
239 * 3 sit am ut
240 * 4 am ut enim am
241 * 5 ut enim am minim veniam
242 * 6 enim am minim veniam es cillum
243 * 7 am minim veniam es cillum dolore eu
244 */
245 function getText($n) {
246 // Start over after 7.
247 $n = $n % 7;
248 $words = explode(' ', "Ipsum dolore sit am. Ut enim am minim veniam. Es cillum dolore eu.");
249 return implode(' ', array_slice($words, $n - 1, $n));
250 }
251
252 /**
253 * Run predefine queries looking for indexed terms.
254 */
255 function _testQueries() {
256 /*
257 Note: OR queries that include short words in OR groups are only accepted
258 if the ORed terms are ANDed with at least one long word in the rest of the query.
259
260 e.g. enim dolore OR ut = enim (dolore OR ut) = (enim dolor) OR (enim ut) -> good
261 e.g. dolore OR ut = (dolore) OR (ut) -> bad
262
263 This is a design limitation to avoid full table scans.
264
265 APACHESOLR NOTE: These are not all in lucene syntax... @TODO. Still works for text searching
266 */
267 $queries = array(
268 // Simple AND queries.
269 'ipsum' => array(1),
270 'enim' => array(4, 5, 6),
271 'xxxxx' => array(),
272 // Mixed queries.
273 '"minim am veniam es" OR "dolore sit"' => array(2),
274 '"minim am veniam es" OR "sit dolore"' => array(),
275 '"am minim veniam es" -eu' => array(6),
276 '"am minim veniam" -"cillum dolore"' => array(5, 6),
277 );
278 $broken = array(
279 'enim minim' => array(5, 6),
280 'enim xxxxx' => array(),
281 'dolore eu' => array(7),
282 'dolore xx' => array(),
283 'ut minim' => array(5),
284 'xx minim' => array(),
285 'enim veniam am minim ut' => array(5),
286 // Simple OR queries.
287 'dolore OR ipsum' => array(1, 2, 7),
288 'dolore OR xxxxx' => array(2, 7),
289 'dolore OR ipsum OR enim' => array(1, 2, 4, 5, 6, 7),
290 'minim dolore OR ipsum OR enim' => array(5, 6, 7),
291 'xxxxx dolore OR ipsum' => array(),
292 // Negative queries.
293 'dolore -sit' => array(7),
294 'dolore -eu' => array(2),
295 'dolore -xxxxx' => array(2, 7),
296 'dolore -xx' => array(2, 7),
297 // Phrase queries.
298 '"dolore sit"' => array(2),
299 '"sit dolore"' => array(),
300 '"am minim veniam es"' => array(6, 7),
301 '"minim am veniam es"' => array(),
302 'xxxxx "minim am veniam es" OR dolore' => array(),
303 'xx "minim am veniam es" OR dolore' => array(),
304 // Mixed queries.
305 '"am minim veniam es" OR dolore' => array(2, 6, 7),
306 '"am minim veniam" -"dolore cillum"' => array(5, 6, 7),
307 );
308 foreach ($queries as $query => $results) {
309 $response = $this->solr->search($query, array());
310 $this->_testQueryMatching($query, $response->response->docs, $results);
311 //@TODO: We might get to this later
312 #$this->_testQueryScores($query, $response->responses->docs, $results);
313 }
314 }
315
316 /**
317 * Test the matching abilities of the engine.
318 *
319 * Verify if a query produces the correct results.
320 */
321 function _testQueryMatching($query, $set, $results) {
322 // Get result IDs.
323 $found = array();
324 foreach ($set as $item) {
325 $found[] = $item->entity_id;
326 }
327 // Compare $results and $found.
328 sort($found);
329 sort($results);
330 $this->assertEqual($found, $results, strtr("Query matching '$query' found: @found expected: @expected", array('@found' => implode(',', $found), '@expected' => implode(',', $results))));
331 }
332
333 /**
334 * Test the scoring abilities of the engine.
335 *
336 * Verify if a query produces normalized, monotonous scores.
337 */
338 function _testQueryScores($query, $set, $results) {
339 // Get result scores.
340 $scores = array();
341 foreach ($set as $item) {
342 $scores[] = $item->score;
343 }
344
345 // Check order.
346 $sorted = $scores;
347 sort($sorted);
348 $this->assertEqual($scores, array_reverse($sorted), "Query order '$query'");
349
350 // Check range.
351 $this->assertEqual(!count($scores) || (min($scores) > 0.0 && max($scores) <= 1.0001), TRUE, "Query scoring '$query'");
352 }
353
354 }