Completed
Pull Request — master (#184)
by Robbie
02:01
created

SolrReindexTest::testReindexSegmentsGroups()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 46
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 46
rs 8.9411
c 0
b 0
f 0
cc 1
eloc 31
nc 1
nop 0
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
        parent::setUp();
47
48
        // Set test handler for reindex
49
        Config::modify()->set(Injector::class, SolrReindexHandler::class, array(
50
            'class' => SolrReindexTest_TestHandler::class
51
        ));
52
53
        Injector::inst()->registerService(new SolrReindexTest_TestHandler(), SolrReindexHandler::class);
54
55
        // Set test variant
56
        SolrReindexTest_Variant::enable();
57
58
        // Set index list
59
        $this->service = $this->getServiceMock();
60
        $this->index = singleton(SolrReindexTest_Index::class);
61
        $this->index->setService($this->service);
62
63
        FullTextSearch::force_index_list($this->index);
64
    }
65
66
    /**
67
     * Populate database with dummy dataset
68
     *
69
     * @param int $number Number of records to create in each variant
70
     */
71
    protected function createDummyData($number)
72
    {
73
        self::resetDBSchema();
74
75
        // Note that we don't create any records in variant = 2, to represent a variant
76
        // that should be cleared without any re-indexes performed
77 View Code Duplication
        foreach (array(0, 1) as $variant) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
78
            for ($i = 1; $i <= $number; $i++) {
79
                $item = new SolrReindexTest_Item();
80
                $item->Variant = $variant;
0 ignored issues
show
Documentation introduced by
The property Variant does not exist on object<SilverStripe\Full...t\SolrReindexTest_Item>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
81
                $item->Title = "Item $variant / $i";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\Full...t\SolrReindexTest_Item>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
82
                $item->write();
83
            }
84
        }
85
    }
86
87
    /**
88
     * Mock service
89
     *
90
     * @return SolrService
91
     */
92
    protected function getServiceMock()
93
    {
94
        $serviceMock = $this->getMockBuilder(Solr4Service::class)
95
            ->setMethods(['deleteByQuery', 'addDocument']);
96
97
        return $serviceMock->getMock();
98
    }
99
100
    public function tearDown()
101
    {
102
        FullTextSearch::force_index_list();
103
        SolrReindexTest_Variant::disable();
104
        parent::tearDown();
105
    }
106
107
    /**
108
     * Get the reindex handler
109
     *
110
     * @return SolrReindexHandler
111
     */
112
    protected function getHandler()
113
    {
114
        return Injector::inst()->get(SolrReindexHandler::class);
115
    }
116
117
    /**
118
     * Ensure the test variant is up and running properly
119
     */
120
    public function testVariant()
121
    {
122
        // State defaults to 0
123
        $variant = SearchVariant::current_state();
124
        $this->assertEquals(
125
            array(
126
                SolrReindexTest_Variant::class => "0"
127
            ),
128
            $variant
129
        );
130
131
        // All states enumerated
132
        $allStates = iterator_to_array(SearchVariant::reindex_states());
133
        $this->assertEquals(
134
            array(
135
                array(
136
                    SolrReindexTest_Variant::class => "0"
137
                ),
138
                array(
139
                    SolrReindexTest_Variant::class => "1"
140
                ),
141
                array(
142
                    SolrReindexTest_Variant::class => "2"
143
                )
144
            ),
145
            $allStates
146
        );
147
148
        // Check correct items created and that filtering on variant works
149
        $this->createDummyData(120);
150
        SolrReindexTest_Variant::set_current(2);
151
        $this->assertEquals(0, SolrReindexTest_Item::get()->count());
152
        SolrReindexTest_Variant::set_current(1);
153
        $this->assertEquals(120, SolrReindexTest_Item::get()->count());
154
        SolrReindexTest_Variant::set_current(0);
155
        $this->assertEquals(120, SolrReindexTest_Item::get()->count());
156
        SolrReindexTest_Variant::disable();
157
        $this->assertEquals(240, SolrReindexTest_Item::get()->count());
158
    }
159
160
161
    /**
162
     * Given the invocation of a new re-index with a given set of data, ensure that the necessary
163
     * list of groups are created and segmented for each state
164
     *
165
     * Test should work fine with any variants (versioned, subsites, etc) specified
166
     */
167
    public function testReindexSegmentsGroups()
168
    {
169
        $this->service->method('deleteByQuery')
170
            ->withConsecutive(
171
                ['-(ClassHierarchy:' . SolrReindexTest_Item::class . ')'],
172
                ['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +(_testvariant:"2")']
173
            );
174
175
        $this->createDummyData(120);
176
177
        // Initiate re-index
178
        $logger = new SolrReindexTest_RecordingLogger();
179
        $this->getHandler()->runReindex($logger, 21, Solr_Reindex::class);
180
181
        // Test that invalid classes are removed
182
        $this->assertContains('Clearing obsolete classes from ' . SolrReindexTest_Index::class, $logger->getMessages());
183
        // Test that valid classes in invalid variants are removed
184
        $this->assertContains('Clearing all records of type ' . SolrReindexTest_Item::class . ' in the current state: {' . json_encode(SolrReindexTest_Variant::class) . ':"2"}', $logger->getMessages());
185
186
        // 120x2 grouped into groups of 21 results in 12 groups
187
        $this->assertEquals(12, $logger->countMessages('Called processGroup with '));
188
        $this->assertEquals(6, $logger->countMessages('{' . json_encode(SolrReindexTest_Variant::class) . ':"0"}'));
189
        $this->assertEquals(6, $logger->countMessages('{' . json_encode(SolrReindexTest_Variant::class) . ':"1"}'));
190
191
        // Given that there are two variants, there should be two group ids of each number
192
        $this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 0 of 6'));
193
        $this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 1 of 6'));
194
        $this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 2 of 6'));
195
        $this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 3 of 6'));
196
        $this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 4 of 6'));
197
        $this->assertEquals(2, $logger->countMessages(' ' . SolrReindexTest_Item::class . ', group 5 of 6'));
198
199
        // Check various group sizes
200
        $logger->clear();
201
        $this->getHandler()->runReindex($logger, 120, 'Solr_Reindex');
202
        $this->assertEquals(2, $logger->countMessages('Called processGroup with '));
203
        $logger->clear();
204
        $this->getHandler()->runReindex($logger, 119, 'Solr_Reindex');
205
        $this->assertEquals(4, $logger->countMessages('Called processGroup with '));
206
        $logger->clear();
207
        $this->getHandler()->runReindex($logger, 121, 'Solr_Reindex');
208
        $this->assertEquals(2, $logger->countMessages('Called processGroup with '));
209
        $logger->clear();
210
        $this->getHandler()->runReindex($logger, 2, 'Solr_Reindex');
211
        $this->assertEquals(120, $logger->countMessages('Called processGroup with '));
212
    }
213
214
    /**
215
     * Test index processing on individual groups
216
     */
217
    public function testRunGroup()
218
    {
219
        $this->service->method('deleteByQuery')
220
            ->with('+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=2 u=2}mod(ID, 6)" +(_testvariant:"1")');
221
222
        $this->createDummyData(120);
223
        $logger = new SolrReindexTest_RecordingLogger();
224
225
        // Initiate re-index of third group (index 2 of 6)
226
        $state = array(SolrReindexTest_Variant::class => '1');
227
        $this->getHandler()->runGroup($logger, $this->index, $state, SolrReindexTest_Item::class, 6, 2);
228
        $idMessage = $logger->filterMessages('Updated ');
229
        $this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/i', $idMessage[0], $matches));
230
        $ids = array_unique(explode(',', $matches['ids']));
231
232
        // Test successful
233
        $this->assertNotEmpty($logger->getMessages('Adding ' . SolrReindexTest_Item::class));
0 ignored issues
show
Unused Code introduced by
The call to SolrReindexTest_RecordingLogger::getMessages() has too many arguments starting with 'Adding ' . \SilverStrip...ReindexTest_Item::class.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
234
        $this->assertNotEmpty($logger->getMessages('Done'));
0 ignored issues
show
Unused Code introduced by
The call to SolrReindexTest_RecordingLogger::getMessages() has too many arguments starting with 'Done'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
235
236
        // Test that items in this variant / group are re-indexed
237
        // 120 divided into 6 groups should be 20 at least (max 21)
238
        $this->assertEquals(21, count($ids), 'Group size is about 20', 1);
239
        foreach ($ids as $id) {
240
            // Each id should be % 6 == 2
241
            $this->assertEquals(2, $id % 6, "ID $id Should match pattern ID % 6 = 2");
242
        }
243
    }
244
245
    /**
246
     * Test that running all groups covers the entire range of dataobject IDs
247
     */
248
    public function testRunAllGroups()
249
    {
250
        $this->service->method('deleteByQuery')
251
            ->withConsecutive(
252
                ['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=0 u=0}mod(ID, 6)" +(_testvariant:"1")'],
253
                ['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=1 u=1}mod(ID, 6)" +(_testvariant:"1")'],
254
                ['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=2 u=2}mod(ID, 6)" +(_testvariant:"1")'],
255
                ['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=3 u=3}mod(ID, 6)" +(_testvariant:"1")'],
256
                ['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=4 u=4}mod(ID, 6)" +(_testvariant:"1")'],
257
                ['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=5 u=5}mod(ID, 6)" +(_testvariant:"1")'],
258
                ['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=6 u=6}mod(ID, 6)" +(_testvariant:"1")']
259
            );
260
261
        $this->createDummyData(120);
262
        $logger = new SolrReindexTest_RecordingLogger();
263
264
        // Test that running all groups covers the complete set of ids
265
        $state = array(SolrReindexTest_Variant::class => '1');
266
        for ($i = 0; $i < 6; $i++) {
267
            // See testReindexSegmentsGroups for test that each of these states is invoked during a full reindex
268
            $this
269
                ->getHandler()
270
                ->runGroup($logger, $this->index, $state, SolrReindexTest_Item::class, 6, $i);
271
        }
272
273
        // Count all ids updated
274
        $ids = array();
275
        foreach ($logger->filterMessages('Updated ') as $message) {
276
            $this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/', $message, $matches));
277
            $ids = array_unique(array_merge($ids, explode(',', $matches['ids'])));
278
        }
279
280
        // Check ids
281
        $this->assertEquals(120, count($ids));
282
    }
283
}
284