Skip to content

Commit 0733eee

Browse files
authored
Merge pull request #1344 from puppetlabs/seeded_rand
Rewrite seeded_rand() as a Puppet 4.x function
2 parents 3378b3a + 2050371 commit 0733eee

File tree

3 files changed

+39
-82
lines changed

3 files changed

+39
-82
lines changed

lib/puppet/functions/seeded_rand.rb

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
# @summary
4+
# Generates a random whole number greater than or equal to 0 and less than max, using the value of seed for repeatable randomness.
5+
Puppet::Functions.create_function(:seeded_rand) do
6+
# @param max The maximum value.
7+
# @param seed The seed used for repeatable randomness.
8+
#
9+
# @return [Integer]
10+
# A random number greater than or equal to 0 and less than max
11+
dispatch :seeded_rand do
12+
param 'Integer[1]', :max
13+
param 'String', :seed
14+
end
15+
16+
def seeded_rand(max, seed)
17+
require 'digest/md5'
18+
19+
seed = Digest::MD5.hexdigest(seed).hex
20+
Puppet::Util.deterministic_rand_int(seed, max)
21+
end
22+
end

lib/puppet/parser/functions/seeded_rand.rb

-30
This file was deleted.

spec/functions/seeded_rand_spec.rb

+17-52
Original file line numberDiff line numberDiff line change
@@ -4,57 +4,22 @@
44

55
describe 'seeded_rand' do
66
it { is_expected.not_to eq(nil) }
7-
it { is_expected.to run.with_params.and_raise_error(ArgumentError, %r{wrong number of arguments}i) }
8-
it { is_expected.to run.with_params(1).and_raise_error(ArgumentError, %r{wrong number of arguments}i) }
9-
it { is_expected.to run.with_params(0, '').and_raise_error(ArgumentError, %r{first argument must be a positive integer}) }
10-
it { is_expected.to run.with_params(1.5, '').and_raise_error(ArgumentError, %r{first argument must be a positive integer}) }
11-
it { is_expected.to run.with_params(-10, '').and_raise_error(ArgumentError, %r{first argument must be a positive integer}) }
12-
it { is_expected.to run.with_params('-10', '').and_raise_error(ArgumentError, %r{first argument must be a positive integer}) }
13-
it { is_expected.to run.with_params('string', '').and_raise_error(ArgumentError, %r{first argument must be a positive integer}) }
14-
it { is_expected.to run.with_params([], '').and_raise_error(ArgumentError, %r{first argument must be a positive integer}) }
15-
it { is_expected.to run.with_params({}, '').and_raise_error(ArgumentError, %r{first argument must be a positive integer}) }
16-
it { is_expected.to run.with_params(1, 1).and_raise_error(ArgumentError, %r{second argument must be a string}) }
17-
it { is_expected.to run.with_params(1, []).and_raise_error(ArgumentError, %r{second argument must be a string}) }
18-
it { is_expected.to run.with_params(1, {}).and_raise_error(ArgumentError, %r{second argument must be a string}) }
19-
20-
it 'provides a random number strictly less than the given max' do
21-
expect(seeded_rand(3, 'seed')).to satisfy { |n| n.to_i < 3 } # rubocop:disable Lint/AmbiguousBlockAssociation : Cannot parenthesize without break code or violating other Rubocop rules
22-
end
23-
24-
it 'provides a random number greater or equal to zero' do
25-
expect(seeded_rand(3, 'seed')).to satisfy { |n| n.to_i >= 0 } # rubocop:disable Lint/AmbiguousBlockAssociation : Cannot parenthesize without break code or violating other Rubocop rules
26-
end
27-
28-
it "provides the same 'random' value on subsequent calls for the same host" do
29-
expect(seeded_rand(10, 'seed')).to eql(seeded_rand(10, 'seed'))
30-
end
31-
32-
it 'allows seed to control the random value on a single host' do
33-
first_random = seeded_rand(1000, 'seed1')
34-
second_different_random = seeded_rand(1000, 'seed2')
35-
36-
expect(first_random).not_to eql(second_different_random)
37-
end
38-
39-
it 'does not return different values for different hosts' do
40-
val1 = seeded_rand(1000, 'foo', host: 'first.host.com')
41-
val2 = seeded_rand(1000, 'foo', host: 'second.host.com')
42-
43-
expect(val1).to eql(val2)
44-
end
45-
46-
def seeded_rand(max, seed, args = {})
47-
host = args[:host] || '127.0.0.1'
48-
49-
# workaround not being able to use let(:facts) because some tests need
50-
# multiple different hostnames in one context
51-
allow(scope).to receive(:lookupvar).with('::fqdn', {}).and_return(host)
52-
53-
scope.function_seeded_rand([max, seed])
54-
end
55-
56-
context 'with UTF8 and double byte characters' do
57-
it { is_expected.to run.with_params(1000, 'ǿňè') }
58-
it { is_expected.to run.with_params(1000, '文字列') }
7+
it { is_expected.to run.with_params.and_raise_error(ArgumentError, %r{'seeded_rand' expects 2 arguments, got none}i) }
8+
it { is_expected.to run.with_params(1).and_raise_error(ArgumentError, %r{'seeded_rand' expects 2 arguments, got 1}i) }
9+
it { is_expected.to run.with_params(0, '').and_raise_error(ArgumentError, %r{parameter 'max' expects an Integer\[1\] value, got Integer\[0, 0\]}) }
10+
it { is_expected.to run.with_params(1.5, '').and_raise_error(ArgumentError, %r{parameter 'max' expects an Integer value, got Float}) }
11+
it { is_expected.to run.with_params(-10, '').and_raise_error(ArgumentError, %r{parameter 'max' expects an Integer\[1\] value, got Integer\[-10, -10\]}) }
12+
it { is_expected.to run.with_params('string', '').and_raise_error(ArgumentError, %r{parameter 'max' expects an Integer value, got String}) }
13+
it { is_expected.to run.with_params([], '').and_raise_error(ArgumentError, %r{parameter 'max' expects an Integer value, got Array}) }
14+
it { is_expected.to run.with_params({}, '').and_raise_error(ArgumentError, %r{parameter 'max' expects an Integer value, got Hash}) }
15+
it { is_expected.to run.with_params(1, 1).and_raise_error(ArgumentError, %r{parameter 'seed' expects a String value, got Integer}) }
16+
it { is_expected.to run.with_params(1, []).and_raise_error(ArgumentError, %r{parameter 'seed' expects a String value, got Array}) }
17+
it { is_expected.to run.with_params(1, {}).and_raise_error(ArgumentError, %r{parameter 'seed' expects a String value, got Hash}) }
18+
19+
context 'produce predictible and reproducible results' do
20+
it { is_expected.to run.with_params(20, 'foo').and_return(1) }
21+
it { is_expected.to run.with_params(100, 'bar').and_return(35) }
22+
it { is_expected.to run.with_params(1000, 'ǿňè').and_return(247) }
23+
it { is_expected.to run.with_params(1000, '文字列').and_return(67) }
5924
end
6025
end

0 commit comments

Comments
 (0)