Skip to content

Commit 79a19cd

Browse files
committed
don't let user fall outside of last bucket in rollout
1 parent 6d67a40 commit 79a19cd

File tree

2 files changed

+82
-2
lines changed

2 files changed

+82
-2
lines changed

src/LaunchDarkly/VariationOrRollout.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,23 @@ public function variationIndexForUser($user, $_key, $_salt)
5252
{
5353
if ($this->_variation !== null) {
5454
return $this->_variation;
55-
} elseif ($this->_rollout !== null) {
55+
}
56+
$rollout = $this->_rollout;
57+
if ($rollout == null) {
58+
return null;
59+
}
60+
$variations = $rollout->getVariations();
61+
if ($variations != null && count($variations) > 0) {
5662
$bucketBy = $this->_rollout->getBucketBy() === null ? "key" : $this->_rollout->getBucketBy();
5763
$bucket = self::bucketUser($user, $_key, $bucketBy, $_salt);
5864
$sum = 0.0;
59-
foreach ($this->_rollout->getVariations() as $wv) {
65+
foreach ($variations as $wv) {
6066
$sum += $wv->getWeight() / 100000.0;
6167
if ($bucket < $sum) {
6268
return $wv->getVariation();
6369
}
6470
}
71+
return $variations[count($variations) - 1]->getVariation();
6572
}
6673
return null;
6774
}

tests/FeatureFlagTest.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use LaunchDarkly\LDUser;
99
use LaunchDarkly\LDUserBuilder;
1010
use LaunchDarkly\Segment;
11+
use LaunchDarkly\VariationOrRollout;
1112

1213
const RULE_ID = 'ruleid';
1314

@@ -610,6 +611,78 @@ public function testFlagReturnsErrorIfRuleHasRolloutWithNoVariations()
610611
self::assertEquals(array(), $result->getPrerequisiteEvents());
611612
}
612613

614+
public function testRolloutSelectsBucket()
615+
{
616+
$ub = new LDUserBuilder('userkey');
617+
$user = $ub->build();
618+
$flagKey = 'flagkey';
619+
$salt = 'salt';
620+
621+
// First verify that with our test inputs, the bucket value will be greater than zero and less than 100000,
622+
// so we can construct a rollout whose second bucket just barely contains that value
623+
$bucketValue = floor(VariationOrRollout::bucketUser($user, $flagKey, "key", $salt) * 100000);
624+
self::assertGreaterThan(0, $bucketValue);
625+
self::assertLessThan(100000, $bucketValue);
626+
627+
$badVariationA = 0;
628+
$matchedVariation = 1;
629+
$badVariationB = 2;
630+
$rollout = array(
631+
'variations' => array(
632+
array('variation' => $badVariationA, 'weight' => $bucketValue), // end of bucket range is not inclusive, so it will *not* match the target value
633+
array('variation' => $matchedVariation, 'weight' => 1), // size of this bucket is 1, so it only matches that specific value
634+
array('variation' => $badVariationB, 'weight' => 100000 - ($bucketValue + 1))
635+
)
636+
);
637+
$flag = FeatureFlag::decode(array(
638+
'key' => $flagKey,
639+
'version' => 1,
640+
'deleted' => false,
641+
'on' => true,
642+
'targets' => array(),
643+
'prerequisites' => array(),
644+
'rules' => array(),
645+
'fallthrough' => array('rollout' => $rollout),
646+
'variations' => array('', '', ''),
647+
'salt' => $salt
648+
));
649+
650+
$result = $flag->evaluate($user, null, static::$eventFactory);
651+
self::assertSame($matchedVariation, $result->getDetail()->getVariationIndex());
652+
}
653+
654+
public function testRolloutSelectsLastBucketIfBucketValueEqualsTotalWeight()
655+
{
656+
$ub = new LDUserBuilder('userkey');
657+
$user = $ub->build();
658+
$flagKey = 'flagkey';
659+
$salt = 'salt';
660+
661+
$bucketValue = floor(VariationOrRollout::bucketUser($user, $flagKey, "key", $salt) * 100000);
662+
663+
// We'll construct a list of variations that stops right at the target bucket value
664+
$rollout = array(
665+
'variations' => array(
666+
array('variation' => 0, 'weight' => $bucketValue)
667+
)
668+
);
669+
$flag = FeatureFlag::decode(array(
670+
'key' => $flagKey,
671+
'version' => 1,
672+
'deleted' => false,
673+
'on' => true,
674+
'targets' => array(),
675+
'prerequisites' => array(),
676+
'rules' => array(),
677+
'fallthrough' => array('rollout' => $rollout),
678+
'variations' => array(''),
679+
'salt' => $salt
680+
));
681+
682+
$result = $flag->evaluate($user, null, static::$eventFactory);
683+
self::assertSame(0, $result->getDetail()->getVariationIndex());
684+
}
685+
613686
public function testRolloutCalculationBucketsByUserKeyByDefault()
614687
{
615688
$ub = new LDUserBuilder('userkey');

0 commit comments

Comments
 (0)