Module: RSpec::Junklet::Junk
- Defined in:
- lib/rspec/junklet/junk.rb
Instance Method Summary collapse
Instance Method Details
#junk(*args) ⇒ Object
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 |
# File 'lib/rspec/junklet/junk.rb', line 6 def junk(*args) # TODO: It's long past time to extract this.... # args.first can be # - an integer indicating the size of the hex string to return # - a symbol denoting the base type, e.g. :int # - an array to sample from # - a range or Enumerable to sample from. WARNING: will call # .to_a on it first, which might be expensive # - a generator Proc which, when called, will generate the # value to use. # # args.rest is a hash of options: # - sequence: Proc or Array of values to choose from. # - exclude: value, array of values, or proc to exclude. If a # Proc is given, it takes the value generated and returns # true if the value should be excluded. # # - for int: # - min: minimum number to return. Default: 0 # - max: upper limit of range # - exclude: number, array of numbers, or proc to # exclude. If Proc is provided, tests the number against # the Proc an excludes it if the Proc returns true. This # is implemented except for the proc. # FIXME: Would be nice to be able to say, e.g. `junk exclude: bob` but # currently this breaks because {exclude: bob} is a Hash which is # an Enumerable which means it's a valid generator. I don't want to # just disallow hashes as generators here. What's a good workaround # for when I really do just want junk with either a format or an # exclusion? `junk :junk, exclude: bob` looks bad. `junk _, # exclude: bob` is also pretty bad. # # Hmm, I'm tempted to say "the one enumerable you CAN'T have is a # Hash, so it can be the options hash; if you DO want to pass in a # hash, you must cast it to an array or yield it from a Proc # instead". Given that the hash case is weird and rare enough, I # think this is acceptable. Oh, if only I didn't have to maintain # backwards compatibility with older Rubies, I could just make # these keyword args. Sigh. # FIXME: This whole darn method is outta control, and for the record none # of the cool excluders and formatters work with the # SecureRandom.hex default. Barf. # FIXME: Raise Argument error unless *args.size is 0-2 # FIXME: If arg 1 is a hash, it's the options hash, raise # ArgumentError unless args.size == 1 # FIXME: If arg 2 present, Raise Argument error unless it's a # hash. # FIXME: Figure out what our valid options are and parse them; # raise errors if present. junk_types = [Symbol, Array, Enumerable, Proc] if args.size > 0 && junk_types.any? {|klass| args.first.is_a?(klass) } type = args.shift opts = args.last || {} excluder = if opts[:exclude] if opts[:exclude].is_a?(Proc) opts[:exclude] else ->(x) { Array(opts[:exclude]).include?(x) } end else ->(x) { false } end formatter = case opts[:format] when :string ->(x) { x.to_s } when :int ->(x) { x.to_i } when Class raise "Formatter class must implement #format method" unless opts[:format].new(0).respond_to? :format ->(x) { opts[:format].new(x).format } when String ->(x) { sprintf(opts[:format], x) } when Proc opts[:format] else ->(x) { x } end # TODO: Refactor me. Seriously, this is a functional # programming version of the strategy pattern. Wouldn't it # be neat if we had some kind of object-oriented language # available here? case type when :int # min,max cooperate with size to further constrain it. So # size: 2, min: 30 would be min 30, max 99. if opts[:size] sized_min = 10**(opts[:size]-1) sized_max = 10**opts[:size]-1 end explicit_min = opts[:min] || 0 explicit_max = (opts[:max] || 2**62-2) + 1 if sized_min min = [sized_min, explicit_min].max max = [sized_max, explicit_max].min else min = sized_min || explicit_min max = sized_max || explicit_max end min,max = max,min if min>max generator = -> { rand(max-min) + min } when :bool generator = -> { [true, false].sample } when Array, Enumerable generator = -> { type.to_a.sample } when Proc generator = type else raise "Unrecognized junk type: '#{type}'" end begin val = formatter.call(generator.call) end while excluder.call(val) val else size = args.first.is_a?(Numeric) ? args.first : 32 # hex returns size*2 digits, because it returns a 0..255 byte # as a hex pair. But when we want junt, we want *bytes* of # junk. Get (size+1)/2 chars, which will be correct for even # sizes and 1 char too many for odds, so trim off with # [0...size] (note three .'s to trim off final char) SecureRandom.hex((size+1)/2)[0...size] end end |