-
Notifications
You must be signed in to change notification settings - Fork 36
feat(attribute_value): Don't target NAN, INF, -INF and > 2^53 #151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Let me know if I should extend is_finite_check to revenue and numeric values within this PR. Looks like a PR on parsing revenue value is already pending for a while. #134 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for putting this up! And I'm sorry, I'm not sure why I didn't see this last week.
Let me know if I should extend is_finite_check to revenue and numeric values within this PR. Looks like a PR on parsing revenue value is already pending for a while. #134
We can only effectively accept integers for revenue
, so I'm thinking we should probably just close #134. 😕
I'm asking offline to figure out the exact size limits for an event's revenue
/value
/tags. Sorry for not having an immediate answer!
|
||
with mock.patch('optimizely.helpers.validator.is_finite_number', | ||
return_value='abc') as is_finite: | ||
self.assertEqual('abc', validator.is_attribute_valid('test_attribute', 0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I'm not sure this block is needed.
Let's skip this after all! It's taking a while to get an answer, it's not directly related to our current epic, and we're skipping this for now in other SDKs (e.g. optimizely/objective-c-sdk#348). |
…nt-target-nan-inf-1e53
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this is really testing the behavior of floating-point arithmetic, so I'm not sure we need this assertion. But if we keep it, maybe we could include a code comment like
# float(2**53) + 1.0 evaluates to float(2**53)
so that code readers aren't confused by this scenario
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Just one comment in the tests - I think we're testing value types instead of code branches when it comes to is_finite_number
.
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this is really testing the behavior of floating-point arithmetic, so I'm not sure we need this assertion. But if we keep it, maybe we could include a code comment like
# float(2**53) + 1.0 evaluates to float(2**53)
so that code readers aren't confused by this scenario
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, thanks for figuring out the need for + 2
!
side_effect=side_effect) as is_finite: | ||
self.assertIsNone(evaluator.evaluate(0)) | ||
is_finite.assert_called_with(long(49)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh, I like the idea of using side_effect
here. Can we go farther with this and test that is_finite
only needs to reject the condition value or the user attribute value in order to stop condition evaluation?
Something like
def test_greater_than__calls_is_finite_number(self):
evaluator = condition_helper.CustomAttributeConditionEvaluator(
gt_int_condition_list, {'meters_travelled': 48.1}
)
def is_finite_number__rejecting_condition_value(value):
if value == 48:
return False
return True
with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=is_finite_number__rejecting_condition_value):
self.assertIsNone(evaluator.evaluate(0))
def is_finite_number__rejecting_user_attribute_value(value):
if value == 48.1:
return False
return True
with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=is_finite_number__rejecting_user_attribute_value):
self.assertIsNone(evaluator.evaluate(0))
def is_finite_number__accepting_both_values(value):
return True
with mock.patch('optimizely.helpers.validator.is_finite_number',
side_effect=is_finite_number__accepting_both_values):
self.assertIsNone(evaluator.evaluate(0))
I don't think we need to test ints, longs, and floats separately... not here, at least... we can probably get away with test_greater_than__calls_is_finite_number
, test_less_than__calls_is_finite_number
, and maybe test_exact__given_number_values__calls_is_finite_number
. If that makes sense.
@oakbani @aliabbasrizvi what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice suggestion. Revised accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @oakbani! test_greater_than__calls_is_finite_number
and test_less_than__calls_is_finite_number
look good. Can you also update test_exact__given_number_values__calls_is_finite_number
to test the same three cases instead of only testing two cases?
optimizely/helpers/validator.py
Outdated
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: or absolute limit of 2^53
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Late to the party, but wouldn't I want this limit for Double type variables and Integer (sys.maxint) for Integer variables?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I get it. I am mixing feature variables here. Apologies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aliabbasrizvi it's true that Python integers can have larger values than 2^53, but they're passed to the backend as JSON Numbers which are best limited to 2^53. Does that clarify why we limit integers too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More or less looks good. Minor feedback around docstrings and naming.
Please address and then @nchilada can sign off as well.
exact_int_condition_list, {'lasers_count': 9000} | ||
) | ||
|
||
with mock.patch('optimizely.helpers.validator.is_finite_number', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit. Please be explicit in naming the mocks. ...as mock_is_finite
@@ -286,6 +282,27 @@ def test_exact_float__returns_null__when_no_user_provided_value(self): | |||
|
|||
self.assertIsNone(evaluator.evaluate(0)) | |||
|
|||
def test_exact__given_number_values__calls_is_finite_number(self): | |||
""" Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit. Looks like an incorrect statement? What Returns
? evaluator right?
Perhaps update Test that CustomAttributeConditionEvaluator.evaluator returns True ....
Please apply this comment to subsequent tests as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One last test request! Thanks so much, this is looking good
side_effect=side_effect) as is_finite: | ||
self.assertIsNone(evaluator.evaluate(0)) | ||
is_finite.assert_called_with(long(49)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @oakbani! test_greater_than__calls_is_finite_number
and test_less_than__calls_is_finite_number
look good. Can you also update test_exact__given_number_values__calls_is_finite_number
to test the same three cases instead of only testing two cases?
return_value=True) as mock_is_finite: | ||
self.assertTrue(evaluator.evaluate(0)) | ||
mock_is_finite.assert_called_with(9000) | ||
side_effect=[False, True]) as mock_is_finite: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nchilada In exact match need to send exactly same values to ensure that it's the mocked is_finite that is really driving the result. Therefore, can't make a value-base side-effect method as in gt/lt methods.
This returns False on 1st call and True on 2nd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, you're right. Good solution. Thanks @oakbani!
return_value=True) as mock_is_finite: | ||
self.assertTrue(evaluator.evaluate(0)) | ||
mock_is_finite.assert_called_with(9000) | ||
side_effect=[False, True]) as mock_is_finite: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, you're right. Good solution. Thanks @oakbani!
Summary
Don't target NAN, INF or -INF doubles
Don't target Values > 2^53.
Modified is_attribute_valid to check if the number is finite.
Test plan
Issues
OASIS-3654
#146 (comment)