]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/util/repeatable_random.py
Merge pull request #236 from daira/2725.timezone-test.0
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / util / repeatable_random.py
1 """
2 If you execute force_repeatability() then the following things are changed in the runtime:
3
4 1.  random.random() and its sibling functions, and random.Random.seed() in the random module are seeded with a known seed so that they will return the same sequence on each run.
5 2.  os.urandom() is replaced by a fake urandom that returns a pseudorandom sequence.
6 3.  time.time() is replaced by a fake time that returns an incrementing number.  (Original time.time is available as time.realtime.)
7
8 Which seed will be used?
9
10 If the environment variable REPEATABLE_RANDOMNESS_SEED is set, then it will use that.  Else, it will use the current real time.  In either case it logs the seed that it used.
11
12 Caveats:
13
14 1.  If some code has acquired a random.Random object before force_repeatability() is executed, then that Random object will produce non-reproducible results.  For example, the tempfile module in the Python Standard Library does this.
15 2.  Likewise if some code called time.time() before force_repeatability() was called, then it will have gotten a real time stamp.  For example, trial does this.  (Then it later subtracts that real timestamp from a faketime timestamp to calculate elapsed time, resulting in a large negative elapsed time.)
16 3.  The output from the fake urandom has weird distribution for performance reasons-- every byte after the first 20 bytes resulting from a single call to os.urandom() is zero.  In practice this hasn't caused any problems.
17 """
18
19 import os, random, time
20 if not hasattr(time, "realtime"):
21     time.realtime = time.time
22 if not hasattr(os, "realurandom"):
23     os.realurandom = os.urandom
24 if not hasattr(random, "realseed"):
25     random.realseed = random.seed
26
27 tdelta = 0
28 seeded = False
29 def force_repeatability():
30     now = 1043659734.0
31     def faketime():
32         global tdelta
33         tdelta += 1
34         return now + tdelta
35     time.faketime = faketime
36     time.time = faketime
37
38     from allmydata.util.idlib import i2b
39     def fakeurandom(n):
40         if n > 20:
41             z = i2b(random.getrandbits(20*8))
42         elif n == 0:
43             return ''
44         else:
45             z = i2b(random.getrandbits(n*8))
46         x = z + "0" * (n-len(z))
47         assert len(x) == n
48         return x
49     os.fakeurandom = fakeurandom
50     os.urandom = fakeurandom
51
52     global seeded
53     if not seeded:
54         SEED = os.environ.get('REPEATABLE_RANDOMNESS_SEED', None)
55
56         if SEED is None:
57             # Generate a seed which is integral and fairly short (to ease cut-and-paste, writing it down, etc.).
58             t = time.realtime()
59             subsec = t % 1
60             t += (subsec * 1000000)
61             t %= 1000000
62             SEED = long(t)
63         import sys
64         sys.stdout.write("REPEATABLE_RANDOMNESS_SEED: %s\n" % SEED) ; sys.stdout.flush()
65         sys.stdout.write("In order to reproduce this run of the code, set the environment variable \"REPEATABLE_RANDOMNESS_SEED\" to %s before executing.\n" % SEED) ; sys.stdout.flush()
66         random.seed(SEED)
67
68         def seed_which_refuses(a):
69             sys.stdout.write("I refuse to reseed to %s.  Go away!\n" % (a,)) ; sys.stdout.flush()
70             return
71         random.realseed = random.seed
72         random.seed = seed_which_refuses
73         seeded = True
74
75     import setutil
76     setutil.RandomSet.DETERMINISTIC = True
77
78 def restore_real_clock():
79     time.time = time.realtime
80
81 def restore_real_urandom():
82     os.urandom = os.realurandom
83
84 def restore_real_seed():
85     random.seed = random.realseed
86
87 def restore_non_repeatability():
88     restore_real_seed()
89     restore_real_urandom()
90     restore_real_clock()