Skip to content

Commit

Permalink
Merge 66159a8 into ad0cb1d
Browse files Browse the repository at this point in the history
  • Loading branch information
oakbani committed Dec 20, 2018
2 parents ad0cb1d + 66159a8 commit f248258
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 8 deletions.
13 changes: 10 additions & 3 deletions optimizely/helpers/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,20 +187,24 @@ def is_attribute_valid(attribute_key, attribute_value):
if not isinstance(attribute_key, string_types):
return False

if isinstance(attribute_value, string_types) or type(attribute_value) in (int, float, bool):
if isinstance(attribute_value, (string_types, bool)):
return True

if isinstance(attribute_value, (numbers.Integral, float)):
return is_finite_number(attribute_value)

return False


def is_finite_number(value):
""" Method to validate if the given value is a number and not one of NAN, INF, -INF.
""" Validates if the given value is a number, enforces
absolute limit of 2^53 and restricts NAN, INF, -INF.
Args:
value: Value to be validated.
Returns:
Boolean: True if value is a number and not NAN, INF or -INF else False.
Boolean: True if value is a number and not NAN, INF, -INF or greater than 2^53 else False.
"""
if not isinstance(value, (numbers.Integral, float)):
# numbers.Integral instead of int to accomodate long integer in python 2
Expand All @@ -214,6 +218,9 @@ def is_finite_number(value):
if math.isnan(value) or math.isinf(value):
return False

if abs(value) > (2**53):
return False

return True


Expand Down
5 changes: 5 additions & 0 deletions tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@

import json
import unittest
from six import PY3

from optimizely import optimizely

if PY3:
def long(a):
raise NotImplementedError('Tests should only call `long` if running in PY2')


class BaseTest(unittest.TestCase):

Expand Down
212 changes: 207 additions & 5 deletions tests/helpers_tests/test_condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@
# limitations under the License.

import mock
from six import PY2, PY3
from six import PY2

from optimizely.helpers import condition as condition_helper

from tests import base

if PY3:
def long(a):
raise NotImplementedError('Tests should only call `long` if running in PY2')

browserConditionSafari = ['browser_type', 'safari', 'custom_attribute', 'exact']
booleanCondition = ['is_firefox', True, 'custom_attribute', 'exact']
integerCondition = ['num_users', 10, 'custom_attribute', 'exact']
Expand Down Expand Up @@ -205,6 +201,37 @@ def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition

self.assertStrictTrue(evaluator.evaluate(0))

def test_exact_int__calls_is_finite_number(self):
""" Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """

if PY2:
evaluator = condition_helper.CustomAttributeConditionEvaluator(
exact_int_condition_list, {'lasers_count': long(9000)}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=True) as is_finite:
self.assertStrictTrue(evaluator.evaluate(0))
is_finite.assert_called_with(long(9000))

evaluator = condition_helper.CustomAttributeConditionEvaluator(
exact_int_condition_list, {'lasers_count': 9000}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=False) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_called_with(9000)

evaluator = condition_helper.CustomAttributeConditionEvaluator(
exact_int_condition_list, {'lasers_count': 9000.0}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=False) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_called_with(9000.0)

def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self):

if PY2:
Expand All @@ -226,6 +253,37 @@ def test_exact_float__returns_true__when_user_provided_value_is_equal_to_conditi

self.assertStrictTrue(evaluator.evaluate(0))

def test_exact_float__calls_is_finite_number(self):
""" Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """

if PY2:
evaluator = condition_helper.CustomAttributeConditionEvaluator(
exact_float_condition_list, {'lasers_count': long(9000)}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=True) as is_finite:
self.assertStrictTrue(evaluator.evaluate(0))
is_finite.assert_called_with(long(9000))

evaluator = condition_helper.CustomAttributeConditionEvaluator(
exact_float_condition_list, {'lasers_count': 9000}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=False) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_called_with(9000)

evaluator = condition_helper.CustomAttributeConditionEvaluator(
exact_float_condition_list, {'lasers_count': 9000.0}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=False) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_called_with(9000.0)

def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self):

evaluator = condition_helper.CustomAttributeConditionEvaluator(
Expand Down Expand Up @@ -371,6 +429,42 @@ def test_greater_than_int__returns_true__when_user_value_greater_than_condition_

self.assertStrictTrue(evaluator.evaluate(0))

def test_greater_than_int__calls_is_finite_number(self):
""" Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """

evaluator = condition_helper.CustomAttributeConditionEvaluator(
gt_int_condition_list, {'meters_travelled': 48.1}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=True) as is_finite:
self.assertStrictTrue(evaluator.evaluate(0))
is_finite.assert_called_with(48.1)

evaluator = condition_helper.CustomAttributeConditionEvaluator(
gt_int_condition_list, {'meters_travelled': 49}
)

def side_effect(*args):
if args[0] == 49:
return False
return True

with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=side_effect) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_any_call(49)

if PY2:
evaluator = condition_helper.CustomAttributeConditionEvaluator(
gt_int_condition_list, {'meters_travelled': long(49)}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=side_effect) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_called_with(long(49))

def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self):

evaluator = condition_helper.CustomAttributeConditionEvaluator(
Expand All @@ -392,6 +486,42 @@ def test_greater_than_float__returns_true__when_user_value_greater_than_conditio

self.assertStrictTrue(evaluator.evaluate(0))

def test_greater_than_float__calls_is_finite_number(self):
""" Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """

evaluator = condition_helper.CustomAttributeConditionEvaluator(
gt_float_condition_list, {'meters_travelled': 48.3}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=True) as is_finite:
self.assertStrictTrue(evaluator.evaluate(0))
is_finite.assert_called_with(48.3)

def side_effect(*args):
if args[0] == 49:
return False
return True

evaluator = condition_helper.CustomAttributeConditionEvaluator(
gt_float_condition_list, {'meters_travelled': 49}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=side_effect) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_called_with(49)

if PY2:
evaluator = condition_helper.CustomAttributeConditionEvaluator(
gt_float_condition_list, {'meters_travelled': long(49)}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=side_effect) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_called_with(long(49))

def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self):

evaluator = condition_helper.CustomAttributeConditionEvaluator(
Expand Down Expand Up @@ -499,6 +629,42 @@ def test_less_than_int__returns_true__when_user_value_less_than_condition_value(

self.assertStrictTrue(evaluator.evaluate(0))

def test_less_than_int__calls_is_finite_number(self):
""" Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """

evaluator = condition_helper.CustomAttributeConditionEvaluator(
lt_int_condition_list, {'meters_travelled': 47.9}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=True) as is_finite:
self.assertStrictTrue(evaluator.evaluate(0))
is_finite.assert_called_with(47.9)

evaluator = condition_helper.CustomAttributeConditionEvaluator(
lt_int_condition_list, {'meters_travelled': 47}
)

def side_effect(*args):
if args[0] == 47:
return False
return True

with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=side_effect) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_any_call(47)

if PY2:
evaluator = condition_helper.CustomAttributeConditionEvaluator(
lt_int_condition_list, {'meters_travelled': long(47)}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=side_effect) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_called_with(long(47))

def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self):

evaluator = condition_helper.CustomAttributeConditionEvaluator(
Expand All @@ -520,6 +686,42 @@ def test_less_than_float__returns_true__when_user_value_less_than_condition_valu

self.assertStrictTrue(evaluator.evaluate(0))

def test_less_than_float__calls_is_finite_number(self):
""" Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """

evaluator = condition_helper.CustomAttributeConditionEvaluator(
lt_float_condition_list, {'meters_travelled': 48.1}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=True) as is_finite:
self.assertStrictTrue(evaluator.evaluate(0))
is_finite.assert_called_with(48.1)

evaluator = condition_helper.CustomAttributeConditionEvaluator(
lt_float_condition_list, {'meters_travelled': 48}
)

def side_effect(*args):
if args[0] == 48:
return False
return True

with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=side_effect) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_any_call(48)

if PY2:
evaluator = condition_helper.CustomAttributeConditionEvaluator(
lt_float_condition_list, {'meters_travelled': long(48)}
)

with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=side_effect) as is_finite:
self.assertIsNone(evaluator.evaluate(0))
is_finite.assert_called_with(long(48))

def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self):

evaluator = condition_helper.CustomAttributeConditionEvaluator(
Expand Down
58 changes: 58 additions & 0 deletions tests/helpers_tests/test_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
# limitations under the License.

import json
import mock

from six import PY2

from optimizely import error_handler
from optimizely import event_dispatcher
Expand Down Expand Up @@ -168,6 +171,61 @@ def test_is_attribute_valid(self):
self.assertTrue(validator.is_attribute_valid('test_attribute', ""))
self.assertTrue(validator.is_attribute_valid('test_attribute', 'test_value'))

# test if attribute value is a number, it calls is_finite_number and returns it's result
with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=True) as is_finite:
self.assertTrue(validator.is_attribute_valid('test_attribute', 5))

is_finite.assert_called_once_with(5)

with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=False) as is_finite:
self.assertFalse(validator.is_attribute_valid('test_attribute', 5.5))

is_finite.assert_called_once_with(5.5)

if PY2:
with mock.patch('optimizely.helpers.validator.is_finite_number',
return_value=None) as is_finite:
self.assertIsNone(validator.is_attribute_valid('test_attribute', long(5)))

is_finite.assert_called_once_with(long(5))

def test_is_finite_number(self):
""" Test that it returns true if value is a number and not NAN, INF, -INF or greater than 2^53.
Otherwise False.
"""
# test non number values
self.assertFalse(validator.is_finite_number('HelloWorld'))
self.assertFalse(validator.is_finite_number(True))
self.assertFalse(validator.is_finite_number(False))
self.assertFalse(validator.is_finite_number(None))
self.assertFalse(validator.is_finite_number({}))
self.assertFalse(validator.is_finite_number([]))
self.assertFalse(validator.is_finite_number(()))

# test invalid numbers
self.assertFalse(validator.is_finite_number(float('inf')))
self.assertFalse(validator.is_finite_number(float('-inf')))
self.assertFalse(validator.is_finite_number(float('nan')))
self.assertFalse(validator.is_finite_number(int(2**53) + 1))
self.assertFalse(validator.is_finite_number(-int(2**53) - 1))
self.assertFalse(validator.is_finite_number(float(2**53) + 2.0))
self.assertFalse(validator.is_finite_number(-float(2**53) - 2.0))
if PY2:
self.assertFalse(validator.is_finite_number(long(2**53) + 1))
self.assertFalse(validator.is_finite_number(-long(2**53) - 1))

# test valid numbers
self.assertTrue(validator.is_finite_number(0))
self.assertTrue(validator.is_finite_number(5))
self.assertTrue(validator.is_finite_number(5.5))
self.assertTrue(validator.is_finite_number(float(2**53) + 1.0))
self.assertTrue(validator.is_finite_number(-float(2**53) - 1.0))
self.assertTrue(validator.is_finite_number(int(2**53)))
if PY2:
self.assertTrue(validator.is_finite_number(long(2**53)))


class DatafileValidationTests(base.BaseTest):

Expand Down

0 comments on commit f248258

Please sign in to comment.