0
|
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 }
|