mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
2db2fb9f6c
Random generators are not Ractor-safe, so we need to prepare per-ractor default random genearators. This patch set `Random::DEFAULT = Randm` (not a Random instance, but the Random class) and singleton methods like `Random.rand()` use a per-ractor random generator. [Feature #17322]
427 lines
10 KiB
Ruby
427 lines
10 KiB
Ruby
# frozen_string_literal: false
|
|
require 'test/unit'
|
|
|
|
class TestRand < Test::Unit::TestCase
|
|
def assert_random_int(m, init = 0, iterate: 5)
|
|
srand(init)
|
|
rnds = [Random.new(init)]
|
|
rnds2 = [rnds[0].dup]
|
|
rnds3 = [rnds[0].dup]
|
|
iterate.times do |i|
|
|
w = rand(m)
|
|
rnds.each do |rnd|
|
|
assert_equal(w, rnd.rand(m))
|
|
end
|
|
rnds2.each do |rnd|
|
|
r=rnd.rand(i...(m+i))
|
|
assert_equal(w+i, r)
|
|
end
|
|
rnds3.each do |rnd|
|
|
r=rnd.rand(i..(m+i-1))
|
|
assert_equal(w+i, r)
|
|
end
|
|
rnds << Marshal.load(Marshal.dump(rnds[-1]))
|
|
rnds2 << Marshal.load(Marshal.dump(rnds2[-1]))
|
|
end
|
|
end
|
|
|
|
def test_mt
|
|
assert_random_int(0x100000000, 0x00000456_00000345_00000234_00000123)
|
|
end
|
|
|
|
def test_0x3fffffff
|
|
assert_random_int(0x3fffffff)
|
|
end
|
|
|
|
def test_0x40000000
|
|
assert_random_int(0x40000000)
|
|
end
|
|
|
|
def test_0x40000001
|
|
assert_random_int(0x40000001)
|
|
end
|
|
|
|
def test_0xffffffff
|
|
assert_random_int(0xffffffff)
|
|
end
|
|
|
|
def test_0x100000000
|
|
assert_random_int(0x100000000)
|
|
end
|
|
|
|
def test_0x100000001
|
|
assert_random_int(0x100000001)
|
|
end
|
|
|
|
def test_rand_0x100000000
|
|
assert_random_int(0x100000001, 311702798)
|
|
end
|
|
|
|
def test_0x1000000000000
|
|
assert_random_int(0x1000000000000)
|
|
end
|
|
|
|
def test_0x1000000000001
|
|
assert_random_int(0x1000000000001)
|
|
end
|
|
|
|
def test_0x3fffffffffffffff
|
|
assert_random_int(0x3fffffffffffffff)
|
|
end
|
|
|
|
def test_0x4000000000000000
|
|
assert_random_int(0x4000000000000000)
|
|
end
|
|
|
|
def test_0x4000000000000001
|
|
assert_random_int(0x4000000000000001)
|
|
end
|
|
|
|
def test_0x10000000000
|
|
assert_random_int(0x10000000000, 3)
|
|
end
|
|
|
|
def test_0x10000
|
|
assert_random_int(0x10000)
|
|
end
|
|
|
|
def assert_same_numbers(type, *nums)
|
|
nums.each do |n|
|
|
assert_instance_of(type, n)
|
|
end
|
|
x = nums.shift
|
|
nums.each do |n|
|
|
assert_equal(x, n)
|
|
end
|
|
x
|
|
end
|
|
|
|
def test_types
|
|
o = Object.new
|
|
class << o
|
|
def to_int; 100; end
|
|
def class; Integer; end
|
|
end
|
|
|
|
srand(0)
|
|
nums = [100.0, (2**100).to_f, (2**100), o, o, o].map do |m|
|
|
k = Integer
|
|
assert_kind_of(k, x = rand(m), m.inspect)
|
|
[m, k, x]
|
|
end
|
|
assert_kind_of(Integer, rand(-(2**100).to_f))
|
|
|
|
srand(0)
|
|
rnd = Random.new(0)
|
|
rnd2 = Random.new(0)
|
|
nums.each do |m, k, x|
|
|
assert_same_numbers(m.class, Random.rand(m), rnd.rand(m), rnd2.rand(m))
|
|
end
|
|
end
|
|
|
|
def test_srand
|
|
srand
|
|
assert_kind_of(Integer, rand(2))
|
|
assert_kind_of(Integer, Random.new.rand(2))
|
|
|
|
srand(2**100)
|
|
rnd = Random.new(2**100)
|
|
r = 3.times.map do
|
|
assert_same_numbers(Integer, rand(0x100000000), rnd.rand(0x100000000))
|
|
end
|
|
srand(2**100)
|
|
r.each do |n|
|
|
assert_same_numbers(Integer, n, rand(0x100000000))
|
|
end
|
|
end
|
|
|
|
def test_shuffle
|
|
srand(0)
|
|
result = [*1..5].shuffle
|
|
assert_equal([*1..5], result.sort)
|
|
assert_equal(result, [*1..5].shuffle(random: Random.new(0)))
|
|
end
|
|
|
|
def test_big_seed
|
|
assert_random_int(0x100000000, 2**1000000-1)
|
|
end
|
|
|
|
def test_random_gc
|
|
r = Random.new(0)
|
|
3.times do
|
|
assert_kind_of(Integer, r.rand(0x100000000))
|
|
end
|
|
GC.start
|
|
3.times do
|
|
assert_kind_of(Integer, r.rand(0x100000000))
|
|
end
|
|
end
|
|
|
|
def test_random_type_error
|
|
assert_raise(TypeError) { Random.new(Object.new) }
|
|
assert_raise(TypeError) { Random.new(0).rand(Object.new) }
|
|
end
|
|
|
|
def test_random_argument_error
|
|
r = Random.new(0)
|
|
assert_raise(ArgumentError) { r.rand(0, 0) }
|
|
assert_raise(ArgumentError, '[ruby-core:24677]') { r.rand(-1) }
|
|
assert_raise(ArgumentError, '[ruby-core:24677]') { r.rand(-1.0) }
|
|
assert_raise(ArgumentError, '[ruby-core:24677]') { r.rand(0) }
|
|
assert_equal(0, r.rand(1), '[ruby-dev:39166]')
|
|
assert_equal(0, r.rand(0...1), '[ruby-dev:39166]')
|
|
assert_equal(0, r.rand(0..0), '[ruby-dev:39166]')
|
|
assert_equal(0.0, r.rand(0.0..0.0), '[ruby-dev:39166]')
|
|
assert_raise(ArgumentError, '[ruby-dev:39166]') { r.rand(0...0) }
|
|
assert_raise(ArgumentError, '[ruby-dev:39166]') { r.rand(0..-1) }
|
|
assert_raise(ArgumentError, '[ruby-dev:39166]') { r.rand(0.0...0.0) }
|
|
assert_raise(ArgumentError, '[ruby-dev:39166]') { r.rand(0.0...-0.1) }
|
|
bug3027 = '[ruby-core:29075]'
|
|
assert_raise(ArgumentError, bug3027) { r.rand(nil) }
|
|
end
|
|
|
|
def test_random_seed
|
|
assert_equal(0, Random.new(0).seed)
|
|
assert_equal(0x100000000, Random.new(0x100000000).seed)
|
|
assert_equal(2**100, Random.new(2**100).seed)
|
|
end
|
|
|
|
def test_random_dup
|
|
r1 = Random.new(0)
|
|
r2 = r1.dup
|
|
3.times do
|
|
assert_same_numbers(Integer, r1.rand(0x100000000), r2.rand(0x100000000))
|
|
end
|
|
r2 = r1.dup
|
|
3.times do
|
|
assert_same_numbers(Integer, r1.rand(0x100000000), r2.rand(0x100000000))
|
|
end
|
|
end
|
|
|
|
def test_random_bytes
|
|
srand(0)
|
|
r = Random.new(0)
|
|
|
|
assert_equal("", r.bytes(0))
|
|
assert_equal("", Random.bytes(0))
|
|
|
|
x = r.bytes(1)
|
|
assert_equal(1, x.bytesize)
|
|
assert_equal(x, Random.bytes(1))
|
|
|
|
x = r.bytes(10)
|
|
assert_equal(10, x.bytesize)
|
|
assert_equal(x, Random.bytes(10))
|
|
end
|
|
|
|
def test_random_range
|
|
srand(0)
|
|
r = Random.new(0)
|
|
now = Time.now
|
|
[5..9, -1000..1000, 2**100+5..2**100+9, 3.1..4, now..(now+2)].each do |range|
|
|
3.times do
|
|
x = rand(range)
|
|
assert_instance_of(range.first.class, x)
|
|
assert_equal(x, r.rand(range))
|
|
assert_include(range, x)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_random_float
|
|
r = Random.new(0)
|
|
3.times do
|
|
assert_include(0...1.0, r.rand)
|
|
end
|
|
[2.0, (2**100).to_f].each do |x|
|
|
range = 0...x
|
|
3.times do
|
|
assert_include(range, r.rand(x), "rand(#{x})")
|
|
end
|
|
end
|
|
|
|
assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(1.0 / 0.0) }
|
|
assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(0.0 / 0.0) }
|
|
assert_raise(Errno::EDOM) {r.rand(1..)}
|
|
assert_raise(Errno::EDOM) {r.rand(..1)}
|
|
|
|
r = Random.new(0)
|
|
[1.0...2.0, 1.0...11.0, 2.0...4.0].each do |range|
|
|
3.times do
|
|
assert_include(range, r.rand(range), "[ruby-core:24655] rand(#{range})")
|
|
end
|
|
end
|
|
|
|
assert_nothing_raised {r.rand(-Float::MAX..Float::MAX)}
|
|
end
|
|
|
|
def test_random_equal
|
|
r = Random.new(0)
|
|
assert_equal(r, r)
|
|
assert_equal(r, r.dup)
|
|
r1 = r.dup
|
|
r2 = r.dup
|
|
r1.rand(0x100)
|
|
assert_not_equal(r1, r2)
|
|
r2.rand(0x100)
|
|
assert_equal(r1, r2)
|
|
end
|
|
|
|
def test_fork_shuffle
|
|
pid = fork do
|
|
(1..10).to_a.shuffle
|
|
raise 'default seed is not set' if srand == 0
|
|
end
|
|
_, st = Process.waitpid2(pid)
|
|
assert_predicate(st, :success?, "#{st.inspect}")
|
|
rescue NotImplementedError, ArgumentError
|
|
end
|
|
|
|
def assert_fork_status(n, mesg, &block)
|
|
IO.pipe do |r, w|
|
|
(1..n).map do
|
|
st = desc = nil
|
|
IO.pipe do |re, we|
|
|
p1 = fork {
|
|
re.close
|
|
STDERR.reopen(we)
|
|
w.puts(block.call.to_s)
|
|
}
|
|
we.close
|
|
err = Thread.start {re.read}
|
|
_, st = Process.waitpid2(p1)
|
|
desc = FailDesc[st, mesg, err.value]
|
|
end
|
|
assert(!st.signaled?, desc)
|
|
assert(st.success?, mesg)
|
|
r.gets.strip
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_rand_reseed_on_fork
|
|
GC.start
|
|
bug5661 = '[ruby-core:41209]'
|
|
|
|
assert_fork_status(1, bug5661) {Random.rand(4)}
|
|
r1, r2 = *assert_fork_status(2, bug5661) {Random.rand}
|
|
assert_not_equal(r1, r2, bug5661)
|
|
|
|
assert_fork_status(1, bug5661) {rand(4)}
|
|
r1, r2 = *assert_fork_status(2, bug5661) {rand}
|
|
assert_not_equal(r1, r2, bug5661)
|
|
|
|
stable = Random.new
|
|
assert_fork_status(1, bug5661) {stable.rand(4)}
|
|
r1, r2 = *assert_fork_status(2, bug5661) {stable.rand}
|
|
assert_equal(r1, r2, bug5661)
|
|
|
|
assert_fork_status(1, '[ruby-core:82100] [Bug #13753]') do
|
|
Random::DEFAULT.rand(4)
|
|
end
|
|
rescue NotImplementedError
|
|
end
|
|
|
|
def test_seed
|
|
bug3104 = '[ruby-core:29292]'
|
|
rand_1 = Random.new(-1).rand
|
|
assert_not_equal(rand_1, Random.new((1 << 31) -1).rand, "#{bug3104} (2)")
|
|
assert_not_equal(rand_1, Random.new((1 << 63) -1).rand, "#{bug3104} (2)")
|
|
|
|
[-1, -2**10, -2**40].each {|n|
|
|
b = (2**64).coerce(n)[0]
|
|
r1 = Random.new(n).rand
|
|
r2 = Random.new(b).rand
|
|
assert_equal(r1, r2)
|
|
}
|
|
end
|
|
|
|
def test_marshal
|
|
bug3656 = '[ruby-core:31622]'
|
|
assert_raise(TypeError, bug3656) {
|
|
Random.new.__send__(:marshal_load, 0)
|
|
}
|
|
end
|
|
|
|
def test_initialize_frozen
|
|
r = Random.new(0)
|
|
r.freeze
|
|
assert_raise(FrozenError, '[Bug #6540]') do
|
|
r.__send__(:initialize, r)
|
|
end
|
|
end
|
|
|
|
def test_marshal_load_frozen
|
|
r = Random.new(0)
|
|
d = r.__send__(:marshal_dump)
|
|
r.freeze
|
|
assert_raise(FrozenError, '[Bug #6540]') do
|
|
r.__send__(:marshal_load, d)
|
|
end
|
|
end
|
|
|
|
def test_random_ulong_limited
|
|
def (gen = Object.new).rand(*) 1 end
|
|
assert_equal([2], (1..100).map {[1,2,3].sample(random: gen)}.uniq)
|
|
|
|
def (gen = Object.new).rand(*) 100 end
|
|
assert_raise_with_message(RangeError, /big 100\z/) {[1,2,3].sample(random: gen)}
|
|
|
|
bug7903 = '[ruby-dev:47061] [Bug #7903]'
|
|
def (gen = Object.new).rand(*) -1 end
|
|
assert_raise_with_message(RangeError, /small -1\z/, bug7903) {[1,2,3].sample(random: gen)}
|
|
|
|
bug7935 = '[ruby-core:52779] [Bug #7935]'
|
|
class << (gen = Object.new)
|
|
def rand(limit) @limit = limit; 0 end
|
|
attr_reader :limit
|
|
end
|
|
[1, 2].sample(1, random: gen)
|
|
assert_equal(2, gen.limit, bug7935)
|
|
end
|
|
|
|
def test_random_ulong_limited_no_rand
|
|
c = Class.new do
|
|
undef rand
|
|
def bytes(n)
|
|
"\0"*n
|
|
end
|
|
end
|
|
gen = c.new.extend(Random::Formatter)
|
|
assert_equal(1, [1, 2].sample(random: gen))
|
|
end
|
|
|
|
def test_default_seed
|
|
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
|
|
begin;
|
|
seed = Random::DEFAULT::seed
|
|
rand1 = Random::DEFAULT::rand
|
|
rand2 = Random.new(seed).rand
|
|
assert_equal(rand1, rand2)
|
|
|
|
srand seed
|
|
rand3 = rand
|
|
assert_equal(rand1, rand3)
|
|
end;
|
|
end
|
|
|
|
def test_urandom
|
|
[0, 1, 100].each do |size|
|
|
v = Random.urandom(size)
|
|
assert_kind_of(String, v)
|
|
assert_equal(size, v.bytesize)
|
|
end
|
|
end
|
|
|
|
def test_new_seed
|
|
size = 0
|
|
n = 8
|
|
n.times do
|
|
v = Random.new_seed
|
|
assert_kind_of(Integer, v)
|
|
size += v.size
|
|
end
|
|
# probability of failure <= 1/256**8
|
|
assert_operator(size.fdiv(n), :>, 15)
|
|
end
|
|
end
|