3 namespace Drupal\Tests\search\Kernel;
5 use Drupal\Core\Language\LanguageInterface;
6 use Drupal\KernelTests\KernelTestBase;
9 * Indexes content and queries it.
13 class SearchMatchTest extends KernelTestBase {
15 // The search index can contain different types of content. Typically the type
16 // is 'node'. Here we test with _test_ and _test2_ as the type.
17 const SEARCH_TYPE = '_test_';
18 const SEARCH_TYPE_2 = '_test2_';
19 const SEARCH_TYPE_JPN = '_test3_';
26 public static $modules = ['search'];
31 protected function setUp() {
33 $this->installSchema('search', ['search_index', 'search_dataset', 'search_total']);
34 $this->installConfig(['search']);
38 * Test search indexing.
40 public function testMatching() {
42 $this->_testQueries();
46 * Set up a small index of items to test against.
48 public function _setup() {
49 $this->config('search.settings')->set('index.minimum_word_size', 3)->save();
51 for ($i = 1; $i <= 7; ++$i) {
52 search_index(static::SEARCH_TYPE, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText($i));
54 for ($i = 1; $i <= 5; ++$i) {
55 search_index(static::SEARCH_TYPE_2, $i + 7, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText2($i));
57 // No getText builder function for Japanese text; just a simple array.
59 13 => '以呂波耳・ほへとち。リヌルヲ。',
63 search_index(static::SEARCH_TYPE_JPN, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $jpn);
65 search_update_totals();
69 * _test_: Helper method for generating snippets of content.
71 * Generated items to test against:
76 * 5 ut enim am minim veniam
77 * 6 enim am minim veniam es cillum
78 * 7 am minim veniam es cillum dolore eu
80 public function getText($n) {
81 $words = explode(' ', "Ipsum dolore sit am. Ut enim am minim veniam. Es cillum dolore eu.");
82 return implode(' ', array_slice($words, $n - 1, $n));
86 * _test2_: Helper method for generating snippets of content.
88 * Generated items to test against:
92 * 11 came over from germany
93 * 12 over from germany swimming
95 public function getText2($n) {
96 $words = explode(' ', "Dear King Philip came over from Germany swimming.");
97 return implode(' ', array_slice($words, $n - 1, $n));
101 * Run predefine queries looking for indexed terms.
103 public function _testQueries() {
104 // Note: OR queries that include short words in OR groups are only accepted
105 // if the ORed terms are ANDed with at least one long word in the rest of
106 // the query. Examples:
107 // enim dolore OR ut = enim (dolore OR ut) = (enim dolor) OR (enim ut)
109 // dolore OR ut = (dolore) OR (ut)
110 // is bad. This is a design limitation to avoid full table scans.
112 // Simple AND queries.
116 'enim minim' => [5, 6],
122 'enim veniam am minim ut' => [5],
123 // Simple OR and AND/OR queries.
124 'dolore OR ipsum' => [1, 2, 7],
125 'dolore OR xxxxx' => [2, 7],
126 'dolore OR ipsum OR enim' => [1, 2, 4, 5, 6, 7],
127 'ipsum OR dolore sit OR cillum' => [2, 7],
128 'minim dolore OR ipsum' => [7],
129 'dolore OR ipsum veniam' => [7],
130 'minim dolore OR ipsum OR enim' => [5, 6, 7],
131 'dolore xx OR yy' => [],
132 'xxxxx dolore OR ipsum' => [],
133 // Sequence of OR queries.
134 'minim' => [5, 6, 7],
135 'minim OR xxxx' => [5, 6, 7],
136 'minim OR xxxx OR minim' => [5, 6, 7],
137 'minim OR xxxx minim' => [5, 6, 7],
138 'minim OR xxxx minim OR yyyy' => [5, 6, 7],
139 'minim OR xxxx minim OR cillum' => [6, 7, 5],
140 'minim OR xxxx minim OR xxxx' => [5, 6, 7],
142 'dolore -sit' => [7],
144 'dolore -xxxxx' => [2, 7],
145 'dolore -xx' => [2, 7],
147 '"dolore sit"' => [2],
148 '"sit dolore"' => [],
149 '"am minim veniam es"' => [6, 7],
150 '"minim am veniam es"' => [],
152 '"am minim veniam es" OR dolore' => [2, 6, 7],
153 '"minim am veniam es" OR "dolore sit"' => [2],
154 '"minim am veniam es" OR "sit dolore"' => [],
155 '"am minim veniam es" -eu' => [6],
156 '"am minim veniam" -"cillum dolore"' => [5, 6],
157 '"am minim veniam" -"dolore cillum"' => [5, 6, 7],
158 'xxxxx "minim am veniam es" OR dolore' => [],
159 'xx "minim am veniam es" OR dolore' => [],
161 foreach ($queries as $query => $results) {
162 $result = db_select('search_index', 'i')
163 ->extend('Drupal\search\SearchQuery')
164 ->searchExpression($query, static::SEARCH_TYPE)
167 $set = $result ? $result->fetchAll() : [];
168 $this->_testQueryMatching($query, $set, $results);
169 $this->_testQueryScores($query, $set, $results);
172 // These queries are run against the second index type, SEARCH_TYPE_2.
174 // Simple AND queries.
179 'germany' => [11, 12],
181 foreach ($queries as $query => $results) {
182 $result = db_select('search_index', 'i')
183 ->extend('Drupal\search\SearchQuery')
184 ->searchExpression($query, static::SEARCH_TYPE_2)
187 $set = $result ? $result->fetchAll() : [];
188 $this->_testQueryMatching($query, $set, $results);
189 $this->_testQueryScores($query, $set, $results);
192 // These queries are run against the third index type, SEARCH_TYPE_JPN.
194 // Simple AND queries.
204 foreach ($queries as $query => $results) {
205 $result = db_select('search_index', 'i')
206 ->extend('Drupal\search\SearchQuery')
207 ->searchExpression($query, static::SEARCH_TYPE_JPN)
210 $set = $result ? $result->fetchAll() : [];
211 $this->_testQueryMatching($query, $set, $results);
212 $this->_testQueryScores($query, $set, $results);
217 * Test the matching abilities of the engine.
219 * Verify if a query produces the correct results.
221 public function _testQueryMatching($query, $set, $results) {
224 foreach ($set as $item) {
225 $found[] = $item->sid;
228 // Compare $results and $found.
231 $this->assertEqual($found, $results, "Query matching '$query'");
235 * Test the scoring abilities of the engine.
237 * Verify if a query produces normalized, monotonous scores.
239 public function _testQueryScores($query, $set, $results) {
240 // Get result scores.
242 foreach ($set as $item) {
243 $scores[] = $item->calculated_score;
249 $this->assertEqual($scores, array_reverse($sorted), "Query order '$query'");
252 $this->assertEqual(!count($scores) || (min($scores) > 0.0 && max($scores) <= 1.0001), TRUE, "Query scoring '$query'");