Skip to content

Commit 943b151

Browse files
fix: created a new matcher for numbers (#230)
* created a new matcher for numbers * missed a file * add license header
1 parent 1e90b56 commit 943b151

File tree

6 files changed

+162
-3
lines changed

6 files changed

+162
-3
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
*
3+
* Copyright 2018, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.config.audience.match;
18+
19+
import javax.annotation.Nullable;
20+
21+
// Because json number is a double in most java json parsers. at this
22+
// point we allow comparision of Integer and Double. The instance class is Double and
23+
// Integer which would fail in our normal exact match. So, we are special casing for now. We have already filtered
24+
// out other Number types.
25+
public class ExactNumberMatch extends AttributeMatch<Number> {
26+
Number value;
27+
28+
protected ExactNumberMatch(Number value) {
29+
this.value = value;
30+
}
31+
32+
public @Nullable
33+
Boolean eval(Object attributeValue) {
34+
try {
35+
return value.doubleValue() == convert(attributeValue).doubleValue();
36+
} catch (Exception e) {
37+
MatchType.logger.error("Exact number match failed ", e);
38+
}
39+
40+
return null;
41+
}
42+
}

core-api/src/main/java/com/optimizely/ab/config/audience/match/MatchType.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static MatchType getMatchType(String matchType, Object conditionValue) {
3838
if (conditionValue instanceof String) {
3939
return new MatchType(matchType, new ExactMatch<String>((String) conditionValue));
4040
} else if (isValidNumber(conditionValue)) {
41-
return new MatchType(matchType, new ExactMatch<Number>((Number) conditionValue));
41+
return new MatchType(matchType, new ExactNumberMatch((Number) conditionValue));
4242
} else if (conditionValue instanceof Boolean) {
4343
return new MatchType(matchType, new ExactMatch<Boolean>((Boolean) conditionValue));
4444
}
@@ -94,4 +94,4 @@ Match getMatcher() {
9494
public String toString() {
9595
return matchType;
9696
}
97-
}
97+
}

core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,100 @@ public void activateEndToEndWithTypedAudienceInt() throws Exception {
279279
verify(mockEventHandler).dispatchEvent(logEventToDispatch);
280280
}
281281

282+
/**
283+
* Verify that activating using typed audiences works for numeric match exact using double and integer.
284+
*/
285+
@Test
286+
public void activateEndToEndWithTypedAudienceIntExactDouble() throws Exception {
287+
Experiment activatedExperiment;
288+
Map<String, Object> testUserAttributes = new HashMap<String, Object>();
289+
String bucketingKey = testBucketingIdKey;
290+
String userId = testUserId;
291+
String bucketingId = testBucketingId;
292+
if(datafileVersion >= 4) {
293+
activatedExperiment = validProjectConfig.getExperimentKeyMapping().get(EXPERIMENT_TYPEDAUDIENCE_EXPERIMENT_KEY);
294+
testUserAttributes.put(ATTRIBUTE_INTEGER_KEY, 1.0); // should be equal 1.
295+
}
296+
else {
297+
return; // only test on v4 datafiles.
298+
}
299+
testUserAttributes.put(bucketingKey, bucketingId);
300+
Variation bucketedVariation = activatedExperiment.getVariations().get(0);
301+
EventFactory mockEventFactory = mock(EventFactory.class);
302+
303+
Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler)
304+
.withBucketing(mockBucketer)
305+
.withEventBuilder(mockEventFactory)
306+
.withConfig(validProjectConfig)
307+
.withErrorHandler(mockErrorHandler)
308+
.build();
309+
310+
311+
when(mockEventFactory.createImpressionEvent(validProjectConfig, activatedExperiment, bucketedVariation, testUserId,
312+
testUserAttributes))
313+
.thenReturn(logEventToDispatch);
314+
315+
when(mockBucketer.bucket(activatedExperiment, bucketingId))
316+
.thenReturn(bucketedVariation);
317+
318+
// activate the experiment
319+
Variation actualVariation = optimizely.activate(activatedExperiment.getKey(), userId, testUserAttributes);
320+
321+
// verify that the bucketing algorithm was called correctly
322+
verify(mockBucketer).bucket(activatedExperiment, bucketingId);
323+
assertThat(actualVariation, is(bucketedVariation));
324+
325+
// verify that dispatchEvent was called with the correct LogEvent object
326+
verify(mockEventHandler).dispatchEvent(logEventToDispatch);
327+
}
328+
329+
/**
330+
* Verify that activating using typed audiences works for numeric match exact using double and integer.
331+
*/
332+
@Test
333+
public void activateEndToEndWithTypedAudienceIntExact() throws Exception {
334+
Experiment activatedExperiment;
335+
Map<String, Object> testUserAttributes = new HashMap<String, Object>();
336+
String bucketingKey = testBucketingIdKey;
337+
String userId = testUserId;
338+
String bucketingId = testBucketingId;
339+
if(datafileVersion >= 4) {
340+
activatedExperiment = validProjectConfig.getExperimentKeyMapping().get(EXPERIMENT_TYPEDAUDIENCE_EXPERIMENT_KEY);
341+
testUserAttributes.put(ATTRIBUTE_INTEGER_KEY, 1); // should be equal 1.
342+
}
343+
else {
344+
return; // only test on v4 datafiles.
345+
}
346+
testUserAttributes.put(bucketingKey, bucketingId);
347+
Variation bucketedVariation = activatedExperiment.getVariations().get(0);
348+
EventFactory mockEventFactory = mock(EventFactory.class);
349+
350+
Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler)
351+
.withBucketing(mockBucketer)
352+
.withEventBuilder(mockEventFactory)
353+
.withConfig(validProjectConfig)
354+
.withErrorHandler(mockErrorHandler)
355+
.build();
356+
357+
358+
when(mockEventFactory.createImpressionEvent(validProjectConfig, activatedExperiment, bucketedVariation, testUserId,
359+
testUserAttributes))
360+
.thenReturn(logEventToDispatch);
361+
362+
when(mockBucketer.bucket(activatedExperiment, bucketingId))
363+
.thenReturn(bucketedVariation);
364+
365+
// activate the experiment
366+
Variation actualVariation = optimizely.activate(activatedExperiment.getKey(), userId, testUserAttributes);
367+
368+
// verify that the bucketing algorithm was called correctly
369+
verify(mockBucketer).bucket(activatedExperiment, bucketingId);
370+
assertThat(actualVariation, is(bucketedVariation));
371+
372+
// verify that dispatchEvent was called with the correct LogEvent object
373+
verify(mockEventHandler).dispatchEvent(logEventToDispatch);
374+
}
375+
282376
/**
283377
* Verify that the {@link Optimizely#activate(Experiment, String, Map)} call correctly builds an endpoint url and
284378
* request params and passes them through {@link EventHandler#dispatchEvent(LogEvent)}.

core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ public class ValidProjectConfigV4 {
9191
CUSTOM_ATTRIBUTE_TYPE, "gt",
9292
AUDIENCE_INT_VALUE)))))))
9393
);
94+
95+
private static final String AUDIENCE_INT_EXACT_ID = "3468206646";
96+
private static final String AUDIENCE_INT_EXACT_KEY = "INTEXACT";
97+
private static final Audience TYPED_AUDIENCE_EXACT_INT = new Audience(
98+
AUDIENCE_INT_EXACT_ID,
99+
AUDIENCE_INT_EXACT_KEY,
100+
new AndCondition(Collections.<Condition>singletonList(
101+
new OrCondition(Collections.<Condition>singletonList(
102+
new OrCondition(Collections.singletonList((Condition) new UserAttribute(ATTRIBUTE_INTEGER_KEY,
103+
CUSTOM_ATTRIBUTE_TYPE, "exact",
104+
AUDIENCE_INT_VALUE)))))))
105+
);
94106
private static final String AUDIENCE_DOUBLE_ID = "3468206645";
95107
private static final String AUDIENCE_DOUBLE_KEY = "DOUBLE";
96108
public static final Double AUDIENCE_DOUBLE_VALUE = 100.0;
@@ -461,6 +473,7 @@ public class ValidProjectConfigV4 {
461473
ProjectConfigTestUtils.createListOfObjects(
462474
AUDIENCE_BOOL_ID,
463475
AUDIENCE_INT_ID,
476+
AUDIENCE_INT_EXACT_ID,
464477
AUDIENCE_DOUBLE_ID
465478
),
466479
ProjectConfigTestUtils.createListOfObjects(
@@ -1173,6 +1186,7 @@ public static ProjectConfig generateValidProjectConfigV4() {
11731186

11741187
List<Audience> typedAudiences = new ArrayList<Audience>();
11751188
typedAudiences.add(TYPED_AUDIENCE_BOOL);
1189+
typedAudiences.add(TYPED_AUDIENCE_EXACT_INT);
11761190
typedAudiences.add(TYPED_AUDIENCE_INT);
11771191
typedAudiences.add(TYPED_AUDIENCE_DOUBLE);
11781192
typedAudiences.add(TYPED_AUDIENCE_GRYFFINDOR);

core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ public void exactMatchConditionEvaluatesNull() throws Exception {
230230
assertNull(testInstanceInteger.evaluate(testTypedUserAttributes));
231231
assertNull(testInstanceDouble.evaluate(testTypedUserAttributes));
232232
assertNull(testInstanceNull.evaluate(testTypedUserAttributes));
233+
Map<String,Object> attr = new HashMap<>();
234+
attr.put("browser_type", "true");
235+
assertNull(testInstanceString.evaluate(attr));
236+
233237
}
234238

235239
/**

core-api/src/test/resources/config/valid-project-config-v4.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
"name": "BOOL",
3434
"conditions": ["and", ["or", ["or", {"name": "booleanKey", "type": "custom_attribute", "match":"exact", "value":true}]]]
3535
},
36+
{
37+
"id": "3468206646",
38+
"name": "INTEXACT",
39+
"conditions": ["and", ["or", ["or", {"name": "integerKey", "type": "custom_attribute", "match":"exact", "value":1.0}]]]
40+
},
3641
{
3742
"id": "3468206644",
3843
"name": "INT",
@@ -182,7 +187,7 @@
182187
"endOfRange": 10000
183188
}
184189
],
185-
"audienceIds": ["3468206643", "3468206644", "3468206645"],
190+
"audienceIds": ["3468206643", "3468206644", "3468206646", "3468206645"],
186191
"forcedVariations": {}
187192
},
188193
{

0 commit comments

Comments
 (0)