Skip to content

bpo-40129: Add fake number classes in test.support. #19262

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod

from test import support
from test.support import is_resource_enabled, ALWAYS_EQ, LARGEST, SMALLEST
from test.support import is_resource_enabled, ALWAYS_EQ, LARGEST, SMALLEST, FakeInt

import datetime as datetime_module
from datetime import MINYEAR, MAXYEAR
Expand Down Expand Up @@ -5076,15 +5076,9 @@ def test_extra_attributes(self):
x.abc = 1

def test_check_arg_types(self):
class Number:
def __init__(self, value):
self.value = value
def __int__(self):
return self.value

for xx in [decimal.Decimal(10),
decimal.Decimal('10.9'),
Number(10)]:
FakeInt(10)]:
with self.assertWarns(DeprecationWarning):
self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
datetime(xx, xx, xx, xx, xx, xx, xx))
Expand All @@ -5093,7 +5087,7 @@ def __int__(self):
r'\(got type str\)$'):
datetime(10, 10, '10')

f10 = Number(10.9)
f10 = FakeInt(10.9)
with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
r'\(type float\)$'):
datetime(10, 10, f10)
Expand Down
40 changes: 28 additions & 12 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"run_with_tz", "PGO", "missing_compiler_executable", "fd_count",
"ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST",
"LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT",
"FakeIndex", "FakeInt", "FakeFloat", "FakeComplex", "FakePath",
]


Expand Down Expand Up @@ -3189,22 +3190,37 @@ def with_pymalloc():
return _testcapi.WITH_PYMALLOC


class FakePath:
"""Simple implementing of the path protocol.
"""
def __init__(self, path):
self.path = path
class Fake:
def __init__(self, value):
self.value = value

def __repr__(self):
return f'<FakePath {self.path!r}>'
return f'<{self.__class__.__name__} {self.value!r}>'

def __fspath__(self):
if (isinstance(self.path, BaseException) or
isinstance(self.path, type) and
issubclass(self.path, BaseException)):
raise self.path
def _return(self):
if (isinstance(self.value, BaseException) or
isinstance(self.value, type) and
issubclass(self.value, BaseException)):
raise self.value
else:
return self.path
return self.value

class FakeIndex(Fake):
__index__ = Fake._return

class FakeInt(Fake):
__int__ = Fake._return

class FakeFloat(Fake):
__float__ = Fake._return

class FakeComplex(Fake):
__complex__ = Fake._return

class FakePath(Fake):
"""Simple implementing of the path protocol.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"""Simple implementing of the path protocol.
"""Simple implementation of the path protocol.

"""
Copy link
Member

Choose a reason for hiding this comment

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

Should this be on the previous line? Looks weird to have it sitting on its own following a short one-line docstring like this.

__fspath__ = Fake._return


class _ALWAYS_EQ:
Expand Down
16 changes: 2 additions & 14 deletions Lib/test/test_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import contextlib
import unittest
from test import support
from test.support import FakeIndex, FakeInt
from itertools import permutations, product
from random import randrange, sample, choice
import warnings
Expand Down Expand Up @@ -2510,22 +2511,9 @@ def test_memoryview_sizeof(self):
check(memoryview(a), vsize(base_struct + 3 * per_dim))

def test_memoryview_struct_module(self):

class INT(object):
def __init__(self, val):
self.val = val
def __int__(self):
return self.val

class IDX(object):
def __init__(self, val):
self.val = val
def __index__(self):
return self.val

def f(): return 7

values = [INT(9), IDX(9),
values = [FakeInt(9), FakeIndex(9),
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't quite understand the name, there's nothing really fake about these classes. Also, it's nice to see what the classes do without leaving the file.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with both points here.

When scanning through the PR on GitHub and seeing FakeInt(10.9), my first thought is that I don't know what that means without guessing, so I want to quickly find the FakeInt definition. That's harder when that definition is in another file. (And yes, if you have a decent editor open and set up properly, the definition should be just a click or keystroke away, but not everyone has that setup available all of the time.)

The problem could potentially be mitigated with a name that more clearly describes what the class is doing (HasDunderIntReturning(3.2)?), but as you can tell I'm having trouble coming up with a name that's going to be immediately unambiguous for people reading the code without also being unwieldy.

2.2+3j, Decimal("-21.1"), 12.2, Fraction(5, 2),
[1,2,3], {4,5,6}, {7:8}, (), (9,),
True, False, None, Ellipsis,
Expand Down
36 changes: 13 additions & 23 deletions Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import test.support
import test.string_tests
import test.list_tests
from test.support import bigaddrspacetest, MAX_Py_ssize_t
from test.support import bigaddrspacetest, MAX_Py_ssize_t, FakeIndex
from test.support.script_helper import assert_python_failure


Expand All @@ -35,13 +35,6 @@ def check_bytes_warnings(func):
return func


class Indexable:
def __init__(self, value=0):
self.value = value
def __index__(self):
return self.value


class BaseBytesTest:

def test_basics(self):
Expand Down Expand Up @@ -133,11 +126,11 @@ def __index__(self):
self.assertEqual(bytes(a), b'*' * 1000) # should not crash

def test_from_index(self):
b = self.type2test([Indexable(), Indexable(1), Indexable(254),
Indexable(255)])
b = self.type2test([FakeIndex(0), FakeIndex(1), FakeIndex(254),
FakeIndex(255)])
self.assertEqual(list(b), [0, 1, 254, 255])
self.assertRaises(ValueError, self.type2test, [Indexable(-1)])
self.assertRaises(ValueError, self.type2test, [Indexable(256)])
self.assertRaises(ValueError, self.type2test, [FakeIndex(-1)])
self.assertRaises(ValueError, self.type2test, [FakeIndex(256)])

def test_from_buffer(self):
a = self.type2test(array.array('B', [1, 2, 3]))
Expand Down Expand Up @@ -208,11 +201,8 @@ def test_constructor_overflow(self):
def test_constructor_exceptions(self):
# Issue #34974: bytes and bytearray constructors replace unexpected
# exceptions.
class BadInt:
def __index__(self):
1/0
self.assertRaises(ZeroDivisionError, self.type2test, BadInt())
self.assertRaises(ZeroDivisionError, self.type2test, [BadInt()])
self.assertRaises(ZeroDivisionError, self.type2test, FakeIndex(ZeroDivisionError))
self.assertRaises(ZeroDivisionError, self.type2test, [FakeIndex(ZeroDivisionError)])

class BadIterable:
def __iter__(self):
Expand Down Expand Up @@ -1267,7 +1257,7 @@ def test_setitem(self):
self.assertEqual(b, bytearray([1, 100, 3]))
b[-1] = 200
self.assertEqual(b, bytearray([1, 100, 200]))
b[0] = Indexable(10)
b[0] = FakeIndex(10)
self.assertEqual(b, bytearray([10, 100, 200]))
try:
b[3] = 0
Expand All @@ -1285,7 +1275,7 @@ def test_setitem(self):
except ValueError:
pass
try:
b[0] = Indexable(-1)
b[0] = FakeIndex(-1)
self.fail("Didn't raise ValueError")
except ValueError:
pass
Expand Down Expand Up @@ -1483,7 +1473,7 @@ def test_extend(self):
self.assertRaises(ValueError, a.extend, [0, 1, 2, -1])
self.assertEqual(len(a), 0)
a = bytearray(b'')
a.extend([Indexable(ord('a'))])
a.extend([FakeIndex(ord('a'))])
self.assertEqual(a, b'a')

def test_remove(self):
Expand All @@ -1500,7 +1490,7 @@ def test_remove(self):
b.remove(ord('h'))
self.assertEqual(b, b'e')
self.assertRaises(TypeError, lambda: b.remove(b'e'))
b.remove(Indexable(ord('e')))
b.remove(FakeIndex(ord('e')))
self.assertEqual(b, b'')

# test values outside of the ascii range: (0, 127)
Expand Down Expand Up @@ -1533,7 +1523,7 @@ def test_append(self):
self.assertEqual(len(b), 1)
self.assertRaises(TypeError, lambda: b.append(b'o'))
b = bytearray()
b.append(Indexable(ord('A')))
b.append(FakeIndex(ord('A')))
self.assertEqual(b, b'A')

def test_insert(self):
Expand All @@ -1545,7 +1535,7 @@ def test_insert(self):
self.assertEqual(b, b'mississippi')
self.assertRaises(TypeError, lambda: b.insert(0, b'1'))
b = bytearray()
b.insert(0, Indexable(ord('A')))
b.insert(0, FakeIndex(ord('A')))
self.assertEqual(b, b'A')

def test_copied(self):
Expand Down
70 changes: 10 additions & 60 deletions Lib/test/test_cmath.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from test.support import requires_IEEE_754, cpython_only
from test.support import FakeIndex, FakeInt, FakeFloat, FakeComplex
from test.test_math import parse_testfile, test_file
import test.test_math as test_math
import unittest
Expand Down Expand Up @@ -191,85 +192,34 @@ def test_user_object(self):
# Now we introduce a variety of classes whose instances might
# end up being passed to the cmath functions

# usual case: new-style class implementing __complex__
class MyComplex(object):
def __init__(self, value):
self.value = value
def __complex__(self):
return self.value

# old-style class implementing __complex__
class MyComplexOS:
def __init__(self, value):
self.value = value
def __complex__(self):
return self.value

# classes for which __complex__ raises an exception
class SomeException(Exception):
pass
class MyComplexException(object):
def __complex__(self):
raise SomeException
class MyComplexExceptionOS:
def __complex__(self):
raise SomeException

# some classes not providing __float__ or __complex__
class NeitherComplexNorFloat(object):
pass
class NeitherComplexNorFloatOS:
pass
class Index:
def __int__(self): return 2
def __index__(self): return 2
class MyInt:
def __int__(self): return 2

# other possible combinations of __float__ and __complex__
# that should work
class FloatAndComplex(object):
class FloatAndComplex:
def __float__(self):
return flt_arg
def __complex__(self):
return cx_arg
class FloatAndComplexOS:
def __float__(self):
return flt_arg
def __complex__(self):
return cx_arg
class JustFloat(object):
def __float__(self):
return flt_arg
class JustFloatOS:
def __float__(self):
return flt_arg

for f in self.test_functions:
# usual usage
self.assertEqual(f(MyComplex(cx_arg)), f(cx_arg))
self.assertEqual(f(MyComplexOS(cx_arg)), f(cx_arg))
self.assertEqual(f(FakeComplex(cx_arg)), f(cx_arg))
# other combinations of __float__ and __complex__
self.assertEqual(f(FloatAndComplex()), f(cx_arg))
self.assertEqual(f(FloatAndComplexOS()), f(cx_arg))
self.assertEqual(f(JustFloat()), f(flt_arg))
self.assertEqual(f(JustFloatOS()), f(flt_arg))
self.assertEqual(f(Index()), f(int(Index())))
self.assertEqual(f(FakeFloat(flt_arg)), f(flt_arg))
self.assertEqual(f(FakeIndex(2)), f(2))
# TypeError should be raised for classes not providing
# either __complex__ or __float__, even if they provide
# __int__ or __index__. An old-style class
# currently raises AttributeError instead of a TypeError;
# this could be considered a bug.
self.assertRaises(TypeError, f, NeitherComplexNorFloat())
self.assertRaises(TypeError, f, MyInt())
self.assertRaises(Exception, f, NeitherComplexNorFloatOS())
# __int__.
self.assertRaises(TypeError, f, object())
self.assertRaises(TypeError, f, FakeInt(2))
# non-complex return value from __complex__ -> TypeError
for bad_complex in non_complexes:
self.assertRaises(TypeError, f, MyComplex(bad_complex))
self.assertRaises(TypeError, f, MyComplexOS(bad_complex))
self.assertRaises(TypeError, f, FakeComplex(bad_complex))
# exceptions in __complex__ should be propagated correctly
self.assertRaises(SomeException, f, MyComplexException())
self.assertRaises(SomeException, f, MyComplexExceptionOS())
self.assertRaises(SomeException, f, FakeComplex(SomeException))

def test_input_type(self):
# ints should be acceptable inputs to all cmath
Expand Down
Loading