Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e3e1c17

Browse files
committed
#17492: Additional tests for random module.
Patch by Victor Terrón.
1 parent d3f41fe commit e3e1c17

2 files changed

Lines changed: 176 additions & 0 deletions

File tree

Lib/test/test_random.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#!/usr/bin/env python3
22

33
import unittest
4+
import unittest.mock
45
import random
56
import time
67
import pickle
78
import warnings
9+
from functools import partial
810
from math import log, exp, pi, fsum, sin
911
from test import support
1012

@@ -46,6 +48,16 @@ def __hash__(self):
4648
self.assertRaises(TypeError, self.gen.seed, 1, 2, 3, 4)
4749
self.assertRaises(TypeError, type(self.gen), [])
4850

51+
@unittest.mock.patch('random._urandom') # os.urandom
52+
def test_seed_when_randomness_source_not_found(self, urandom_mock):
53+
# Random.seed() uses time.time() when an operating system specific
54+
# randomness source is not found. To test this on machines were it
55+
# exists, run the above test, test_seedargs(), again after mocking
56+
# os.urandom() so that it raises the exception expected when the
57+
# randomness source is not available.
58+
urandom_mock.side_effect = NotImplementedError
59+
self.test_seedargs()
60+
4961
def test_shuffle(self):
5062
shuffle = self.gen.shuffle
5163
lst = []
@@ -98,6 +110,8 @@ def test_sample(self):
98110
self.assertEqual(len(uniq), k)
99111
self.assertTrue(uniq <= set(population))
100112
self.assertEqual(self.gen.sample([], 0), []) # test edge case N==k==0
113+
# Exception raised if size of sample exceeds that of population
114+
self.assertRaises(ValueError, self.gen.sample, population, N+1)
101115

102116
def test_sample_distribution(self):
103117
# For the entire allowable range of 0 <= k <= N, validate that
@@ -230,6 +244,25 @@ def test_rangelimits(self):
230244
self.assertEqual(set(range(start,stop)),
231245
set([self.gen.randrange(start,stop) for i in range(100)]))
232246

247+
def test_randrange_nonunit_step(self):
248+
rint = self.gen.randrange(0, 10, 2)
249+
self.assertIn(rint, (0, 2, 4, 6, 8))
250+
rint = self.gen.randrange(0, 2, 2)
251+
self.assertEqual(rint, 0)
252+
253+
def test_randrange_errors(self):
254+
raises = partial(self.assertRaises, ValueError, self.gen.randrange)
255+
# Empty range
256+
raises(3, 3)
257+
raises(-721)
258+
raises(0, 100, -12)
259+
# Non-integer start/stop
260+
raises(3.14159)
261+
raises(0, 2.71828)
262+
# Zero and non-integer step
263+
raises(0, 42, 0)
264+
raises(0, 42, 3.14159)
265+
233266
def test_genrandbits(self):
234267
# Verify ranges
235268
for k in range(1, 1000):
@@ -299,6 +332,16 @@ def test_setstate_middle_arg(self):
299332
# Last element s/b an int also
300333
self.assertRaises(TypeError, self.gen.setstate, (2, (0,)*624+('a',), None))
301334

335+
# Little trick to make "tuple(x % (2**32) for x in internalstate)"
336+
# raise ValueError. I cannot think of a simple way to achieve this, so
337+
# I am opting for using a generator as the middle argument of setstate
338+
# which attempts to cast a NaN to integer.
339+
state_values = self.gen.getstate()[1]
340+
state_values = list(state_values)
341+
state_values[-1] = float('nan')
342+
state = (int(x) for x in state_values)
343+
self.assertRaises(TypeError, self.gen.setstate, (2, state, None))
344+
302345
def test_referenceImplementation(self):
303346
# Compare the python implementation with results from the original
304347
# code. Create 2000 53-bit precision random floats. Compare only
@@ -438,6 +481,38 @@ def test_randbelow_logic(self, _log=log, int=int):
438481
self.assertEqual(k, numbits) # note the stronger assertion
439482
self.assertTrue(2**k > n > 2**(k-1)) # note the stronger assertion
440483

484+
@unittest.mock.patch('random.Random.random')
485+
def test_randbelow_overriden_random(self, random_mock):
486+
# Random._randbelow() can only use random() when the built-in one
487+
# has been overridden but no new getrandbits() method was supplied.
488+
random_mock.side_effect = random.SystemRandom().random
489+
maxsize = 1<<random.BPF
490+
with warnings.catch_warnings():
491+
warnings.simplefilter("ignore", UserWarning)
492+
# Population range too large (n >= maxsize)
493+
self.gen._randbelow(maxsize+1, maxsize = maxsize)
494+
self.gen._randbelow(5640, maxsize = maxsize)
495+
496+
# This might be going too far to test a single line, but because of our
497+
# noble aim of achieving 100% test coverage we need to write a case in
498+
# which the following line in Random._randbelow() gets executed:
499+
#
500+
# rem = maxsize % n
501+
# limit = (maxsize - rem) / maxsize
502+
# r = random()
503+
# while r >= limit:
504+
# r = random() # <== *This line* <==<
505+
#
506+
# Therefore, to guarantee that the while loop is executed at least
507+
# once, we need to mock random() so that it returns a number greater
508+
# than 'limit' the first time it gets called.
509+
510+
n = 42
511+
epsilon = 0.01
512+
limit = (maxsize - (maxsize % n)) / maxsize
513+
random_mock.side_effect = [limit + epsilon, limit - epsilon]
514+
self.gen._randbelow(n, maxsize = maxsize)
515+
441516
def test_randrange_bug_1590891(self):
442517
start = 1000000000000
443518
stop = -100000000000000000000
@@ -555,6 +630,106 @@ def test_von_mises_large_kappa(self):
555630
random.vonmisesvariate(0, 1e15)
556631
random.vonmisesvariate(0, 1e100)
557632

633+
def test_gammavariate_errors(self):
634+
# Both alpha and beta must be > 0.0
635+
self.assertRaises(ValueError, random.gammavariate, -1, 3)
636+
self.assertRaises(ValueError, random.gammavariate, 0, 2)
637+
self.assertRaises(ValueError, random.gammavariate, 2, 0)
638+
self.assertRaises(ValueError, random.gammavariate, 1, -3)
639+
640+
@unittest.mock.patch('random.Random.random')
641+
def test_gammavariate_full_code_coverage(self, random_mock):
642+
# There are three different possibilities in the current implementation
643+
# of random.gammavariate(), depending on the value of 'alpha'. What we
644+
# are going to do here is to fix the values returned by random() to
645+
# generate test cases that provide 100% line coverage of the method.
646+
647+
# #1: alpha > 1.0: we want the first random number to be outside the
648+
# [1e-7, .9999999] range, so that the continue statement executes
649+
# once. The values of u1 and u2 will be 0.5 and 0.3, respectively.
650+
random_mock.side_effect = [1e-8, 0.5, 0.3]
651+
returned_value = random.gammavariate(1.1, 2.3)
652+
self.assertAlmostEqual(returned_value, 2.53)
653+
654+
# #2: alpha == 1: first random number less than 1e-7 to that the body
655+
# of the while loop executes once. Then random.random() returns 0.45,
656+
# which causes while to stop looping and the algorithm to terminate.
657+
random_mock.side_effect = [1e-8, 0.45]
658+
returned_value = random.gammavariate(1.0, 3.14)
659+
self.assertAlmostEqual(returned_value, 2.507314166123803)
660+
661+
# #3: 0 < alpha < 1. This is the most complex region of code to cover,
662+
# as there are multiple if-else statements. Let's take a look at the
663+
# source code, and determine the values that we need accordingly:
664+
#
665+
# while 1:
666+
# u = random()
667+
# b = (_e + alpha)/_e
668+
# p = b*u
669+
# if p <= 1.0: # <=== (A)
670+
# x = p ** (1.0/alpha)
671+
# else: # <=== (B)
672+
# x = -_log((b-p)/alpha)
673+
# u1 = random()
674+
# if p > 1.0: # <=== (C)
675+
# if u1 <= x ** (alpha - 1.0): # <=== (D)
676+
# break
677+
# elif u1 <= _exp(-x): # <=== (E)
678+
# break
679+
# return x * beta
680+
#
681+
# First, we want (A) to be True. For that we need that:
682+
# b*random() <= 1.0
683+
# r1 = random() <= 1.0 / b
684+
#
685+
# We now get to the second if-else branch, and here, since p <= 1.0,
686+
# (C) is False and we take the elif branch, (E). For it to be True,
687+
# so that the break is executed, we need that:
688+
# r2 = random() <= _exp(-x)
689+
# r2 <= _exp(-(p ** (1.0/alpha)))
690+
# r2 <= _exp(-((b*r1) ** (1.0/alpha)))
691+
692+
_e = random._e
693+
_exp = random._exp
694+
_log = random._log
695+
alpha = 0.35
696+
beta = 1.45
697+
b = (_e + alpha)/_e
698+
epsilon = 0.01
699+
700+
r1 = 0.8859296441566 # 1.0 / b
701+
r2 = 0.3678794411714 # _exp(-((b*r1) ** (1.0/alpha)))
702+
703+
# These four "random" values result in the following trace:
704+
# (A) True, (E) False --> [next iteration of while]
705+
# (A) True, (E) True --> [while loop breaks]
706+
random_mock.side_effect = [r1, r2 + epsilon, r1, r2]
707+
returned_value = random.gammavariate(alpha, beta)
708+
self.assertAlmostEqual(returned_value, 1.4499999999997544)
709+
710+
# Let's now make (A) be False. If this is the case, when we get to the
711+
# second if-else 'p' is greater than 1, so (C) evaluates to True. We
712+
# now encounter a second if statement, (D), which in order to execute
713+
# must satisfy the following condition:
714+
# r2 <= x ** (alpha - 1.0)
715+
# r2 <= (-_log((b-p)/alpha)) ** (alpha - 1.0)
716+
# r2 <= (-_log((b-(b*r1))/alpha)) ** (alpha - 1.0)
717+
r1 = 0.8959296441566 # (1.0 / b) + epsilon -- so that (A) is False
718+
r2 = 0.9445400408898141
719+
720+
# And these four values result in the following trace:
721+
# (B) and (C) True, (D) False --> [next iteration of while]
722+
# (B) and (C) True, (D) True [while loop breaks]
723+
random_mock.side_effect = [r1, r2 + epsilon, r1, r2]
724+
returned_value = random.gammavariate(alpha, beta)
725+
self.assertAlmostEqual(returned_value, 1.5830349561760781)
726+
727+
@unittest.mock.patch('random.Random.gammavariate')
728+
def test_betavariate_return_zero(self, gammavariate_mock):
729+
# betavariate() returns zero when the Gamma distribution
730+
# that it uses internally returns this same value.
731+
gammavariate_mock.return_value = 0.0
732+
self.assertEqual(0.0, random.betavariate(2.71828, 3.14159))
558733

559734
class TestModule(unittest.TestCase):
560735
def testMagicConstants(self):

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,7 @@ Amy Taylor
12061206
Monty Taylor
12071207
Anatoly Techtonik
12081208
Mikhail Terekhov
1209+
Victor Terrón
12091210
Richard M. Tew
12101211
Tobias Thelen
12111212
Lowe Thiderman

0 commit comments

Comments
 (0)