1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
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
assert_equal(16 * 2, @it.hex.size)
33.times do |idx|
assert_equal(idx * 2, @it.hex(idx).size)
assert_equal(idx, @it.hex(idx).gsub(/(..)/) { [$1].pack('H*') }.size)
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
10001.times do |idx|
return if safe =~ @it.base64(idx)
end
flunk
end
def test_s_random_number_float
101.times do
v = @it.random_number
assert(0.0 <= v && v < 1.0)
end
end
def test_s_random_number_float_by_zero
101.times do
v = @it.random_number(0)
assert(0.0 <= v && v < 1.0)
end
end
def test_s_random_number_int
101.times do |idx|
next if idx.zero?
v = @it.random_number(idx)
assert(0 <= v && v < idx)
end
end
def test_uuid
uuid = @it.uuid
assert_equal(36, uuid.size)
uuid.unpack('a8xa4xa4xa4xa12').each do |e|
assert_match(/^[0-9a-f]+$/, e)
end
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.exists?(File.join(dir, basename))
}
end
}
end
end
|