pool_of_entropy 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/workflows/rspec.yml +23 -0
- data/.rubocop.yml +18 -0
- data/Gemfile +11 -1
- data/README.md +0 -5
- data/Rakefile +7 -5
- data/lib/pool_of_entropy/core_prng.rb +149 -143
- data/lib/pool_of_entropy/version.rb +3 -1
- data/lib/pool_of_entropy.rb +55 -59
- data/pool_of_entropy.gemspec +15 -16
- data/spec/pool_of_entropy/core_prng_spec.rb +386 -0
- data/spec/pool_of_entropy_spec.rb +264 -303
- data/spec/spec_helper.rb +49 -3
- metadata +12 -88
- data/.travis.yml +0 -11
- data/spec/core_prng_spec.rb +0 -543
data/pool_of_entropy.gemspec
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
5
|
require 'pool_of_entropy/version'
|
|
5
6
|
|
|
6
7
|
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name =
|
|
8
|
+
spec.name = 'pool_of_entropy'
|
|
8
9
|
spec.version = PoolOfEntropy::VERSION
|
|
9
|
-
spec.authors = [
|
|
10
|
-
spec.email = [
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.description =
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
spec.authors = ['Neil Slater']
|
|
11
|
+
spec.email = ['slobo777@gmail.com']
|
|
12
|
+
spec.summary = 'Random number generator with extra features for gamers.'
|
|
13
|
+
spec.description = 'PoolOfEntropy is a PRNG based on cryptographically secure PRNGs, ' \
|
|
14
|
+
"intended to bring back the feeling of 'personal luck' that some gamers " \
|
|
15
|
+
'feel when rolling their own dice.'
|
|
16
|
+
spec.homepage = 'https://github.com/neilslater/pool_of_entropy'
|
|
17
|
+
spec.license = 'MIT'
|
|
18
|
+
spec.required_ruby_version = '>= 3.3'
|
|
15
19
|
|
|
16
20
|
spec.files = `git ls-files -z`.split("\x0")
|
|
17
21
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
-
spec.
|
|
19
|
-
spec.require_paths = ["lib"]
|
|
22
|
+
spec.require_paths = ['lib']
|
|
20
23
|
|
|
21
|
-
spec.
|
|
22
|
-
spec.add_development_dependency "bundler", ">= 1.3"
|
|
23
|
-
spec.add_development_dependency "rspec", ">= 2.13.0"
|
|
24
|
-
spec.add_development_dependency "rake", ">= 1.9.1"
|
|
25
|
-
spec.add_development_dependency "coveralls", ">= 0.6.7"
|
|
24
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
26
25
|
end
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe PoolOfEntropy::CorePRNG do
|
|
6
|
+
shared_examples 'an adjustable reader' do |prng, read|
|
|
7
|
+
let(:original) { prng.clone }
|
|
8
|
+
let(:copy) { original.clone }
|
|
9
|
+
|
|
10
|
+
it 'returns the same output for the same adjustment' do
|
|
11
|
+
copy
|
|
12
|
+
10.times do
|
|
13
|
+
expect(read.call(original, 'Hello!')).to eql read.call(copy, 'Hello!')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
{ 'different adjustments' => [%w[Hello!], %w[Goodbye!]],
|
|
18
|
+
'an adjustment on only the original' => [%w[Hello!], []],
|
|
19
|
+
'an adjustment on only the copy' => [[], %w[Goodbye!]],
|
|
20
|
+
'multiple adjustments on only the original' => [%w[Hello Goodbye], []] }.each do |description, arguments|
|
|
21
|
+
it "returns different output for #{description}" do
|
|
22
|
+
copy
|
|
23
|
+
10.times do
|
|
24
|
+
expect(read.call(original, *arguments.first)).not_to eql read.call(copy, *arguments.last)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'when adjusted reads use mismatched arguments' do
|
|
30
|
+
before do
|
|
31
|
+
copy
|
|
32
|
+
[[%w[Hello!], %w[Goodbye!]], [[], %w[Goodbye!]], [%w[Hello!], []], [%w[Hello Goodbye], []]].each do |args|
|
|
33
|
+
read.call(original, *args.first)
|
|
34
|
+
read.call(copy, *args.last)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'does not include adjustments in state changes' do
|
|
39
|
+
expect(original.state).to eql copy.state
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'returns to matching output' do
|
|
43
|
+
expect(read.call(original)).to eql read.call(copy)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '.new' do
|
|
49
|
+
it 'instantiates a default object' do
|
|
50
|
+
prng = described_class.new
|
|
51
|
+
expect(prng).to be_a(described_class).and have_attributes(size: 1)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'allows setting number of blocks in pool' do
|
|
55
|
+
prng = described_class.new(10)
|
|
56
|
+
expect(prng).to be_a(described_class).and have_attributes(size: 10)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
{ -43 => ArgumentError, -1 => ArgumentError, 0 => ArgumentError,
|
|
60
|
+
257 => ArgumentError, 1000 => ArgumentError, nil => TypeError,
|
|
61
|
+
'' => ArgumentError, { foo: 2 } => TypeError }.each do |size, error|
|
|
62
|
+
it "rejects block count #{size.inspect}" do
|
|
63
|
+
expect { described_class.new(size) }.to raise_error error
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'allows setting internal state' do
|
|
68
|
+
prng = described_class.new(1, "\x0" * 64)
|
|
69
|
+
expect(prng).to be_a(described_class).and have_attributes(size: 1, state: "\x0" * 64)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
{ [1, :boo] => TypeError, [1, []] => TypeError, [1, ''.dup] => ArgumentError,
|
|
73
|
+
[1, "\x0" * 63] => ArgumentError, [1, "\x0" * 200] => ArgumentError,
|
|
74
|
+
[2, "\x0" * 64] => ArgumentError }.each do |arguments, error|
|
|
75
|
+
it "rejects state data #{arguments.last.inspect}" do
|
|
76
|
+
expect { described_class.new(*arguments) }.to raise_error error
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'allows setting mix_block_id' do
|
|
81
|
+
prng = described_class.new(3, "\x12" * 192, 1)
|
|
82
|
+
expect(prng).to be_a(described_class).and have_attributes(
|
|
83
|
+
size: 3, state: "\x12" * 192, mix_block_id: 1
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe '#clone' do
|
|
89
|
+
it 'copies all attributes' do
|
|
90
|
+
prng_orig = described_class.new
|
|
91
|
+
prng_copy = prng_orig.clone
|
|
92
|
+
|
|
93
|
+
expect(prng_copy).to have_attributes(
|
|
94
|
+
size: prng_orig.size, state: prng_orig.state, mix_block_id: prng_orig.mix_block_id
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'deeps clone the internal state string' do
|
|
99
|
+
prng_orig = described_class.new
|
|
100
|
+
prng_copy = prng_orig.clone
|
|
101
|
+
expect(prng_copy.state).not_to be prng_orig.state
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe '#update' do
|
|
106
|
+
it 'changes the internal state' do
|
|
107
|
+
prng = described_class.new
|
|
108
|
+
init_state = prng.state.clone
|
|
109
|
+
prng.update('boo')
|
|
110
|
+
expect(prng.state).not_to eql init_state
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
{ 1 => %w[boo boowgkjwrhqgioueqrhgiue2hguirhqwiughreuioghreuifhqwoifhr3iufghfwrgrwgetdfwd],
|
|
114
|
+
5 => ['boo', 'getdfwd' * 1000, 'boefewfweo', 'geefewftdfwd' * 1000] }.each do |size, updates|
|
|
115
|
+
it "preserves the state length for a #{size}-block pool" do
|
|
116
|
+
prng = described_class.new(size)
|
|
117
|
+
lengths = state_lengths_after(prng, updates)
|
|
118
|
+
expect(lengths).to all be(size * 64)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
['boo', 'getdfwd' * 1000, 'boefewfweo', 'geefewftdfwd' * 1000].each_with_index do |data, index|
|
|
123
|
+
it "changes only state block #{index}" do
|
|
124
|
+
prng = described_class.new(5)
|
|
125
|
+
index.times { |prior| prng.update(['boo', 'getdfwd' * 1000, 'boefewfweo'][prior]) }
|
|
126
|
+
before = prng.state.clone
|
|
127
|
+
expect { prng.update(data) }.to change { [before, prng.state] }.to(change_only_block(index))
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
instance01 = described_class.new
|
|
133
|
+
instance01.update('QWertyuiopp')
|
|
134
|
+
instance01.update('Asdfghjkjl')
|
|
135
|
+
instance01.update('Zxcvbnm')
|
|
136
|
+
|
|
137
|
+
pool_types = [
|
|
138
|
+
[
|
|
139
|
+
'default instance',
|
|
140
|
+
described_class.new
|
|
141
|
+
],
|
|
142
|
+
[
|
|
143
|
+
'instance with 2KB pool size',
|
|
144
|
+
described_class.new(32)
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
'instance with initial state all 0',
|
|
148
|
+
described_class.new(1, "\x0" * 64)
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
'instance with fixed initial state',
|
|
152
|
+
described_class.new(5, 'fiver' * 64, 3)
|
|
153
|
+
],
|
|
154
|
+
[
|
|
155
|
+
'instance cloned from 2KB instance',
|
|
156
|
+
described_class.new(32).clone
|
|
157
|
+
],
|
|
158
|
+
[
|
|
159
|
+
'instance that has been updated with user data',
|
|
160
|
+
instance01
|
|
161
|
+
]
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
# NB "probability" and "randomness" tests in the following block are very light, just
|
|
165
|
+
# intended to capture high-level failures in logic. See DIEHARDER_TEST.md for thorough
|
|
166
|
+
# checks on statistical randomness of PoolOfEntropy::CorePRNG
|
|
167
|
+
pool_types.each do |prng_name, prng|
|
|
168
|
+
context "with #{prng_name}" do
|
|
169
|
+
describe '#read_bytes' do
|
|
170
|
+
it 'always returns a 16 byte string' do
|
|
171
|
+
100.times { expect(prng.read_bytes.length).to be 16 }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'has a high probability of returning a different string each time' do
|
|
175
|
+
expect(Set[*(1..100).map { prng.read_bytes }].size).to be 100
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'always returns a 16 byte string with adjustments' do
|
|
179
|
+
100.times { expect(prng.read_bytes('654321').length).to be 16 }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'has a high probability of returning a different adjusted string each time' do
|
|
183
|
+
expect(Set[*(1..100).map { prng.read_bytes('654321') }].size).to be 100
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it_behaves_like 'an adjustable reader', prng, ->(reader, *args) { reader.read_bytes(*args) }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
describe '#read_hex' do
|
|
190
|
+
it 'always returns a 32 digit hex string' do
|
|
191
|
+
100.times do
|
|
192
|
+
expect(prng.read_hex).to match(/\A[0-9a-f]{32}\z/)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'has a high probability of returning a different string each time' do
|
|
197
|
+
expect(Set[*(1..100).map { prng.read_hex }].size).to be 100
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'always returns a 32 digit hex string with adjustments' do
|
|
201
|
+
100.times do
|
|
202
|
+
expect(prng.read_hex('QWertyeu')).to match(/\A[0-9a-f]{32}\z/)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it 'has a high probability of returning a different adjusted string each time' do
|
|
207
|
+
expect(Set[*(1..100).map { prng.read_hex('654321') }].size).to be 100
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it_behaves_like 'an adjustable reader', prng, ->(reader, *args) { reader.read_hex(*args) }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
describe '#read_bignum' do
|
|
214
|
+
it 'always returns a 128-bit unsigned integer' do
|
|
215
|
+
100.times do
|
|
216
|
+
expect(prng.read_bignum).to be_an_integer_in(0...(2**128))
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'has a high probability of returning a different number each time' do
|
|
221
|
+
expect(Set[*(1..100).map { prng.read_bignum }].size).to be 100
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'always returns an adjusted 128-bit unsigned integer' do
|
|
225
|
+
100.times do
|
|
226
|
+
expect(prng.read_bignum('Biggest')).to be_an_integer_in(0...(2**128))
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it 'has a high probability of returning a different adjusted number each time' do
|
|
231
|
+
expect(Set[*(1..100).map { prng.read_bignum('654321') }].size).to be 100
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
it_behaves_like 'an adjustable reader', prng, ->(reader, *args) { reader.read_bignum(*args) }
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
describe '#read_float' do
|
|
238
|
+
it 'always returns a Float between 0.0 (inclusive) and 1.0 (exclusive)' do
|
|
239
|
+
100.times do
|
|
240
|
+
expect(prng.read_float).to be_a_float_in(0.0...1.0)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'has a high probability of returning a different Float each time' do
|
|
245
|
+
expect(Set[*(1..100).map { prng.read_float }].size).to be 100
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'always returns an adjusted Float between 0.0 (inclusive) and 1.0 (exclusive)' do
|
|
249
|
+
100.times do
|
|
250
|
+
expect(prng.read_float('Boom')).to be_a_float_in(0.0...1.0)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it 'has a high probability of returning a different adjusted Float each time' do
|
|
255
|
+
expect(Set[*(1..100).map { prng.read_float('654321') }].size).to be 100
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
it_behaves_like 'an adjustable reader', prng, ->(reader, *args) { reader.read_float(*args) }
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
describe '#generate_integer' do
|
|
262
|
+
it 'always returns an integer between 0 (inclusive) and supplied top (exclusive)' do
|
|
263
|
+
[10, 100, 1000, 647_218_456].each do |maximum|
|
|
264
|
+
100.times do
|
|
265
|
+
expect(prng.generate_integer(maximum)).to be_an_integer_in(0...maximum)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it 'generates integers within a range larger than 2**128' do
|
|
271
|
+
results = (0..100).map do
|
|
272
|
+
prng.generate_integer(2**1024)
|
|
273
|
+
end
|
|
274
|
+
expect(results).to all be_an_integer_in(0...(2**1024))
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it 'can generate integers larger than 2**1020' do
|
|
278
|
+
results = (0..100).map { prng.generate_integer(2**1024) }
|
|
279
|
+
expect(results.select { |n| n > 2**1020 }.count).to be > 50
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it 'returns different integers without adjustments when the upper bound is large' do
|
|
283
|
+
expect(Set[*(1..100).map { prng.generate_integer((2**75) - 7) }].size).to be 100
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
it 'covers a distribution 0...top' do
|
|
287
|
+
expect(Set[*(1..100).map { prng.generate_integer(10) }].size).to be 10
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
[[10, %w[jkffwe]], [100, %w[jkffweefewg]], [1000, %w[jkffweefewg efhwjkfgw]],
|
|
291
|
+
[647_218_456, %w[j*****g efhwjkfgw]]].each do |maximum, adjustments|
|
|
292
|
+
it "returns integers below #{maximum}" do
|
|
293
|
+
100.times do
|
|
294
|
+
expect(prng.generate_integer(maximum, *adjustments)).to be_an_integer_in(0...maximum)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
it 'returns different integers with adjustments when the upper bound is large' do
|
|
300
|
+
expect(Set[*(1..100).map { prng.generate_integer((2**80) - 5, '654321') }].size).to be 100
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
it_behaves_like 'an adjustable reader', prng,
|
|
304
|
+
->(reader, *args) { reader.generate_integer((2**64) - 3, *args) }
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
describe 'all read methods' do
|
|
308
|
+
it 'generate higher and lower values in sync' do
|
|
309
|
+
orders = reader_orders(prng)
|
|
310
|
+
expect(orders).to match(
|
|
311
|
+
int: orders[:hex], hex: orders[:hex], float: orders[:hex],
|
|
312
|
+
num: orders[:bytes], bytes: orders[:bytes]
|
|
313
|
+
)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
describe 'using predictable sequences' do
|
|
320
|
+
let(:prng) { described_class.new(1, "\x0" * 64) }
|
|
321
|
+
|
|
322
|
+
let(:zeroed_sequence) do
|
|
323
|
+
%w[da0cd77eb1c84458ddcc91e36b9dcb35
|
|
324
|
+
498ec24d1126440047eed396d836b5e1 0b90df55c5e7c1513b072367eae4a4ce
|
|
325
|
+
f12b5b54b5594e785bbb9a4ac50ccec8 d506bd4e201a00dc30499bd8e59a30d8
|
|
326
|
+
2557893cf995fe43bd00721fce6ab16a 41ee50244cdd02334bafc3e9d8f564d9
|
|
327
|
+
8156c7c7bcfd856cb9d1012243cfc662 1116f840e2aee924bd2c7d722c602635
|
|
328
|
+
96cf31967465b83f5ef3476c60afe20a f041f6df72aa2eab7394f08e83d52a0c
|
|
329
|
+
5ccbb30077f2433bd765ddc86840a880 6a4339fd5d445024048ea8f91a3e02fd
|
|
330
|
+
707e9499b9f0e9e906a0c0ddd0530803 553704d95703284df323f0aa4244cf81
|
|
331
|
+
cb1522ce0bdf2504fdd62df7416be73e 05c4932cb9d3a7c0675b0228826e661a
|
|
332
|
+
59606f23e34e726a7912dacfde533d97 188cf17dde6947264ce05f8274874ffa
|
|
333
|
+
24c184b56453891657953f557635b742 a5f969e50228b2ee0f38cc37c5033541
|
|
334
|
+
f1de0e5d149cc5a7f0e38c0ee501d7bc 5ca3a6beb0b810568be83ab2179eb550
|
|
335
|
+
756fb9c58277eb8c6092142224caecf4 0ed9505eadb3def60ec42051f0bf15ef]
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
let(:alternating_sequence) do
|
|
339
|
+
%w[
|
|
340
|
+
da0cd77eb1c84458ddcc91e36b9dcb35 de4b20a0560263090c6fe11ebba6256e
|
|
341
|
+
0b90df55c5e7c1513b072367eae4a4ce 0c92d534160cb268cb3282a9dd3f1efe
|
|
342
|
+
d506bd4e201a00dc30499bd8e59a30d8 089f799236a9cf64f22f1deafbe28214
|
|
343
|
+
41ee50244cdd02334bafc3e9d8f564d9 84477b9a3d51bb72868ccb85bea71cad
|
|
344
|
+
1116f840e2aee924bd2c7d722c602635 469e4bb2f38719c7f85d79c8900a8fa8
|
|
345
|
+
f041f6df72aa2eab7394f08e83d52a0c e21bf2508b8e1755ae70c74c0d36ec2e
|
|
346
|
+
6a4339fd5d445024048ea8f91a3e02fd a94a80c80c9edd95808a67775c2b42d4
|
|
347
|
+
553704d95703284df323f0aa4244cf81 edd6aea5bda7b2d9c494067c5cdbe410
|
|
348
|
+
05c4932cb9d3a7c0675b0228826e661a cf81635ca2fa910fd11cf45508dc658b
|
|
349
|
+
188cf17dde6947264ce05f8274874ffa bb4aaeb98e13400c1216c674a872ba93
|
|
350
|
+
a5f969e50228b2ee0f38cc37c5033541 1a795355f979575247b3fa9c92d97917
|
|
351
|
+
5ca3a6beb0b810568be83ab2179eb550 1a98419cca1dd1b370ce8a4a2ab721b7
|
|
352
|
+
0ed9505eadb3def60ec42051f0bf15ef
|
|
353
|
+
]
|
|
354
|
+
end
|
|
355
|
+
let(:adjusted_sequence) do
|
|
356
|
+
%w[
|
|
357
|
+
fed9de9a612f4157ebb49582ca557a50 a7adc2374a4df2ed67846ac09a3b6645
|
|
358
|
+
cbdf8bbbe6145751fe14004719915160 8cd84be95376f72918c305bdea43e36d
|
|
359
|
+
9e08e80ff40c942f0cf4d479b5378fa0 54d80c5330873f9733a0adef0197220f
|
|
360
|
+
2abe07bd85d2066a624dd3630e59730a 88b6a697fe74aeb8ec83845e103b7b63
|
|
361
|
+
9c6c9d613855f6535adb419cc564fd10 23f9b778b254035a0e4219423a52da77
|
|
362
|
+
4cd50c14ee17fa29c8b1f209432a36b3 2b5646b164d863de716f67adef653859
|
|
363
|
+
fd81d0bbbd2828ecdfcba0b486ef786c 08e6594fc277ff7fafbf37475ecadbf6
|
|
364
|
+
5e2333ecc800eb9a06e347924ed42e94 8a14e48013028b2c9f174a07ddd5ef49
|
|
365
|
+
e9a5c331f54b155f570c2cf2bcd37209 1ce84279195a5b23ffeb3063edbfab21
|
|
366
|
+
7422e19e58f2fcf20805f601266bf676 8df0226465b74d830360ea8609d181bf
|
|
367
|
+
3e5d5e8cf8ad032fb4005826bdf7bb94 65e3f9ab28c26bead6647c21bcc6245e
|
|
368
|
+
8ed5d8892d464ebeaa8dcbcef0968936 2f000bc1ab14d914196cc1d5055db189
|
|
369
|
+
8e9c4b8152f14e47ac31f25a80765ebf
|
|
370
|
+
]
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
it 'generates expected hex strings from simple zeroed pool' do
|
|
374
|
+
expect((0..24).map { prng.read_hex }).to eql zeroed_sequence
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
it 'generates expected hex strings from simple zeroed pool and an adjustment' do
|
|
378
|
+
expect((0..24).map { prng.read_hex('bananas') }).to eql adjusted_sequence
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
it 'generates expected hex strings from simple zeroed pool and alternating adjustments' do
|
|
382
|
+
results = (0..24).map { |index| index.odd? ? prng.read_hex('bananas', index.to_s) : prng.read_hex }
|
|
383
|
+
expect(results).to eql alternating_sequence
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|