--- /dev/null
+"""
+If you execute force_repeatability() then the following things are changed in the runtime:
+
+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.
+2. os.urandom() is replaced by a fake urandom that returns a pseudorandom sequence.
+3. time.time() is replaced by a fake time that returns an incrementing number. (Original time.time is available as time.realtime.)
+
+Which seed will be used?
+
+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.
+
+Caveats:
+
+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.
+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.)
+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.
+"""
+
+import os, random, time
+if not hasattr(time, "realtime"):
+ time.realtime = time.time
+if not hasattr(os, "realurandom"):
+ os.realurandom = os.urandom
+if not hasattr(random, "realseed"):
+ random.realseed = random.seed
+
+tdelta = 0
+seeded = False
+def force_repeatability():
+ now = 1043659734.0
+ def faketime():
+ global tdelta
+ tdelta += 1
+ return now + tdelta
+ time.faketime = faketime
+ time.time = faketime
+
+ from idlib import i2b
+ def fakeurandom(n):
+ if n > 20:
+ z = i2b(random.getrandbits(20*8))
+ elif n == 0:
+ return ''
+ else:
+ z = i2b(random.getrandbits(n*8))
+ x = z + "0" * (n-len(z))
+ assert len(x) == n
+ return x
+ os.fakeurandom = fakeurandom
+ os.urandom = fakeurandom
+
+ global seeded
+ if not seeded:
+ SEED = os.environ.get('REPEATABLE_RANDOMNESS_SEED', None)
+
+ if SEED is None:
+ # Generate a seed which is integral and fairly short (to ease cut-and-paste, writing it down, etc.).
+ t = time.realtime()
+ subsec = t % 1
+ t += (subsec * 1000000)
+ t %= 1000000
+ SEED = long(t)
+ import sys
+ sys.stdout.write("REPEATABLE_RANDOMNESS_SEED: %s\n" % SEED) ; sys.stdout.flush()
+ 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()
+ random.seed(SEED)
+
+ def seed_which_refuses(a):
+ sys.stdout.write("I refuse to reseed to %s. Go away!\n" % (a,)) ; sys.stdout.flush()
+ return
+ random.realseed = random.seed
+ random.seed = seed_which_refuses
+ seeded = True
+
+ import setutil
+ setutil.RandomSet.DETERMINISTIC = True
+
+def restore_real_clock():
+ time.time = time.realtime
+
+def restore_real_urandom():
+ os.urandom = os.realurandom
+
+def restore_real_seed():
+ random.seed = random.realseed
+
+def restore_non_repeatability():
+ restore_real_seed()
+ restore_real_urandom()
+ restore_real_clock()