bpo-33144: random.Random and subclasses: split _randbelow implementat… · python/cpython@ba3a87a

@@ -5,6 +5,7 @@

55

import time

66

import pickle

77

import warnings

8+

import logging

89

from functools import partial

910

from math import log, exp, pi, fsum, sin, factorial

1011

from test import support

@@ -619,6 +620,16 @@ def test_genrandbits(self):

619620

self.assertRaises(ValueError, self.gen.getrandbits, 0)

620621

self.assertRaises(ValueError, self.gen.getrandbits, -1)

621622623+

def test_randrange_uses_getrandbits(self):

624+

# Verify use of getrandbits by randrange

625+

# Use same seed as in the cross-platform repeatability test

626+

# in test_genrandbits above.

627+

self.gen.seed(1234567)

628+

# If randrange uses getrandbits, it should pick getrandbits(100)

629+

# when called with a 100-bits stop argument.

630+

self.assertEqual(self.gen.randrange(2**99),

631+

97904845777343510404718956115)

632+622633

def test_randbelow_logic(self, _log=log, int=int):

623634

# check bitcount transition points: 2**i and 2**(i+1)-1

624635

# show that: k = int(1.001 + _log(n, 2))

@@ -640,21 +651,22 @@ def test_randbelow_logic(self, _log=log, int=int):

640651

self.assertEqual(k, numbits) # note the stronger assertion

641652

self.assertTrue(2**k > n > 2**(k-1)) # note the stronger assertion

642653643-

@unittest.mock.patch('random.Random.random')

644-

def test_randbelow_overridden_random(self, random_mock):

654+

def test_randbelow_without_getrandbits(self):

645655

# Random._randbelow() can only use random() when the built-in one

646656

# has been overridden but no new getrandbits() method was supplied.

647-

random_mock.side_effect = random.SystemRandom().random

648657

maxsize = 1<<random.BPF

649658

with warnings.catch_warnings():

650659

warnings.simplefilter("ignore", UserWarning)

651660

# Population range too large (n >= maxsize)

652-

self.gen._randbelow(maxsize+1, maxsize = maxsize)

653-

self.gen._randbelow(5640, maxsize = maxsize)

661+

self.gen._randbelow_without_getrandbits(

662+

maxsize+1, maxsize=maxsize

663+

)

664+

self.gen._randbelow_without_getrandbits(5640, maxsize=maxsize)

654665

# issue 33203: test that _randbelow raises ValueError on

655666

# n == 0 also in its getrandbits-independent branch.

656667

with self.assertRaises(ValueError):

657-

self.gen._randbelow(0, maxsize=maxsize)

668+

self.gen._randbelow_without_getrandbits(0, maxsize=maxsize)

669+658670

# This might be going too far to test a single line, but because of our

659671

# noble aim of achieving 100% test coverage we need to write a case in

660672

# which the following line in Random._randbelow() gets executed:

@@ -672,8 +684,10 @@ def test_randbelow_overridden_random(self, random_mock):

672684

n = 42

673685

epsilon = 0.01

674686

limit = (maxsize - (maxsize % n)) / maxsize

675-

random_mock.side_effect = [limit + epsilon, limit - epsilon]

676-

self.gen._randbelow(n, maxsize = maxsize)

687+

with unittest.mock.patch.object(random.Random, 'random') as random_mock:

688+

random_mock.side_effect = [limit + epsilon, limit - epsilon]

689+

self.gen._randbelow_without_getrandbits(n, maxsize=maxsize)

690+

self.assertEqual(random_mock.call_count, 2)

677691678692

def test_randrange_bug_1590891(self):

679693

start = 1000000000000

@@ -926,6 +940,49 @@ def test_betavariate_return_zero(self, gammavariate_mock):

926940

gammavariate_mock.return_value = 0.0

927941

self.assertEqual(0.0, random.betavariate(2.71828, 3.14159))

928942943+

class TestRandomSubclassing(unittest.TestCase):

944+

def test_random_subclass_with_kwargs(self):

945+

# SF bug #1486663 -- this used to erroneously raise a TypeError

946+

class Subclass(random.Random):

947+

def __init__(self, newarg=None):

948+

random.Random.__init__(self)

949+

Subclass(newarg=1)

950+951+

def test_subclasses_overriding_methods(self):

952+

# Subclasses with an overridden random, but only the original

953+

# getrandbits method should not rely on getrandbits in for randrange,

954+

# but should use a getrandbits-independent implementation instead.

955+956+

# subclass providing its own random **and** getrandbits methods

957+

# like random.SystemRandom does => keep relying on getrandbits for

958+

# randrange

959+

class SubClass1(random.Random):

960+

def random(self):

961+

return super().random()

962+963+

def getrandbits(self, n):

964+

logging.getLogger('getrandbits').info('used getrandbits')

965+

return super().getrandbits(n)

966+

with self.assertLogs('getrandbits'):

967+

SubClass1().randrange(42)

968+969+

# subclass providing only random => can only use random for randrange

970+

class SubClass2(random.Random):

971+

def random(self):

972+

logging.getLogger('random').info('used random')

973+

return super().random()

974+

with self.assertLogs('random'):

975+

SubClass2().randrange(42)

976+977+

# subclass defining getrandbits to complement its inherited random

978+

# => can now rely on getrandbits for randrange again

979+

class SubClass3(SubClass2):

980+

def getrandbits(self, n):

981+

logging.getLogger('getrandbits').info('used getrandbits')

982+

return super().getrandbits(n)

983+

with self.assertLogs('getrandbits'):

984+

SubClass3().randrange(42)

985+929986

class TestModule(unittest.TestCase):

930987

def testMagicConstants(self):

931988

self.assertAlmostEqual(random.NV_MAGICCONST, 1.71552776992141)

@@ -937,13 +994,6 @@ def test__all__(self):

937994

# tests validity but not completeness of the __all__ list

938995

self.assertTrue(set(random.__all__) <= set(dir(random)))

939996940-

def test_random_subclass_with_kwargs(self):

941-

# SF bug #1486663 -- this used to erroneously raise a TypeError

942-

class Subclass(random.Random):

943-

def __init__(self, newarg=None):

944-

random.Random.__init__(self)

945-

Subclass(newarg=1)

946-947997

@unittest.skipUnless(hasattr(os, "fork"), "fork() required")

948998

def test_after_fork(self):

949999

# Test the global Random instance gets reseeded in child