require 'test/unit' require 'securerandom' require 'tempfile' # This testcase does NOT aim to test cryptographically strongness and randomness. class TestSecureRandom < Test::Unit::TestCase def setup @it = SecureRandom end def test_s_random_bytes assert_equal(16, @it.random_bytes.size) assert_equal(Encoding::ASCII_8BIT, @it.random_bytes.encoding) 65.times do |idx| assert_equal(idx, @it.random_bytes(idx).size) end end # This test took 2 minutes on my machine. # And 65536 times loop could not be enough for forcing PID recycle. if false def test_s_random_bytes_is_fork_safe begin require 'openssl' rescue LoadError return end SecureRandom.random_bytes(8) pid, v1 = forking_random_bytes assert(check_forking_random_bytes(pid, v1), 'Process ID not recycled?') end def forking_random_bytes r, w = IO.pipe pid = fork { r.close w.write SecureRandom.random_bytes(8) w.close } w.close v = r.read(8) r.close Process.waitpid2(pid) [pid, v] end def check_forking_random_bytes(target_pid, target) 65536.times do pid = fork { if $$ == target_pid v2 = SecureRandom.random_bytes(8) if v2 == target exit(1) else exit(2) end end exit(3) } pid, status = Process.waitpid2(pid) case status.exitstatus when 1 raise 'returned same sequence for same PID' when 2 return true end end false # not recycled? end end def test_s_random_bytes_without_openssl begin require 'openssl' rescue LoadError return end begin load_path = $LOAD_PATH.dup loaded_features = $LOADED_FEATURES.dup openssl = Object.instance_eval { remove_const(:OpenSSL) } remove_feature('securerandom.rb') remove_feature('openssl.rb') Dir.mktmpdir do |dir| open(File.join(dir, 'openssl.rb'), 'w') { |f| f << 'raise LoadError' } $LOAD_PATH.unshift(dir) v = $VERBOSE begin $VERBOSE = false require 'securerandom' ensure $VERBOSE = v end test_s_random_bytes end ensure $LOADED_FEATURES.replace(loaded_features) $LOAD_PATH.replace(load_path) Object.const_set(:OpenSSL, openssl) end end def test_s_hex s = @it.hex assert_equal(16 * 2, s.size) assert_match(/\A\h+\z/, s) 33.times do |idx| s = @it.hex(idx) assert_equal(idx * 2, s.size) assert_match(/\A\h*\z/, s) end end def test_hex_encoding assert_equal(Encoding::US_ASCII, @it.hex.encoding) end def test_s_base64 assert_equal(16, @it.base64.unpack('m*')[0].size) 17.times do |idx| assert_equal(idx, @it.base64(idx).unpack('m*')[0].size) end end def test_s_urlsafe_base64 safe = /[\n+\/]/ 65.times do |idx| assert_not_match(safe, @it.urlsafe_base64(idx)) end # base64 can include unsafe byte assert((0..10000).any? {|idx| safe =~ @it.base64(idx)}, "None of base64(0..10000) is url-safe") end def test_s_random_number_float 101.times do v = @it.random_number assert_in_range(0.0...1.0, v) end end def test_s_random_number_float_by_zero 101.times do v = @it.random_number(0) assert_in_range(0.0...1.0, v) end end def test_s_random_number_int 101.times do |idx| next if idx.zero? v = @it.random_number(idx) assert_in_range(0...idx, v) end end def test_uuid uuid = @it.uuid assert_equal(36, uuid.size) assert_match(/\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/, uuid) end def protect begin yield rescue NotImplementedError # ignore end end def remove_feature(basename) $LOADED_FEATURES.delete_if { |path| if File.basename(path) == basename $LOAD_PATH.any? { |dir| File.exist?(File.join(dir, basename)) } end } end def assert_in_range(range, result, mesg = nil) assert(range.cover?(result), message(mesg) {"Expected #{result} to be in #{range}"}) end end