[Python-checkins] cpython: #17492: Additional tests for random module.

r.david.murray python-checkins at python.org
Tue Apr 2 18:48:16 CEST 2013


http://hg.python.org/cpython/rev/2a340f963518
changeset:   83062:2a340f963518
parent:      83060:96c0efe93774
user:        R David Murray <rdmurray at bitdance.com>
date:        Tue Apr 02 12:47:23 2013 -0400
summary:
  #17492: Additional tests for random module.

Patch by Victor Terrón.

files:
  Lib/test/test_random.py |  175 ++++++++++++++++++++++++++++
  Misc/ACKS               |    1 +
  2 files changed, 176 insertions(+), 0 deletions(-)


diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py
--- a/Lib/test/test_random.py
+++ b/Lib/test/test_random.py
@@ -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 @@
         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 @@
             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 @@
             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 @@
         # 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 @@
             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 @@
         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):
diff --git a/Misc/ACKS b/Misc/ACKS
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1206,6 +1206,7 @@
 Monty Taylor
 Anatoly Techtonik
 Mikhail Terekhov
+Victor Terrón
 Richard M. Tew
 Tobias Thelen
 Lowe Thiderman

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list