Completed
Push — master ( e62317...cd1faf )
by adam
03:51 queued 01:43
created

ReferenceListTest::testGetValueHashStability()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Wikibase\DataModel\Tests;
4
5
use Hashable;
6
use InvalidArgumentException;
7
use Traversable;
8
use Wikibase\DataModel\Entity\PropertyId;
9
use Wikibase\DataModel\Reference;
10
use Wikibase\DataModel\ReferenceList;
11
use Wikibase\DataModel\Snak\PropertyNoValueSnak;
12
use Wikibase\DataModel\Snak\SnakList;
13
14
/**
15
 * @covers \Wikibase\DataModel\ReferenceList
16
 *
17
 * @group Wikibase
18
 * @group WikibaseDataModel
19
 * @group WikibaseReference
20
 *
21
 * @license GPL-2.0-or-later
22
 * @author Jeroen De Dauw < [email protected] >
23
 * @author Thiemo Kreuz
24
 */
25
class ReferenceListTest extends \PHPUnit\Framework\TestCase {
26
27
	public function instanceProvider() {
28
		return [
29
			[ new ReferenceList( [] ) ],
30
			[ new ReferenceList( [
31
				new Reference(),
32
				new Reference( [ new PropertyNoValueSnak( 2 ) ] ),
33
				new Reference( [ new PropertyNoValueSnak( 3 ) ] ),
34
			] ) ],
35
		];
36
	}
37
38
	public function testCanConstructWithReferenceListObject() {
39
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
40
		$original = new ReferenceList( [ $reference ] );
41
		$copy = new ReferenceList( $original );
42
43
		$this->assertSame( 1, $copy->count() );
44
		$this->assertNotNull( $copy->getReference( $reference->getHash() ) );
45
	}
46
47
	public function testConstructorIgnoresIdenticalObjects() {
48
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
49
		$list = new ReferenceList( [ $reference, $reference ] );
50
		$this->assertCount( 1, $list );
51
	}
52
53
	public function testConstructorDoesNotIgnoreCopies() {
54
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
55
		$list = new ReferenceList( [ $reference, clone $reference ] );
56
		$this->assertCount( 2, $list );
57
	}
58
59
	/**
60
	 * @dataProvider invalidConstructorArgumentsProvider
61
	 */
62
	public function testGivenInvalidConstructorArguments_constructorThrowsException( $input ) {
63
		$this->expectException( InvalidArgumentException::class );
64
		new ReferenceList( $input );
65
	}
66
67
	public function invalidConstructorArgumentsProvider() {
68
		$id1 = new PropertyId( 'P1' );
69
70
		return [
71
			[ null ],
72
			[ false ],
73
			[ 1 ],
74
			[ 0.1 ],
75
			[ 'string' ],
76
			[ $id1 ],
77
			[ new PropertyNoValueSnak( $id1 ) ],
78
			[ new Reference() ],
79
			[ new SnakList( [ new PropertyNoValueSnak( $id1 ) ] ) ],
80
			[ [ new PropertyNoValueSnak( $id1 ) ] ],
81
			[ [ new ReferenceList() ] ],
82
			[ [ new SnakList() ] ],
83
		];
84
	}
85
86
	public function testGetIterator_isTraversable() {
87
		$references = new ReferenceList();
88
		$references->addNewReference( new PropertyNoValueSnak( 1 ) );
89
		$iterator = $references->getIterator();
90
91
		$this->assertInstanceOf( Traversable::class, $iterator );
92
		$this->assertCount( 1, $iterator );
93
		foreach ( $references as $reference ) {
94
			$this->assertInstanceOf( Reference::class, $reference );
95
		}
96
	}
97
98
	/**
99
	 * @dataProvider instanceProvider
100
	 */
101
	public function testHasReferenceBeforeRemoveButNotAfter( ReferenceList $array ) {
102
		if ( $array->count() === 0 ) {
103
			$this->assertTrue( true );
104
			return;
105
		}
106
107
		/**
108
		 * @var Reference $hashable
109
		 */
110
		foreach ( iterator_to_array( $array ) as $hashable ) {
111
			$this->assertTrue( $array->hasReference( $hashable ) );
112
			$array->removeReference( $hashable );
113
			$this->assertFalse( $array->hasReference( $hashable ) );
114
		}
115
	}
116
117
	public function testGivenCloneOfReferenceInList_hasReferenceReturnsTrue() {
118
		$list = new ReferenceList();
119
120
		$reference = new Reference( [ new PropertyNoValueSnak( 42 ) ] );
121
		$sameReference = unserialize( serialize( $reference ) );
122
123
		$list->addReference( $reference );
124
125
		$this->assertTrue(
126
			$list->hasReference( $sameReference ),
127
			'hasReference should return true when a reference with the same value is present, even '
128
				. 'when its another instance'
129
		);
130
	}
131
132
	/**
133
	 * @dataProvider instanceProvider
134
	 */
135
	public function testRemoveReference( ReferenceList $array ) {
136
		$elementCount = count( $array );
137
138
		/**
139
		 * @var Reference $element
140
		 */
141
		foreach ( iterator_to_array( $array ) as $element ) {
142
			$this->assertTrue( $array->hasReference( $element ) );
143
144
			$array->removeReference( $element );
145
146
			$this->assertFalse( $array->hasReference( $element ) );
147
			$this->assertSame( --$elementCount, count( $array ) );
148
		}
149
		if ( $elementCount === 0 ) {
150
			$this->assertTrue( true );
151
		}
152
	}
153
154
	public function testRemoveReferenceRemovesIdenticalObjects() {
155
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
156
		$references = new ReferenceList( [ $reference, $reference ] );
157
158
		$references->removeReference( $reference );
159
160
		$this->assertTrue( $references->isEmpty() );
161
	}
162
163
	public function testRemoveReferenceDoesNotRemoveCopies() {
164
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
165
		$references = new ReferenceList( [ $reference, clone $reference ] );
166
167
		$references->removeReference( $reference );
168
169
		$this->assertFalse( $references->isEmpty() );
170
		$this->assertTrue( $references->hasReference( $reference ) );
171
		$this->assertNotSame( $reference, $references->getReference( $reference->getHash() ) );
172
	}
173
174
	public function testAddReferenceOnEmptyList() {
175
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
176
177
		$references = new ReferenceList();
178
		$references->addReference( $reference );
179
180
		$this->assertCount( 1, $references );
181
182
		$expectedList = new ReferenceList( [ $reference ] );
183
		$this->assertSameReferenceOrder( $expectedList, $references );
184
	}
185
186
	private function assertSameReferenceOrder( ReferenceList $expectedList, ReferenceList $references ) {
187
		$this->assertSame(
188
			iterator_to_array( $expectedList ),
189
			iterator_to_array( $references )
190
		);
191
	}
192
193
	public function testAddReferenceAtTheEnd() {
194
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
195
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
196
		$reference3 = new Reference( [ new PropertyNoValueSnak( 3 ) ] );
197
198
		$references = new ReferenceList( [ $reference1, $reference2 ] );
199
		$references->addReference( $reference3 );
200
201
		$this->assertCount( 3, $references );
202
203
		$expectedList = new ReferenceList( [ $reference1, $reference2, $reference3 ] );
204
		$this->assertSameReferenceOrder( $expectedList, $references );
205
	}
206
207
	public function testAddReferenceBetweenExistingReferences() {
208
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
209
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
210
		$list = new ReferenceList( [ $reference1, $reference2 ] );
211
212
		$reference3 = new Reference( [ new PropertyNoValueSnak( 3 ) ] );
213
		$list->addReference( $reference3, 1 );
214
215
		$this->assertCount( 3, $list );
216
		$this->assertSame( 1, $list->indexOf( $reference3 ) );
217
	}
218
219
	public function testAddReferenceIgnoresIdenticalObjects() {
220
		$list = new ReferenceList();
221
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
222
		$list->addReference( $reference );
223
		$list->addReference( $reference );
224
		$this->assertCount( 1, $list );
225
	}
226
227
	public function testAddReferenceDoesNotIgnoreCopies() {
228
		$list = new ReferenceList();
229
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
230
		$list->addReference( $reference );
231
		$list->addReference( clone $reference );
232
		$this->assertCount( 2, $list );
233
	}
234
235
	public function testAddReferenceAtIndexIgnoresIdenticalObjects() {
236
		$list = new ReferenceList();
237
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
238
		$list->addReference( $reference, 0 );
239
		$list->addReference( $reference, 0 );
240
		$this->assertCount( 1, $list );
241
	}
242
243
	public function testAddReferenceAtIndexMovesIdenticalObjects() {
244
		$list = new ReferenceList();
245
		$list->addNewReference( new PropertyNoValueSnak( 1 ) );
246
		$reference = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
247
		$list->addReference( $reference );
248
		$list->addNewReference( new PropertyNoValueSnak( 3 ) );
249
250
		$this->assertSame(
251
			1,
252
			$list->indexOf( $reference ),
253
			'pre-condition is that the element is at index 1'
254
		);
255
256
		$list->addReference( $reference, 0 );
257
258
		$this->assertCount( 3, $list, 'not added' );
259
		$this->assertSame(
260
			1,
261
			$list->indexOf( $reference ),
262
			'make sure calling addReference with a lower index did not changed it'
263
		);
264
265
		$list->addReference( $reference, 2 );
266
267
		$this->assertCount( 3, $list, 'not added' );
268
		$this->assertSame(
269
			1,
270
			$list->indexOf( $reference ),
271
			'make sure calling addReference with a higher index did not changed it'
272
		);
273
	}
274
275
	public function testAddReferenceAtIndexZero() {
276
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
277
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
278
		$reference3 = new Reference( [ new PropertyNoValueSnak( 3 ) ] );
279
280
		$references = new ReferenceList( [ $reference1, $reference2 ] );
281
		$references->addReference( $reference3, 0 );
282
283
		$expectedList = new ReferenceList( [ $reference3, $reference1, $reference2 ] );
284
		$this->assertSameReferenceOrder( $expectedList, $references );
285
	}
286
287
	public function testAddReferenceAtNegativeIndex() {
288
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
289
		$referenceList = new ReferenceList();
290
291
		$this->expectException( InvalidArgumentException::class );
292
		$referenceList->addReference( $reference, -1 );
293
	}
294
295
	public function testGivenEmptyReference_addReferenceDoesNotAdd() {
296
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
297
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
298
		$emptyReference = new Reference( [] );
299
300
		$references = new ReferenceList( [ $reference1, $reference2 ] );
301
		$references->addReference( $emptyReference );
302
303
		$expectedList = new ReferenceList( [ $reference1, $reference2 ] );
304
		$this->assertSameReferenceOrder( $expectedList, $references );
305
	}
306
307
	public function testGivenEmptyReferenceAndIndex_addReferenceDoesNotAdd() {
308
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
309
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
310
		$emptyReference = new Reference( [] );
311
312
		$references = new ReferenceList( [ $reference1, $reference2 ] );
313
		$references->addReference( $emptyReference, 0 );
314
315
		$expectedList = new ReferenceList( [ $reference1, $reference2 ] );
316
		$this->assertSameReferenceOrder( $expectedList, $references );
317
	}
318
319
	/**
320
	 * @dataProvider instanceProvider
321
	 */
322
	public function testIndexOf( ReferenceList $array ) {
323
		$this->assertFalse( $array->indexOf( new Reference() ) );
324
325
		$i = 0;
326
		foreach ( $array as $reference ) {
327
			$this->assertSame( $i++, $array->indexOf( $reference ) );
328
		}
329
	}
330
331
	public function testIndexOf_checksForIdentity() {
332
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
333
		$reference2 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
334
		$referenceList = new ReferenceList( [ $reference1 ] );
335
336
		$this->assertNotSame( $reference1, $reference2, 'post condition' );
337
		$this->assertTrue( $reference1->equals( $reference2 ), 'post condition' );
338
		$this->assertSame( 0, $referenceList->indexOf( $reference1 ), 'identity' );
339
		$this->assertFalse( $referenceList->indexOf( $reference2 ), 'not equality' );
340
	}
341
342
	/**
343
	 * @dataProvider instanceProvider
344
	 */
345
	public function testEquals( ReferenceList $array ) {
346
		$this->assertTrue( $array->equals( $array ) );
347
		$this->assertFalse( $array->equals( 42 ) );
348
	}
349
350
	/**
351
	 * @dataProvider instanceProvider
352
	 */
353
	public function testGetValueHashReturnsString( ReferenceList $array ) {
354
		$this->assertIsString( $array->getValueHash() );
355
	}
356
357
	/**
358
	 * @dataProvider instanceProvider
359
	 */
360
	public function testGetValueHashIsTheSameForClone( ReferenceList $array ) {
361
		$copy = unserialize( serialize( $array ) );
362
		$this->assertSame( $array->getValueHash(), $copy->getValueHash() );
363
	}
364
365
	/**
366
	 * @dataProvider instanceProvider
367
	 */
368
	public function testHasReferenceHash( ReferenceList $references ) {
369
		$this->assertFalse( $references->hasReferenceHash( '~=[,,_,,]:3' ) );
370
371
		/**
372
		 * @var Hashable $reference
373
		 */
374
		foreach ( $references as $reference ) {
375
			$this->assertTrue( $references->hasReferenceHash( $reference->getHash() ) );
376
		}
377
	}
378
379
	/**
380
	 * @dataProvider instanceProvider
381
	 */
382
	public function testGetReference( ReferenceList $references ) {
383
		$this->assertNull( $references->getReference( '~=[,,_,,]:3' ) );
384
385
		/**
386
		 * @var Reference $reference
387
		 */
388
		foreach ( $references as $reference ) {
389
			$this->assertTrue( $reference->equals( $references->getReference( $reference->getHash() ) ) );
390
		}
391
	}
392
393
	/**
394
	 * @dataProvider instanceProvider
395
	 */
396
	public function testRemoveReferenceHash( ReferenceList $references ) {
397
		$references->removeReferenceHash( '~=[,,_,,]:3' );
398
399
		$hashes = [];
400
401
		/**
402
		 * @var Reference $reference
403
		 */
404
		foreach ( $references as $reference ) {
405
			$hashes[] = $reference->getHash();
406
		}
407
408
		foreach ( $hashes as $hash ) {
409
			$references->removeReferenceHash( $hash );
410
		}
411
412
		$this->assertTrue( $references->isEmpty() );
413
	}
414
415
	/**
416
	 * This integration test (relies on ReferenceList::getValueHash) is supposed to break whenever the hash
417
	 * calculation changes.
418
	 */
419
	public function testGetValueHashStability() {
420
		$array = new ReferenceList();
421
		$snak1 = new PropertyNoValueSnak( 1 );
422
		$snak2 = new PropertyNoValueSnak( 3 );
423
		$snak3 = new PropertyNoValueSnak( 2 );
424
425
		$array->addNewReference( $snak1, $snak2, $snak3 );
426
		$expectedHash = '92244e1a91f60b7fa922d42441995442bf50adb5';
427
		$this->assertSame( $expectedHash, $array->getValueHash() );
428
	}
429
430
	public function testRemoveReferenceHashRemovesIdenticalObjects() {
431
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
432
		$references = new ReferenceList( [ $reference, $reference ] );
433
434
		$references->removeReferenceHash( $reference->getHash() );
435
436
		$this->assertTrue( $references->isEmpty() );
437
	}
438
439
	public function testRemoveReferenceHashDoesNotRemoveCopies() {
440
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
441
		$references = new ReferenceList( [ $reference, clone $reference ] );
442
443
		$references->removeReferenceHash( $reference->getHash() );
444
445
		$this->assertFalse( $references->isEmpty() );
446
		$this->assertTrue( $references->hasReference( $reference ) );
447
		$this->assertNotSame( $reference, $references->getReference( $reference->getHash() ) );
448
	}
449
450
	public function testRemoveReferenceHashUpdatesIndexes() {
451
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
452
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
453
		$references = new ReferenceList( [ $reference1, $reference2 ] );
454
455
		$references->removeReferenceHash( $reference1->getHash() );
456
457
		$this->assertSame( 0, $references->indexOf( $reference2 ) );
458
	}
459
460
	public function testGivenOneSnak_addNewReferenceAddsSnak() {
461
		$references = new ReferenceList();
462
		$snak = new PropertyNoValueSnak( 1 );
463
464
		$references->addNewReference( $snak );
465
		$this->assertTrue( $references->hasReference( new Reference( [ $snak ] ) ) );
466
	}
467
468
	public function testGivenMultipleSnaks_addNewReferenceAddsThem() {
469
		$references = new ReferenceList();
470
		$snak1 = new PropertyNoValueSnak( 1 );
471
		$snak2 = new PropertyNoValueSnak( 3 );
472
		$snak3 = new PropertyNoValueSnak( 2 );
473
474
		$references->addNewReference( $snak1, $snak2, $snak3 );
475
476
		$expectedSnaks = [ $snak1, $snak2, $snak3 ];
477
		$this->assertTrue( $references->hasReference( new Reference( $expectedSnaks ) ) );
478
	}
479
480
	public function testGivenAnArrayOfSnaks_addNewReferenceAddsThem() {
481
		$references = new ReferenceList();
482
		$snaks = [
483
			new PropertyNoValueSnak( 1 ),
484
			new PropertyNoValueSnak( 3 ),
485
			new PropertyNoValueSnak( 2 )
486
		];
487
488
		$references->addNewReference( $snaks );
489
		$this->assertTrue( $references->hasReference( new Reference( $snaks ) ) );
490
	}
491
492
	public function testAddNewReferenceDoesNotIgnoreIdenticalObjects() {
493
		$list = new ReferenceList();
494
		$snak = new PropertyNoValueSnak( 1 );
495
		$list->addNewReference( $snak );
496
		$list->addNewReference( $snak );
497
		$this->assertCount( 2, $list );
498
	}
499
500
	public function testAddNewReferenceDoesNotIgnoreCopies() {
501
		$list = new ReferenceList();
502
		$snak = new PropertyNoValueSnak( 1 );
503
		$list->addNewReference( $snak );
504
		$list->addNewReference( clone $snak );
505
		$this->assertCount( 2, $list );
506
	}
507
508
	public function testGivenNoneSnak_addNewReferenceThrowsException() {
509
		$references = new ReferenceList();
510
511
		$this->expectException( InvalidArgumentException::class );
512
		$references->addNewReference( new PropertyNoValueSnak( 1 ), null );
513
	}
514
515
	public function testEmptySerializationStability() {
516
		$list = new ReferenceList();
517
		$this->assertSame( 'a:0:{}', $list->serialize() );
518
	}
519
520
	/**
521
	 * This test will change when the serialization format changes.
522
	 * If it is being changed intentionally, the test should be updated.
523
	 * It is just here to catch unintentional changes.
524
	 */
525
	public function testSerializationStability() {
526
		$list = new ReferenceList();
527
		$list->addNewReference( new PropertyNoValueSnak( 1 ) );
528
529
		/*
530
		 * https://wiki.php.net/rfc/custom_object_serialization
531
		 */
532
		if ( version_compare( phpversion(), '7.4', '>=' ) ) {
533
			$testString = "a:1:{i:0;O:28:\"Wikibase\\DataModel\\Reference\":1:{s:35:\"\x00Wikibase\\DataModel\\"
534
				. "Reference\x00snaks\";O:32:\"Wikibase\\DataModel\\Snak\\SnakList\":2:{s:4:\""
535
				. 'data";a:1:{i:0;C:43:"Wikibase\\DataModel\\Snak\\PropertyNoValueSnak":2:{P1}}s:5'
536
				. ':"index";i:0;}}}';
537
		} else {
538
			$testString = "a:1:{i:0;O:28:\"Wikibase\\DataModel\\Reference\":1:{s:35:\"\x00Wikibase\\DataModel\\"
539
				. "Reference\x00snaks\";C:32:\"Wikibase\\DataModel\\Snak\\SnakList\":100:{a:2:{s:4:\""
540
				. 'data";a:1:{i:0;C:43:"Wikibase\\DataModel\\Snak\\PropertyNoValueSnak":2:{P1}}s:5'
541
				. ':"index";i:0;}}}}';
542
		}
543
544
		$this->assertSame(
545
			$testString,
546
			$list->serialize()
547
		);
548
	}
549
550
	public function testSerializeUnserializeRoundtrip() {
551
		$original = new ReferenceList();
552
		$original->addNewReference( new PropertyNoValueSnak( 1 ) );
553
554
		/** @var ReferenceList $clone */
555
		$clone = unserialize( serialize( $original ) );
556
557
		$this->assertTrue( $original->equals( $clone ) );
558
		$this->assertSame( $original->getValueHash(), $clone->getValueHash() );
559
		$this->assertSame( $original->serialize(), $clone->serialize() );
560
	}
561
562
	public function testUnserializeCreatesNonIdenticalClones() {
563
		$original = new ReferenceList();
564
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
565
		$original->addReference( $reference );
566
567
		/** @var ReferenceList $clone */
568
		$clone = unserialize( serialize( $original ) );
569
		$clone->addReference( $reference );
570
571
		$this->assertCount( 2, $clone );
572
	}
573
574
	public function testGivenEmptyList_isEmpty() {
575
		$references = new ReferenceList();
576
		$this->assertTrue( $references->isEmpty() );
577
	}
578
579
	public function testGivenNonEmptyList_isNotEmpty() {
580
		$references = new ReferenceList();
581
		$references->addNewReference( new PropertyNoValueSnak( 1 ) );
582
583
		$this->assertFalse( $references->isEmpty() );
584
	}
585
586
}
587