Skip to content

Commit 683872f

Browse files
authored
Merge pull request #47 from launchdarkly/eb/ch43307/bucketing-fix
don't let user fall outside of last bucket in rollout
2 parents e6c0e97 + 679e5ca commit 683872f

File tree

2 files changed

+84
-2
lines changed

2 files changed

+84
-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) {
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: 75 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,80 @@ 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+
'offVariation' => null,
643+
'targets' => array(),
644+
'prerequisites' => array(),
645+
'rules' => array(),
646+
'fallthrough' => array('rollout' => $rollout),
647+
'variations' => array('', '', ''),
648+
'salt' => $salt
649+
));
650+
651+
$result = $flag->evaluate($user, null, static::$eventFactory);
652+
self::assertSame($matchedVariation, $result->getDetail()->getVariationIndex());
653+
}
654+
655+
public function testRolloutSelectsLastBucketIfBucketValueEqualsTotalWeight()
656+
{
657+
$ub = new LDUserBuilder('userkey');
658+
$user = $ub->build();
659+
$flagKey = 'flagkey';
660+
$salt = 'salt';
661+
662+
$bucketValue = floor(VariationOrRollout::bucketUser($user, $flagKey, "key", $salt) * 100000);
663+
664+
// We'll construct a list of variations that stops right at the target bucket value
665+
$rollout = array(
666+
'variations' => array(
667+
array('variation' => 0, 'weight' => $bucketValue)
668+
)
669+
);
670+
$flag = FeatureFlag::decode(array(
671+
'key' => $flagKey,
672+
'version' => 1,
673+
'deleted' => false,
674+
'on' => true,
675+
'offVariation' => null,
676+
'targets' => array(),
677+
'prerequisites' => array(),
678+
'rules' => array(),
679+
'fallthrough' => array('rollout' => $rollout),
680+
'variations' => array(''),
681+
'salt' => $salt
682+
));
683+
684+
$result = $flag->evaluate($user, null, static::$eventFactory);
685+
self::assertSame(0, $result->getDetail()->getVariationIndex());
686+
}
687+
613688
public function testRolloutCalculationBucketsByUserKeyByDefault()
614689
{
615690
$ub = new LDUserBuilder('userkey');

0 commit comments

Comments
 (0)