Skip to content

Commit 57581b5

Browse files
author
Dave Johnston
authored
[FFM-2217]: Evaluation returning wrong result with multiple target groups (#70)
Segments evaluation had two problems: 1) It returned false if any segment evaluate returned false (even if segment was not part of the clause) 2) It evaluated the target in segmentMatch clause against all segments, where it should only evaluate against the segments defined by the clause. Evaluation Percentage Rollout was also distributing values incorrectly when there were more than two variations. Updated unit tests to ensure segment Evaluation performed as expected
1 parent df6fa51 commit 57581b5

File tree

4 files changed

+111
-21
lines changed

4 files changed

+111
-21
lines changed

evaluation/feature.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,16 @@ func (c *Clause) Evaluate(target *Target, segments Segments, operator types.Valu
5151
if segments == nil {
5252
return false
5353
}
54-
return segments.Evaluate(target)
54+
// Determine if the given target belongs to one of the segments,
55+
// that was specified by the clause
56+
for _, segmentName := range c.Value {
57+
if segment, ok := segments[segmentName]; ok {
58+
if segment.Evaluate(target) {
59+
return true
60+
}
61+
}
62+
}
63+
return false
5564
}
5665

5766
// Ensure operator is valid and not nil
@@ -100,6 +109,7 @@ func (c Clauses) Evaluate(target *Target, segments Segments) bool {
100109
log.Warn(err)
101110
}
102111
}
112+
103113
if !clause.Evaluate(target, segments, op) {
104114
return false
105115
}
@@ -118,9 +128,12 @@ type Distribution struct {
118128
// GetKeyName returns variation identifier based on target
119129
func (d *Distribution) GetKeyName(target *Target) string {
120130
variation := ""
131+
132+
totalPercentage := 0
121133
for _, tdVariation := range d.Variations {
122134
variation = tdVariation.Variation
123-
if d.isEnabled(target, tdVariation.Weight) {
135+
totalPercentage += tdVariation.Weight
136+
if d.isEnabled(target, totalPercentage) {
124137
return variation
125138
}
126139
}

evaluation/feature_test.go

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -556,34 +556,47 @@ func TestClause_Evaluate(t *testing.T) {
556556
Anonymous: &f,
557557
Attributes: &m,
558558
}
559-
tests := []struct {
559+
tests := map[string]struct {
560560
name string
561561
fields fields
562562
args args
563563
want bool
564564
}{
565-
{name: "segment match operator (include)", fields: struct {
566-
Attribute string
567-
ID string
568-
Negate bool
569-
Op string
570-
Value []string
571-
}{Op: segmentMatchOperator, Value: []string{"beta"}},
572-
args: struct {
573-
target *Target
574-
segments Segments
575-
operator types.ValueType
576-
}{target: &target, segments: map[string]*Segment{
565+
"segment match operator (include)": {
566+
fields: fields{Op: segmentMatchOperator, Value: []string{"beta"}},
567+
args: args{target: &target, segments: map[string]*Segment{
577568
"beta": {
578569
Identifier: "beta",
579570
Name: "Beta users",
580571
Included: []string{target.Identifier},
581572
},
582-
}, operator: nil}, want: true},
573+
}, operator: nil},
574+
want: true,
575+
},
576+
"evaluate returns false when clause value does not match any segment": {
577+
fields: fields{Op: segmentMatchOperator, Value: []string{"beta"}},
578+
args: args{
579+
target: &target,
580+
segments: Segments{
581+
"beta": {Identifier: "beta", Included: []string{}},
582+
"alpha": {Identifier: "alpha", Included: []string{target.Identifier}},
583+
}, operator: nil},
584+
want: false,
585+
},
586+
"evaluate returns true when clause value matches segment that target belongs to": {
587+
fields: fields{Op: segmentMatchOperator, Value: []string{"alpha"}},
588+
args: args{
589+
target: &target,
590+
segments: Segments{
591+
"beta": {Identifier: "beta", Excluded: []string{target.Identifier}},
592+
"alpha": {Identifier: "alpha", Included: []string{target.Identifier}},
593+
}, operator: nil},
594+
want: true,
595+
},
583596
}
584-
for _, tt := range tests {
597+
for name, tt := range tests {
585598
val := tt
586-
t.Run(tt.name, func(t *testing.T) {
599+
t.Run(name, func(t *testing.T) {
587600
c := &Clause{
588601
Attribute: val.fields.Attribute,
589602
ID: val.fields.ID,

evaluation/segment.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ type Segments map[string]*Segment
107107
// Evaluate through all segments based on target input
108108
func (s Segments) Evaluate(target *Target) bool {
109109
for _, segment := range s {
110-
if !segment.Evaluate(target) {
111-
return false
110+
if segment.Evaluate(target) {
111+
return true
112112
}
113113
}
114-
return true
114+
return false
115115
}

evaluation/segment_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,67 @@ func TestSegment_Evaluate(t *testing.T) {
5151
})
5252
}
5353
}
54+
55+
func TestSegments_Evaluate(t *testing.T) {
56+
f := false
57+
m := make(map[string]interface{})
58+
m["email"] = "[email protected]"
59+
target := Target{
60+
Identifier: "john",
61+
Anonymous: &f,
62+
Attributes: &m,
63+
}
64+
65+
tests := map[string]struct {
66+
segments Segments
67+
target Target
68+
want bool
69+
}{
70+
"test target included by segment alpha returns true": {
71+
segments: Segments{"alpha": {Identifier: "alpha", Included: []string{target.Identifier}}},
72+
target: target,
73+
want: true,
74+
},
75+
"test target not included segment alpha, but included in beta returns true": {
76+
segments: Segments{
77+
"alpha": {Identifier: "alpha", Included: []string{}},
78+
"beta": {Identifier: "beta", Included: []string{target.Identifier}},
79+
},
80+
target: target,
81+
want: true,
82+
},
83+
"test target not included segment alpha, and not included in beta returns false": {
84+
segments: Segments{
85+
"alpha": {Identifier: "alpha", Included: []string{}},
86+
"beta": {Identifier: "beta", Included: []string{}},
87+
},
88+
target: target,
89+
want: false,
90+
},
91+
"test target included segment alpha, and excluded in beta returns true": {
92+
segments: Segments{
93+
"alpha": {Identifier: "alpha", Included: []string{target.Identifier}},
94+
"beta": {Identifier: "beta", Excluded: []string{target.Identifier}},
95+
},
96+
target: target,
97+
want: true,
98+
},
99+
"test target excluded from segment alpha, and included in beta returns true": {
100+
segments: Segments{
101+
"alpha": {Identifier: "alpha", Excluded: []string{target.Identifier}},
102+
"beta": {Identifier: "beta", Included: []string{target.Identifier}},
103+
},
104+
target: target,
105+
want: true,
106+
},
107+
}
108+
for name, tt := range tests {
109+
val := tt
110+
t.Run(name, func(t *testing.T) {
111+
s := val.segments
112+
if got := s.Evaluate(&val.target); got != val.want {
113+
t.Errorf("Evaluate() = %v, want %v", got, val.want)
114+
}
115+
})
116+
}
117+
}

0 commit comments

Comments
 (0)