mirror of https://github.com/python/cpython
parent
d3f41fe121
commit
e3e1c17e08
|
@ -1,10 +1,12 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import random
|
||||
import time
|
||||
import pickle
|
||||
import warnings
|
||||
from functools import partial
|
||||
from math import log, exp, pi, fsum, sin
|
||||
from test import support
|
||||
|
||||
|
@ -46,6 +48,16 @@ class TestBasicOps(unittest.TestCase):
|
|||
self.assertRaises(TypeError, self.gen.seed, 1, 2, 3, 4)
|
||||
self.assertRaises(TypeError, type(self.gen), [])
|
||||
|
||||
@unittest.mock.patch('random._urandom') # os.urandom
|
||||
def test_seed_when_randomness_source_not_found(self, urandom_mock):
|
||||
# Random.seed() uses time.time() when an operating system specific
|
||||
# randomness source is not found. To test this on machines were it
|
||||
# exists, run the above test, test_seedargs(), again after mocking
|
||||
# os.urandom() so that it raises the exception expected when the
|
||||
# randomness source is not available.
|
||||
urandom_mock.side_effect = NotImplementedError
|
||||
self.test_seedargs()
|
||||
|
||||
def test_shuffle(self):
|
||||
shuffle = self.gen.shuffle
|
||||
lst = []
|
||||
|
@ -98,6 +110,8 @@ class TestBasicOps(unittest.TestCase):
|
|||
self.assertEqual(len(uniq), k)
|
||||
self.assertTrue(uniq <= set(population))
|
||||
self.assertEqual(self.gen.sample([], 0), []) # test edge case N==k==0
|
||||
# Exception raised if size of sample exceeds that of population
|
||||
self.assertRaises(ValueError, self.gen.sample, population, N+1)
|
||||
|
||||
def test_sample_distribution(self):
|
||||
# For the entire allowable range of 0 <= k <= N, validate that
|
||||
|
@ -230,6 +244,25 @@ class SystemRandom_TestBasicOps(TestBasicOps):
|
|||
self.assertEqual(set(range(start,stop)),
|
||||
set([self.gen.randrange(start,stop) for i in range(100)]))
|
||||
|
||||
def test_randrange_nonunit_step(self):
|
||||
rint = self.gen.randrange(0, 10, 2)
|
||||
self.assertIn(rint, (0, 2, 4, 6, 8))
|
||||
rint = self.gen.randrange(0, 2, 2)
|
||||
self.assertEqual(rint, 0)
|
||||
|
||||
def test_randrange_errors(self):
|
||||
raises = partial(self.assertRaises, ValueError, self.gen.randrange)
|
||||
# Empty range
|
||||
raises(3, 3)
|
||||
raises(-721)
|
||||
raises(0, 100, -12)
|
||||
# Non-integer start/stop
|
||||
raises(3.14159)
|
||||
raises(0, 2.71828)
|
||||
# Zero and non-integer step
|
||||
raises(0, 42, 0)
|
||||
raises(0, 42, 3.14159)
|
||||
|
||||
def test_genrandbits(self):
|
||||
# Verify ranges
|
||||
for k in range(1, 1000):
|
||||
|
@ -299,6 +332,16 @@ class MersenneTwister_TestBasicOps(TestBasicOps):
|
|||
# Last element s/b an int also
|
||||
self.assertRaises(TypeError, self.gen.setstate, (2, (0,)*624+('a',), None))
|
||||
|
||||
# Little trick to make "tuple(x % (2**32) for x in internalstate)"
|
||||
# raise ValueError. I cannot think of a simple way to achieve this, so
|
||||
# I am opting for using a generator as the middle argument of setstate
|
||||
# which attempts to cast a NaN to integer.
|
||||
state_values = self.gen.getstate()[1]
|
||||
state_values = list(state_values)
|
||||
state_values[-1] = float('nan')
|
||||
state = (int(x) for x in state_values)
|
||||
self.assertRaises(TypeError, self.gen.setstate, (2, state, None))
|
||||
|
||||
def test_referenceImplementation(self):
|
||||
# Compare the python implementation with results from the original
|
||||
# code. Create 2000 53-bit precision random floats. Compare only
|
||||
|
@ -438,6 +481,38 @@ class MersenneTwister_TestBasicOps(TestBasicOps):
|
|||
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_overriden_random(self, random_mock):
|
||||
# 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)
|
||||
|
||||
# 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:
|
||||
#
|
||||
# rem = maxsize % n
|
||||
# limit = (maxsize - rem) / maxsize
|
||||
# r = random()
|
||||
# while r >= limit:
|
||||
# r = random() # <== *This line* <==<
|
||||
#
|
||||
# Therefore, to guarantee that the while loop is executed at least
|
||||
# once, we need to mock random() so that it returns a number greater
|
||||
# than 'limit' the first time it gets called.
|
||||
|
||||
n = 42
|
||||
epsilon = 0.01
|
||||
limit = (maxsize - (maxsize % n)) / maxsize
|
||||
random_mock.side_effect = [limit + epsilon, limit - epsilon]
|
||||
self.gen._randbelow(n, maxsize = maxsize)
|
||||
|
||||
def test_randrange_bug_1590891(self):
|
||||
start = 1000000000000
|
||||
stop = -100000000000000000000
|
||||
|
@ -555,6 +630,106 @@ class TestDistributions(unittest.TestCase):
|
|||
random.vonmisesvariate(0, 1e15)
|
||||
random.vonmisesvariate(0, 1e100)
|
||||
|
||||
def test_gammavariate_errors(self):
|
||||
# Both alpha and beta must be > 0.0
|
||||
self.assertRaises(ValueError, random.gammavariate, -1, 3)
|
||||
self.assertRaises(ValueError, random.gammavariate, 0, 2)
|
||||
self.assertRaises(ValueError, random.gammavariate, 2, 0)
|
||||
self.assertRaises(ValueError, random.gammavariate, 1, -3)
|
||||
|
||||
@unittest.mock.patch('random.Random.random')
|
||||
def test_gammavariate_full_code_coverage(self, random_mock):
|
||||
# There are three different possibilities in the current implementation
|
||||
# of random.gammavariate(), depending on the value of 'alpha'. What we
|
||||
# are going to do here is to fix the values returned by random() to
|
||||
# generate test cases that provide 100% line coverage of the method.
|
||||
|
||||
# #1: alpha > 1.0: we want the first random number to be outside the
|
||||
# [1e-7, .9999999] range, so that the continue statement executes
|
||||
# once. The values of u1 and u2 will be 0.5 and 0.3, respectively.
|
||||
random_mock.side_effect = [1e-8, 0.5, 0.3]
|
||||
returned_value = random.gammavariate(1.1, 2.3)
|
||||
self.assertAlmostEqual(returned_value, 2.53)
|
||||
|
||||
# #2: alpha == 1: first random number less than 1e-7 to that the body
|
||||
# of the while loop executes once. Then random.random() returns 0.45,
|
||||
# which causes while to stop looping and the algorithm to terminate.
|
||||
random_mock.side_effect = [1e-8, 0.45]
|
||||
returned_value = random.gammavariate(1.0, 3.14)
|
||||
self.assertAlmostEqual(returned_value, 2.507314166123803)
|
||||
|
||||
# #3: 0 < alpha < 1. This is the most complex region of code to cover,
|
||||
# as there are multiple if-else statements. Let's take a look at the
|
||||
# source code, and determine the values that we need accordingly:
|
||||
#
|
||||
# while 1:
|
||||
# u = random()
|
||||
# b = (_e + alpha)/_e
|
||||
# p = b*u
|
||||
# if p <= 1.0: # <=== (A)
|
||||
# x = p ** (1.0/alpha)
|
||||
# else: # <=== (B)
|
||||
# x = -_log((b-p)/alpha)
|
||||
# u1 = random()
|
||||
# if p > 1.0: # <=== (C)
|
||||
# if u1 <= x ** (alpha - 1.0): # <=== (D)
|
||||
# break
|
||||
# elif u1 <= _exp(-x): # <=== (E)
|
||||
# break
|
||||
# return x * beta
|
||||
#
|
||||
# First, we want (A) to be True. For that we need that:
|
||||
# b*random() <= 1.0
|
||||
# r1 = random() <= 1.0 / b
|
||||
#
|
||||
# We now get to the second if-else branch, and here, since p <= 1.0,
|
||||
# (C) is False and we take the elif branch, (E). For it to be True,
|
||||
# so that the break is executed, we need that:
|
||||
# r2 = random() <= _exp(-x)
|
||||
# r2 <= _exp(-(p ** (1.0/alpha)))
|
||||
# r2 <= _exp(-((b*r1) ** (1.0/alpha)))
|
||||
|
||||
_e = random._e
|
||||
_exp = random._exp
|
||||
_log = random._log
|
||||
alpha = 0.35
|
||||
beta = 1.45
|
||||
b = (_e + alpha)/_e
|
||||
epsilon = 0.01
|
||||
|
||||
r1 = 0.8859296441566 # 1.0 / b
|
||||
r2 = 0.3678794411714 # _exp(-((b*r1) ** (1.0/alpha)))
|
||||
|
||||
# These four "random" values result in the following trace:
|
||||
# (A) True, (E) False --> [next iteration of while]
|
||||
# (A) True, (E) True --> [while loop breaks]
|
||||
random_mock.side_effect = [r1, r2 + epsilon, r1, r2]
|
||||
returned_value = random.gammavariate(alpha, beta)
|
||||
self.assertAlmostEqual(returned_value, 1.4499999999997544)
|
||||
|
||||
# Let's now make (A) be False. If this is the case, when we get to the
|
||||
# second if-else 'p' is greater than 1, so (C) evaluates to True. We
|
||||
# now encounter a second if statement, (D), which in order to execute
|
||||
# must satisfy the following condition:
|
||||
# r2 <= x ** (alpha - 1.0)
|
||||
# r2 <= (-_log((b-p)/alpha)) ** (alpha - 1.0)
|
||||
# r2 <= (-_log((b-(b*r1))/alpha)) ** (alpha - 1.0)
|
||||
r1 = 0.8959296441566 # (1.0 / b) + epsilon -- so that (A) is False
|
||||
r2 = 0.9445400408898141
|
||||
|
||||
# And these four values result in the following trace:
|
||||
# (B) and (C) True, (D) False --> [next iteration of while]
|
||||
# (B) and (C) True, (D) True [while loop breaks]
|
||||
random_mock.side_effect = [r1, r2 + epsilon, r1, r2]
|
||||
returned_value = random.gammavariate(alpha, beta)
|
||||
self.assertAlmostEqual(returned_value, 1.5830349561760781)
|
||||
|
||||
@unittest.mock.patch('random.Random.gammavariate')
|
||||
def test_betavariate_return_zero(self, gammavariate_mock):
|
||||
# betavariate() returns zero when the Gamma distribution
|
||||
# that it uses internally returns this same value.
|
||||
gammavariate_mock.return_value = 0.0
|
||||
self.assertEqual(0.0, random.betavariate(2.71828, 3.14159))
|
||||
|
||||
class TestModule(unittest.TestCase):
|
||||
def testMagicConstants(self):
|
||||
|
|
Loading…
Reference in New Issue