bpo-33144: random.Random and subclasses: split _randbelow implementation (GH-6291)

This commit is contained in:
Wolfgang Maier 2018-04-17 17:16:17 +02:00 committed by Raymond Hettinger
parent 28e8b66d6c
commit ba3a87aca3
3 changed files with 107 additions and 29 deletions

View file

@ -5,6 +5,7 @@ import os
import time
import pickle
import warnings
import logging
from functools import partial
from math import log, exp, pi, fsum, sin, factorial
from test import support
@ -619,6 +620,16 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
self.assertRaises(ValueError, self.gen.getrandbits, 0)
self.assertRaises(ValueError, self.gen.getrandbits, -1)
def test_randrange_uses_getrandbits(self):
# Verify use of getrandbits by randrange
# Use same seed as in the cross-platform repeatability test
# in test_genrandbits above.
self.gen.seed(1234567)
# If randrange uses getrandbits, it should pick getrandbits(100)
# when called with a 100-bits stop argument.
self.assertEqual(self.gen.randrange(2**99),
97904845777343510404718956115)
def test_randbelow_logic(self, _log=log, int=int):
# check bitcount transition points: 2**i and 2**(i+1)-1
# show that: k = int(1.001 + _log(n, 2))
@ -640,21 +651,22 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
self.assertEqual(k, numbits) # note the stronger assertion
self.assertTrue(2**k > n > 2**(k-1)) # note the stronger assertion
@unittest.mock.patch('random.Random.random')
def test_randbelow_overridden_random(self, random_mock):
def test_randbelow_without_getrandbits(self):
# Random._randbelow() can only use random() when the built-in one
# has been overridden but no new getrandbits() method was supplied.
random_mock.side_effect = random.SystemRandom().random
maxsize = 1<<random.BPF
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
# Population range too large (n >= maxsize)
self.gen._randbelow(maxsize+1, maxsize = maxsize)
self.gen._randbelow(5640, maxsize = maxsize)
self.gen._randbelow_without_getrandbits(
maxsize+1, maxsize=maxsize
)
self.gen._randbelow_without_getrandbits(5640, maxsize=maxsize)
# issue 33203: test that _randbelow raises ValueError on
# n == 0 also in its getrandbits-independent branch.
with self.assertRaises(ValueError):
self.gen._randbelow(0, maxsize=maxsize)
self.gen._randbelow_without_getrandbits(0, maxsize=maxsize)
# This might be going too far to test a single line, but because of our
# noble aim of achieving 100% test coverage we need to write a case in
# which the following line in Random._randbelow() gets executed:
@ -672,8 +684,10 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
n = 42
epsilon = 0.01
limit = (maxsize - (maxsize % n)) / maxsize
random_mock.side_effect = [limit + epsilon, limit - epsilon]
self.gen._randbelow(n, maxsize = maxsize)
with unittest.mock.patch.object(random.Random, 'random') as random_mock:
random_mock.side_effect = [limit + epsilon, limit - epsilon]
self.gen._randbelow_without_getrandbits(n, maxsize=maxsize)
self.assertEqual(random_mock.call_count, 2)
def test_randrange_bug_1590891(self):
start = 1000000000000
@ -926,6 +940,49 @@ class TestDistributions(unittest.TestCase):
gammavariate_mock.return_value = 0.0
self.assertEqual(0.0, random.betavariate(2.71828, 3.14159))
class TestRandomSubclassing(unittest.TestCase):
def test_random_subclass_with_kwargs(self):
# SF bug #1486663 -- this used to erroneously raise a TypeError
class Subclass(random.Random):
def __init__(self, newarg=None):
random.Random.__init__(self)
Subclass(newarg=1)
def test_subclasses_overriding_methods(self):
# Subclasses with an overridden random, but only the original
# getrandbits method should not rely on getrandbits in for randrange,
# but should use a getrandbits-independent implementation instead.
# subclass providing its own random **and** getrandbits methods
# like random.SystemRandom does => keep relying on getrandbits for
# randrange
class SubClass1(random.Random):
def random(self):
return super().random()
def getrandbits(self, n):
logging.getLogger('getrandbits').info('used getrandbits')
return super().getrandbits(n)
with self.assertLogs('getrandbits'):
SubClass1().randrange(42)
# subclass providing only random => can only use random for randrange
class SubClass2(random.Random):
def random(self):
logging.getLogger('random').info('used random')
return super().random()
with self.assertLogs('random'):
SubClass2().randrange(42)
# subclass defining getrandbits to complement its inherited random
# => can now rely on getrandbits for randrange again
class SubClass3(SubClass2):
def getrandbits(self, n):
logging.getLogger('getrandbits').info('used getrandbits')
return super().getrandbits(n)
with self.assertLogs('getrandbits'):
SubClass3().randrange(42)
class TestModule(unittest.TestCase):
def testMagicConstants(self):
self.assertAlmostEqual(random.NV_MAGICCONST, 1.71552776992141)
@ -937,13 +994,6 @@ class TestModule(unittest.TestCase):
# tests validity but not completeness of the __all__ list
self.assertTrue(set(random.__all__) <= set(dir(random)))
def test_random_subclass_with_kwargs(self):
# SF bug #1486663 -- this used to erroneously raise a TypeError
class Subclass(random.Random):
def __init__(self, newarg=None):
random.Random.__init__(self)
Subclass(newarg=1)
@unittest.skipUnless(hasattr(os, "fork"), "fork() required")
def test_after_fork(self):
# Test the global Random instance gets reseeded in child