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

SolrReindexTest::testVariant()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 39
rs 8.8571
cc 1
eloc 25
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
        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) {
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...
80
            for ($i = 1; $i <= $number; $i++) {
81
                $item = new SolrReindexTest_Item();
82
                $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...
83
                $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...
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));
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...
236
        $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...
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