-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Issue #3473 #3474
Issue #3473 #3474
Conversation
…on is a string. If "expected" is a string then raise a typeError and a friendly message.
Would it also make sense to add a small test, checking against the expected type error when passing in a string? |
There are two tests I could write.
|
xfail is for tests you want to fix, this is a test for a exception condition that is supposed to happen - so use pytest.raises |
Do you consider the first test a unit test and the second one an integration (where you actually try to run a test that uses approx with a string? If so both tests sound useful. As Ronny said, using |
… that a TypeError is raised correctly when a string is passed into approx().
Added a single test case that uses pytest.raises as @RonnyPfannschmidt suggested. |
@@ -443,8 +443,9 @@ def approx(expected, rel=None, abs=None, nan_ok=False): | |||
# This has the advantage that it made it easy to support mapping types | |||
# (i.e. dict). The old code accepted mapping types, but would only compare | |||
# their keys, which is probably not what most people would expect. | |||
|
|||
if _is_numpy_array(expected): | |||
if isinstance(expected, String): |
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.
Perhaps it would be better if we change the else:
clause here to check for float
/int
, and then add an else:
clause which fails with everything else?
elif isinstance(expected, Decimal):
cls = ApproxDecimal
elif isinstance(expected, (float,) + six.integer_types):
cls = ApproxScalar
else:
raise TypeError('Unknown type passed to approx: {!r} ({!r})'.format(expected, type(expected))
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.
But isn't string the only thing that cannot currently be approx'd?
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.
Nevermind! I did some tests and noticed it doesnt accept things I thought it could.
if _is_numpy_array(expected):
cls = ApproxNumpy
elif isinstance(expected, Mapping):
cls = ApproxMapping
elif isinstance(expected, Sequence) and not isinstance(expected, String):
cls = ApproxSequence
elif isinstance(expected, Decimal):
cls = ApproxDecimal
elif isinstance(expected, (float,) + six.integer_types):
cls = ApproxScalar
else:
raise TypeError('Unknown type passed to approx: {!r} ({!r})'.format(expected, type(expected))
Would be the final commit correct?
Im not sure how that gets us from passing in [6, (5,4)] though. Because the above clauses would see it as valid, but math.isinf freaks out about it (rightly so).
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.
Hey @Drew-Ack, thanks for replying so quickly!
Im not sure how that gets us from passing in [6, (5,4)] though. Because the above clauses would see it as valid, but math.isinf freaks out about it (rightly so).
I'm not sure I follow, could you clarify please?
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.
No problem! Love pytest afterall :)
As an example this elif:
elif isinstance(expected, Sequence) and not isinstance(expected, String):
cls = ApproxSequence
allows this [5, (6,7)] as valid input into the approx function. This technically shouldnt be, because even though its a sequence it will just blow up when appox runs.
The issue comes when math.isinf runs. math.isinf is evaluated in the ApproxSequence class. I show this in #3473
I assume this clause is used purely to delegate which class to use, but provides no checking to make sure the sequence is valid.
Is this something we should try to do?
Should we throw an exception out when an invalid sequence like [5, (6,7)] is given
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.
allows this [5, (6,7)] as valid input into the approx function.
Oh my bad, it was obvious you meant [5, (6, 7)]
the literal. Sorry about that.
This technically shouldnt be, because even though its a sequence it will just blow up when appox runs.
I see. I guess we can iterate over every element of Sequence
and Mapping
to ensure their elements are float
or int
objects; there might be a performance problem but I don't think people are using pytest.approx
with arrays so large that this would make a difference.
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.
This is the first open source project I've contributed too, so I will defer to you developers on what is best practice.
It seems to me we are just picking what point to throw an exception at.
I put together a small test case to see how long it would take :)
def test_time():
mylist = []
for x in range(1000000):
mylist.append(x)
start_time = time.time()
for x in mylist:
if isinstance(x, float) or isinstance(x, int):
continue
end_time = time.time()
print(end_time - start_time)
Its roughly .28 seconds to run through 1 million elements.
Its roughly 2.8 seconds to run through 10 million.
The pattern holds as you increase by x10
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.
This is the first open source project I've contributed too, so I will defer to you developers on what is best practice.
This is great, thanks for contributing to pytest, we definitely appreciate it!
What do you think @RonnyPfannschmidt? I think we can get away by keeping things simple and just going with my suggestion in #3474 (comment), but I don't oppose if others prefer to be more through.
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.
basically massive sequences shouldn't use python lists ^^ so the check should be reasonably fine for normal sequences
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.
OK, @Drew-Ack could go with a more through check for sequences then? Thanks!
I am unsure why the AppVeyor build failed. |
Looks like a version conflict with |
@@ -450,6 +450,9 @@ def approx(expected, rel=None, abs=None, nan_ok=False): | |||
elif isinstance(expected, Mapping): | |||
cls = ApproxMapping | |||
elif isinstance(expected, Sequence) and not isinstance(expected, String): | |||
for value in expected: | |||
if isinstance(value, (Sequence, String, Mapping)): |
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 believe it is better to do check for numbers, and provide a better message to locate the problem:
number_types = six.integer_types + (float,)
for index, value in enumerate(expected):
if not isinstance(value, number_types):
msg = 'Sequence must contain only numbers, but found {!r} ({!r}) at index {}'
raise TypeError(msg.format(value, type(value), index))
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.
We also should add a test for this 😉
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 cant just check for int, float, and long though. I would also have to check for each numpy type too. I do agree that a better message should be there :)
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.
@nicoddemus How should I approach numpy types?
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's already a elif _is_numpy_array(expected):
check near the start of the if/elif chain, so numpy.array
are covered already.
There's the situation where the user passes a builtin list
containing numpy
numbers, but that seems rare to me. So we can either:
-
Also check every possible number type, including numpy's:
# _get_numpy_types() returns a tuple (numpy.int32, numpy.int64, ...) if numpy is available, # otherwise an empty tuple number_types = six.integer_types + (float,) + self._get_numpy_types()
-
Consider this overkill and just fail when we encounter a
list
containing numpy types. In this case
should make sure to show the data type in the error message (like in my previous example).
I think it is worthwhile trying to go with 1), this will make things more convenient for our users at the cost of a little more
code.
What do you folks think?
@@ -450,6 +450,9 @@ def approx(expected, rel=None, abs=None, nan_ok=False): | |||
elif isinstance(expected, Mapping): | |||
cls = ApproxMapping | |||
elif isinstance(expected, Sequence) and not isinstance(expected, String): | |||
for value in expected: | |||
if isinstance(value, (Sequence, String, Mapping)): |
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.
We also should add a test for this 😉
@Drew-Ack gentle ping. 😁 |
A small change to how pytest.approx works.
This is to address issue #3473