Skip to content

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

Merged
merged 8 commits into from
Dec 28, 2018

Conversation

oakbani
Copy link
Contributor

@oakbani oakbani commented Dec 10, 2018

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)

@oakbani
Copy link
Contributor Author

oakbani commented Dec 10, 2018

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

@coveralls
Copy link

coveralls commented Dec 10, 2018

Coverage Status

Coverage increased (+0.08%) to 99.695% when pulling 3137fd4 on oakbani/-dont-target-nan-inf-1e53 into 3121437 on master.

Copy link
Contributor

@nchilada nchilada left a 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))
Copy link
Contributor

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.

@nchilada
Copy link
Contributor

nchilada commented Dec 20, 2018

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!

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).

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))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

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

@oakbani oakbani changed the title feat(attribute_value): Don't target NAN, INF, -INF and Integers > 1e53 feat(attribute_value): Don't target NAN, INF, -INF and > 2^53 Dec 20, 2018
Copy link
Contributor

@nchilada nchilada left a 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))
Copy link
Contributor

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))
Copy link
Contributor

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))

Copy link
Contributor

@nchilada nchilada Dec 20, 2018

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice suggestion. Revised accordingly.

Copy link
Contributor

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?


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.
Copy link
Contributor

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

Copy link
Contributor

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

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.

Copy link
Contributor

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?

Copy link
Contributor

@aliabbasrizvi aliabbasrizvi left a 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',
Copy link
Contributor

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. """
Copy link
Contributor

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.

Copy link
Contributor

@nchilada nchilada left a 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))

Copy link
Contributor

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:
Copy link
Contributor Author

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.

Copy link
Contributor

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:
Copy link
Contributor

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!

@aliabbasrizvi aliabbasrizvi merged commit 2c35eb8 into master Dec 28, 2018
@aliabbasrizvi aliabbasrizvi deleted the oakbani/-dont-target-nan-inf-1e53 branch December 28, 2018 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants