Chris@0: installSchema('search', ['search_index', 'search_dataset', 'search_total']); Chris@0: $this->installConfig(['search']); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test search indexing. Chris@0: */ Chris@0: public function testMatching() { Chris@0: $this->_setup(); Chris@0: $this->_testQueries(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set up a small index of items to test against. Chris@0: */ Chris@0: public function _setup() { Chris@0: $this->config('search.settings')->set('index.minimum_word_size', 3)->save(); Chris@0: Chris@0: for ($i = 1; $i <= 7; ++$i) { Chris@0: search_index(static::SEARCH_TYPE, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText($i)); Chris@0: } Chris@0: for ($i = 1; $i <= 5; ++$i) { Chris@0: search_index(static::SEARCH_TYPE_2, $i + 7, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText2($i)); Chris@0: } Chris@0: // No getText builder function for Japanese text; just a simple array. Chris@0: foreach ([ Chris@0: 13 => '以呂波耳・ほへとち。リヌルヲ。', Chris@0: 14 => 'ドルーパルが大好きよ!', Chris@0: 15 => 'コーヒーとケーキ', Chris@0: ] as $i => $jpn) { Chris@0: search_index(static::SEARCH_TYPE_JPN, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $jpn); Chris@0: } Chris@0: search_update_totals(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * _test_: Helper method for generating snippets of content. Chris@0: * Chris@0: * Generated items to test against: Chris@0: * 1 ipsum Chris@0: * 2 dolore sit Chris@0: * 3 sit am ut Chris@0: * 4 am ut enim am Chris@0: * 5 ut enim am minim veniam Chris@0: * 6 enim am minim veniam es cillum Chris@0: * 7 am minim veniam es cillum dolore eu Chris@0: */ Chris@0: public function getText($n) { Chris@0: $words = explode(' ', "Ipsum dolore sit am. Ut enim am minim veniam. Es cillum dolore eu."); Chris@0: return implode(' ', array_slice($words, $n - 1, $n)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * _test2_: Helper method for generating snippets of content. Chris@0: * Chris@0: * Generated items to test against: Chris@0: * 8 dear Chris@0: * 9 king philip Chris@0: * 10 philip came over Chris@0: * 11 came over from germany Chris@0: * 12 over from germany swimming Chris@0: */ Chris@0: public function getText2($n) { Chris@0: $words = explode(' ', "Dear King Philip came over from Germany swimming."); Chris@0: return implode(' ', array_slice($words, $n - 1, $n)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Run predefine queries looking for indexed terms. Chris@0: */ Chris@0: public function _testQueries() { Chris@0: // Note: OR queries that include short words in OR groups are only accepted Chris@0: // if the ORed terms are ANDed with at least one long word in the rest of Chris@0: // the query. Examples: Chris@0: // enim dolore OR ut = enim (dolore OR ut) = (enim dolor) OR (enim ut) Chris@0: // is good, and Chris@0: // dolore OR ut = (dolore) OR (ut) Chris@0: // is bad. This is a design limitation to avoid full table scans. Chris@0: $queries = [ Chris@0: // Simple AND queries. Chris@0: 'ipsum' => [1], Chris@0: 'enim' => [4, 5, 6], Chris@0: 'xxxxx' => [], Chris@0: 'enim minim' => [5, 6], Chris@0: 'enim xxxxx' => [], Chris@0: 'dolore eu' => [7], Chris@0: 'dolore xx' => [], Chris@0: 'ut minim' => [5], Chris@0: 'xx minim' => [], Chris@0: 'enim veniam am minim ut' => [5], Chris@0: // Simple OR and AND/OR queries. Chris@0: 'dolore OR ipsum' => [1, 2, 7], Chris@0: 'dolore OR xxxxx' => [2, 7], Chris@0: 'dolore OR ipsum OR enim' => [1, 2, 4, 5, 6, 7], Chris@0: 'ipsum OR dolore sit OR cillum' => [2, 7], Chris@0: 'minim dolore OR ipsum' => [7], Chris@0: 'dolore OR ipsum veniam' => [7], Chris@0: 'minim dolore OR ipsum OR enim' => [5, 6, 7], Chris@0: 'dolore xx OR yy' => [], Chris@0: 'xxxxx dolore OR ipsum' => [], Chris@0: // Sequence of OR queries. Chris@0: 'minim' => [5, 6, 7], Chris@0: 'minim OR xxxx' => [5, 6, 7], Chris@0: 'minim OR xxxx OR minim' => [5, 6, 7], Chris@0: 'minim OR xxxx minim' => [5, 6, 7], Chris@0: 'minim OR xxxx minim OR yyyy' => [5, 6, 7], Chris@0: 'minim OR xxxx minim OR cillum' => [6, 7, 5], Chris@0: 'minim OR xxxx minim OR xxxx' => [5, 6, 7], Chris@0: // Negative queries. Chris@0: 'dolore -sit' => [7], Chris@0: 'dolore -eu' => [2], Chris@0: 'dolore -xxxxx' => [2, 7], Chris@0: 'dolore -xx' => [2, 7], Chris@0: // Phrase queries. Chris@0: '"dolore sit"' => [2], Chris@0: '"sit dolore"' => [], Chris@0: '"am minim veniam es"' => [6, 7], Chris@0: '"minim am veniam es"' => [], Chris@0: // Mixed queries. Chris@0: '"am minim veniam es" OR dolore' => [2, 6, 7], Chris@0: '"minim am veniam es" OR "dolore sit"' => [2], Chris@0: '"minim am veniam es" OR "sit dolore"' => [], Chris@0: '"am minim veniam es" -eu' => [6], Chris@0: '"am minim veniam" -"cillum dolore"' => [5, 6], Chris@0: '"am minim veniam" -"dolore cillum"' => [5, 6, 7], Chris@0: 'xxxxx "minim am veniam es" OR dolore' => [], Chris@0: 'xx "minim am veniam es" OR dolore' => [] Chris@0: ]; Chris@0: foreach ($queries as $query => $results) { Chris@0: $result = db_select('search_index', 'i') Chris@0: ->extend('Drupal\search\SearchQuery') Chris@0: ->searchExpression($query, static::SEARCH_TYPE) Chris@0: ->execute(); Chris@0: Chris@0: $set = $result ? $result->fetchAll() : []; Chris@0: $this->_testQueryMatching($query, $set, $results); Chris@0: $this->_testQueryScores($query, $set, $results); Chris@0: } Chris@0: Chris@0: // These queries are run against the second index type, SEARCH_TYPE_2. Chris@0: $queries = [ Chris@0: // Simple AND queries. Chris@0: 'ipsum' => [], Chris@0: 'enim' => [], Chris@0: 'enim minim' => [], Chris@0: 'dear' => [8], Chris@0: 'germany' => [11, 12], Chris@0: ]; Chris@0: foreach ($queries as $query => $results) { Chris@0: $result = db_select('search_index', 'i') Chris@0: ->extend('Drupal\search\SearchQuery') Chris@0: ->searchExpression($query, static::SEARCH_TYPE_2) Chris@0: ->execute(); Chris@0: Chris@0: $set = $result ? $result->fetchAll() : []; Chris@0: $this->_testQueryMatching($query, $set, $results); Chris@0: $this->_testQueryScores($query, $set, $results); Chris@0: } Chris@0: Chris@0: // These queries are run against the third index type, SEARCH_TYPE_JPN. Chris@0: $queries = [ Chris@0: // Simple AND queries. Chris@0: '呂波耳' => [13], Chris@0: '以呂波耳' => [13], Chris@0: 'ほへと ヌルヲ' => [13], Chris@0: 'とちリ' => [], Chris@0: 'ドルーパル' => [14], Chris@0: 'パルが大' => [14], Chris@0: 'コーヒー' => [15], Chris@0: 'ヒーキ' => [], Chris@0: ]; Chris@0: foreach ($queries as $query => $results) { Chris@0: $result = db_select('search_index', 'i') Chris@0: ->extend('Drupal\search\SearchQuery') Chris@0: ->searchExpression($query, static::SEARCH_TYPE_JPN) Chris@0: ->execute(); Chris@0: Chris@0: $set = $result ? $result->fetchAll() : []; Chris@0: $this->_testQueryMatching($query, $set, $results); Chris@0: $this->_testQueryScores($query, $set, $results); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test the matching abilities of the engine. Chris@0: * Chris@0: * Verify if a query produces the correct results. Chris@0: */ Chris@0: public function _testQueryMatching($query, $set, $results) { Chris@0: // Get result IDs. Chris@0: $found = []; Chris@0: foreach ($set as $item) { Chris@0: $found[] = $item->sid; Chris@0: } Chris@0: Chris@0: // Compare $results and $found. Chris@0: sort($found); Chris@0: sort($results); Chris@0: $this->assertEqual($found, $results, "Query matching '$query'"); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test the scoring abilities of the engine. Chris@0: * Chris@0: * Verify if a query produces normalized, monotonous scores. Chris@0: */ Chris@0: public function _testQueryScores($query, $set, $results) { Chris@0: // Get result scores. Chris@0: $scores = []; Chris@0: foreach ($set as $item) { Chris@0: $scores[] = $item->calculated_score; Chris@0: } Chris@0: Chris@0: // Check order. Chris@0: $sorted = $scores; Chris@0: sort($sorted); Chris@0: $this->assertEqual($scores, array_reverse($sorted), "Query order '$query'"); Chris@0: Chris@0: // Check range. Chris@0: $this->assertEqual(!count($scores) || (min($scores) > 0.0 && max($scores) <= 1.0001), TRUE, "Query scoring '$query'"); Chris@0: } Chris@0: Chris@0: }