1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SilverStripe\FullTextSearch\Tests; |
4
|
|
|
|
5
|
|
|
use SilverStripe\Dev\SapphireTest; |
6
|
|
|
use SilverStripe\FullTextSearch\Search\FullTextSearch; |
7
|
|
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater; |
8
|
|
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant; |
9
|
|
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Variant; |
10
|
|
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Index; |
11
|
|
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_TestHandler; |
12
|
|
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Item; |
13
|
|
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_RecordingLogger; |
14
|
|
|
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexHandler; |
15
|
|
|
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service; |
16
|
|
|
use SilverStripe\FullTextSearch\Solr\Tasks\Solr_Reindex; |
17
|
|
|
use SilverStripe\Core\Config\Config; |
18
|
|
|
use SilverStripe\Core\Injector\Injector; |
19
|
|
|
use SilverStripe\ORM\DataObject; |
20
|
|
|
use SilverStripe\ORM\DB; |
21
|
|
|
|
22
|
|
|
class SolrReindexTest extends SapphireTest |
23
|
|
|
{ |
24
|
|
|
protected $usesDatabase = true; |
25
|
|
|
|
26
|
|
|
protected static $extra_dataobjects = array( |
27
|
|
|
SolrReindexTest_Item::class |
28
|
|
|
); |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Forced index for testing |
32
|
|
|
* |
33
|
|
|
* @var SolrReindexTest_Index |
34
|
|
|
*/ |
35
|
|
|
protected $index = null; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Mock service |
39
|
|
|
* |
40
|
|
|
* @var SolrService |
41
|
|
|
*/ |
42
|
|
|
protected $service = null; |
43
|
|
|
|
44
|
|
|
protected function setUp() |
45
|
|
|
{ |
46
|
|
|
Config::modify()->set(SearchUpdater::class, 'flush_on_shutdown', false); |
47
|
|
|
|
48
|
|
|
parent::setUp(); |
49
|
|
|
|
50
|
|
|
// Set test handler for reindex |
51
|
|
|
Config::modify()->set(Injector::class, SolrReindexHandler::class, array( |
52
|
|
|
'class' => SolrReindexTest_TestHandler::class |
53
|
|
|
)); |
54
|
|
|
|
55
|
|
|
Injector::inst()->registerService(new SolrReindexTest_TestHandler(), SolrReindexHandler::class); |
56
|
|
|
|
57
|
|
|
// Set test variant |
58
|
|
|
SolrReindexTest_Variant::enable(); |
59
|
|
|
|
60
|
|
|
// Set index list |
61
|
|
|
$this->service = $this->getServiceMock(); |
62
|
|
|
$this->index = singleton(SolrReindexTest_Index::class); |
63
|
|
|
$this->index->setService($this->service); |
64
|
|
|
|
65
|
|
|
FullTextSearch::force_index_list($this->index); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Populate database with dummy dataset |
70
|
|
|
* |
71
|
|
|
* @param int $number Number of records to create in each variant |
72
|
|
|
*/ |
73
|
|
|
protected function createDummyData($number) |
74
|
|
|
{ |
75
|
|
|
self::resetDBSchema(); |
76
|
|
|
|
77
|
|
|
// Note that we don't create any records in variant = 2, to represent a variant |
78
|
|
|
// that should be cleared without any re-indexes performed |
79
|
|
View Code Duplication |
foreach (array(0, 1) as $variant) { |
|
|
|
|
80
|
|
|
for ($i = 1; $i <= $number; $i++) { |
81
|
|
|
$item = new SolrReindexTest_Item(); |
82
|
|
|
$item->Variant = $variant; |
|
|
|
|
83
|
|
|
$item->Title = "Item $variant / $i"; |
|
|
|
|
84
|
|
|
$item->write(); |
85
|
|
|
} |
86
|
|
|
} |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Mock service |
91
|
|
|
* |
92
|
|
|
* @return SolrService |
93
|
|
|
*/ |
94
|
|
|
protected function getServiceMock() |
95
|
|
|
{ |
96
|
|
|
$serviceMock = $this->getMockBuilder(Solr4Service::class) |
97
|
|
|
->setMethods(['deleteByQuery', 'addDocument']); |
98
|
|
|
|
99
|
|
|
return $serviceMock->getMock(); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
public function tearDown() |
103
|
|
|
{ |
104
|
|
|
FullTextSearch::force_index_list(); |
105
|
|
|
SolrReindexTest_Variant::disable(); |
106
|
|
|
parent::tearDown(); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Get the reindex handler |
111
|
|
|
* |
112
|
|
|
* @return SolrReindexHandler |
113
|
|
|
*/ |
114
|
|
|
protected function getHandler() |
115
|
|
|
{ |
116
|
|
|
return Injector::inst()->get(SolrReindexHandler::class); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Ensure the test variant is up and running properly |
121
|
|
|
*/ |
122
|
|
|
public function testVariant() |
123
|
|
|
{ |
124
|
|
|
// State defaults to 0 |
125
|
|
|
$variant = SearchVariant::current_state(); |
126
|
|
|
$this->assertEquals( |
127
|
|
|
array( |
128
|
|
|
SolrReindexTest_Variant::class => "0" |
129
|
|
|
), |
130
|
|
|
$variant |
131
|
|
|
); |
132
|
|
|
|
133
|
|
|
// All states enumerated |
134
|
|
|
$allStates = iterator_to_array(SearchVariant::reindex_states()); |
135
|
|
|
$this->assertEquals( |
136
|
|
|
array( |
137
|
|
|
array( |
138
|
|
|
SolrReindexTest_Variant::class => "0" |
139
|
|
|
), |
140
|
|
|
array( |
141
|
|
|
SolrReindexTest_Variant::class => "1" |
142
|
|
|
), |
143
|
|
|
array( |
144
|
|
|
SolrReindexTest_Variant::class => "2" |
145
|
|
|
) |
146
|
|
|
), |
147
|
|
|
$allStates |
148
|
|
|
); |
149
|
|
|
|
150
|
|
|
// Check correct items created and that filtering on variant works |
151
|
|
|
$this->createDummyData(120); |
152
|
|
|
SolrReindexTest_Variant::set_current(2); |
153
|
|
|
$this->assertEquals(0, SolrReindexTest_Item::get()->count()); |
154
|
|
|
SolrReindexTest_Variant::set_current(1); |
155
|
|
|
$this->assertEquals(120, SolrReindexTest_Item::get()->count()); |
156
|
|
|
SolrReindexTest_Variant::set_current(0); |
157
|
|
|
$this->assertEquals(120, SolrReindexTest_Item::get()->count()); |
158
|
|
|
SolrReindexTest_Variant::disable(); |
159
|
|
|
$this->assertEquals(240, SolrReindexTest_Item::get()->count()); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Given the invocation of a new re-index with a given set of data, ensure that the necessary |
165
|
|
|
* list of groups are created and segmented for each state |
166
|
|
|
* |
167
|
|
|
* Test should work fine with any variants (versioned, subsites, etc) specified |
168
|
|
|
*/ |
169
|
|
|
public function testReindexSegmentsGroups() |
170
|
|
|
{ |
171
|
|
|
$this->service->method('deleteByQuery') |
172
|
|
|
->withConsecutive( |
173
|
|
|
['-(ClassHierarchy:' . SolrReindexTest_Item::class . ')'], |
174
|
|
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +(_testvariant:"2")'] |
175
|
|
|
); |
176
|
|
|
|
177
|
|
|
$this->createDummyData(120); |
178
|
|
|
|
179
|
|
|
// Initiate re-index |
180
|
|
|
$logger = new SolrReindexTest_RecordingLogger(); |
181
|
|
|
$this->getHandler()->runReindex($logger, 21, Solr_Reindex::class); |
182
|
|
|
|
183
|
|
|
// Test that invalid classes are removed |
184
|
|
|
$this->assertContains('Clearing obsolete classes from ' . SolrReindexTest_Index::class, $logger->getMessages()); |
185
|
|
|
// Test that valid classes in invalid variants are removed |
186
|
|
|
$this->assertContains('Clearing all records of type ' . SolrReindexTest_Item::class . ' in the current state: {' . json_encode(SolrReindexTest_Variant::class) . ':"2"}', $logger->getMessages()); |
187
|
|
|
|
188
|
|
|
// 120x2 grouped into groups of 21 results in 12 groups |
189
|
|
|
$this->assertEquals(12, $logger->countMessages('Called processGroup with ')); |
190
|
|
|
$this->assertEquals(6, $logger->countMessages('{' . json_encode(SolrReindexTest_Variant::class) . ':"0"}')); |
191
|
|
|
$this->assertEquals(6, $logger->countMessages('{' . json_encode(SolrReindexTest_Variant::class) . ':"1"}')); |
192
|
|
|
|
193
|
|
|
// Given that there are two variants, there should be two group ids of each number |
194
|
|
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 0 of 6')); |
195
|
|
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 1 of 6')); |
196
|
|
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 2 of 6')); |
197
|
|
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 3 of 6')); |
198
|
|
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 4 of 6')); |
199
|
|
|
$this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 5 of 6')); |
200
|
|
|
|
201
|
|
|
// Check various group sizes |
202
|
|
|
$logger->clear(); |
203
|
|
|
$this->getHandler()->runReindex($logger, 120, 'Solr_Reindex'); |
204
|
|
|
$this->assertEquals(2, $logger->countMessages('Called processGroup with ')); |
205
|
|
|
$logger->clear(); |
206
|
|
|
$this->getHandler()->runReindex($logger, 119, 'Solr_Reindex'); |
207
|
|
|
$this->assertEquals(4, $logger->countMessages('Called processGroup with ')); |
208
|
|
|
$logger->clear(); |
209
|
|
|
$this->getHandler()->runReindex($logger, 121, 'Solr_Reindex'); |
210
|
|
|
$this->assertEquals(2, $logger->countMessages('Called processGroup with ')); |
211
|
|
|
$logger->clear(); |
212
|
|
|
$this->getHandler()->runReindex($logger, 2, 'Solr_Reindex'); |
213
|
|
|
$this->assertEquals(120, $logger->countMessages('Called processGroup with ')); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Test index processing on individual groups |
218
|
|
|
*/ |
219
|
|
|
public function testRunGroup() |
220
|
|
|
{ |
221
|
|
|
$this->service->method('deleteByQuery') |
222
|
|
|
->with('+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=2 u=2}mod(ID, 6)" +(_testvariant:"1")'); |
223
|
|
|
|
224
|
|
|
$this->createDummyData(120); |
225
|
|
|
$logger = new SolrReindexTest_RecordingLogger(); |
226
|
|
|
|
227
|
|
|
// Initiate re-index of third group (index 2 of 6) |
228
|
|
|
$state = array(SolrReindexTest_Variant::class => '1'); |
229
|
|
|
$this->getHandler()->runGroup($logger, $this->index, $state, SolrReindexTest_Item::class, 6, 2); |
230
|
|
|
$idMessage = $logger->filterMessages('Updated '); |
231
|
|
|
$this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/i', $idMessage[0], $matches)); |
232
|
|
|
$ids = array_unique(explode(',', $matches['ids'])); |
233
|
|
|
|
234
|
|
|
// Test successful |
235
|
|
|
$this->assertNotEmpty($logger->getMessages('Adding ' . SolrReindexTest_Item::class)); |
|
|
|
|
236
|
|
|
$this->assertNotEmpty($logger->getMessages('Done')); |
|
|
|
|
237
|
|
|
|
238
|
|
|
// Test that items in this variant / group are re-indexed |
239
|
|
|
// 120 divided into 6 groups should be 20 at least (max 21) |
240
|
|
|
$this->assertEquals(21, count($ids), 'Group size is about 20', 1); |
241
|
|
|
foreach ($ids as $id) { |
242
|
|
|
// Each id should be % 6 == 2 |
243
|
|
|
$this->assertEquals(2, $id % 6, "ID $id Should match pattern ID % 6 = 2"); |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Test that running all groups covers the entire range of dataobject IDs |
249
|
|
|
*/ |
250
|
|
|
public function testRunAllGroups() |
251
|
|
|
{ |
252
|
|
|
$this->service->method('deleteByQuery') |
253
|
|
|
->withConsecutive( |
254
|
|
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=0 u=0}mod(ID, 6)" +(_testvariant:"1")'], |
255
|
|
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=1 u=1}mod(ID, 6)" +(_testvariant:"1")'], |
256
|
|
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=2 u=2}mod(ID, 6)" +(_testvariant:"1")'], |
257
|
|
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=3 u=3}mod(ID, 6)" +(_testvariant:"1")'], |
258
|
|
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=4 u=4}mod(ID, 6)" +(_testvariant:"1")'], |
259
|
|
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=5 u=5}mod(ID, 6)" +(_testvariant:"1")'], |
260
|
|
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=6 u=6}mod(ID, 6)" +(_testvariant:"1")'] |
261
|
|
|
); |
262
|
|
|
|
263
|
|
|
$this->createDummyData(120); |
264
|
|
|
$logger = new SolrReindexTest_RecordingLogger(); |
265
|
|
|
|
266
|
|
|
// Test that running all groups covers the complete set of ids |
267
|
|
|
$state = array(SolrReindexTest_Variant::class => '1'); |
268
|
|
|
for ($i = 0; $i < 6; $i++) { |
269
|
|
|
// See testReindexSegmentsGroups for test that each of these states is invoked during a full reindex |
270
|
|
|
$this |
271
|
|
|
->getHandler() |
272
|
|
|
->runGroup($logger, $this->index, $state, SolrReindexTest_Item::class, 6, $i); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
// Count all ids updated |
276
|
|
|
$ids = array(); |
277
|
|
|
foreach ($logger->filterMessages('Updated ') as $message) { |
278
|
|
|
$this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/', $message, $matches)); |
279
|
|
|
$ids = array_unique(array_merge($ids, explode(',', $matches['ids']))); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
// Check ids |
283
|
|
|
$this->assertEquals(120, count($ids)); |
284
|
|
|
} |
285
|
|
|
} |
286
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.