$KCODE='UTF8' require 'json' class Fuzzer def initialize(n, freqs = {}) sum = freqs.inject(0.0) { |s, x| s + x.last } freqs.each_key { |x| freqs[x] /= sum } s = 0.0 freqs.each_key do |x| freqs[x] = s .. (s + t = freqs[x]) s += t end @freqs = freqs @n = n @alpha = (0..0x7f).to_a end def random_string s = '' 30.times { s << @alpha[rand(@alpha.size)] } s end def pick r = rand found = @freqs.find { |k, f| f.include? rand } found && found.first end def make_pick k = pick case when k == Hash, k == Array k.new when k == true, k == false, k == nil k when k == String random_string when k == Fixnum rand(2 ** 30) - 2 ** 29 when k == Bignum rand(2 ** 70) - 2 ** 69 end end def fuzz(current = nil) if @n > 0 case current when nil @n -= 1 current = fuzz [ Hash, Array ][rand(2)].new when Array while @n > 0 @n -= 1 current << case p = make_pick when Array, Hash fuzz(p) else p end end when Hash while @n > 0 @n -= 1 current[random_string] = case p = make_pick when Array, Hash fuzz(p) else p end end end end current end end class MyState < JSON.state WS = " \r\t\n" def initialize super( :indent => make_spaces, :space => make_spaces, :space_before => make_spaces, :object_nl => make_spaces, :array_nl => make_spaces ) end def make_spaces s = '' rand(1).times { s << WS[rand(WS.size)] } s end end n = (ARGV.shift || 500).to_i loop do fuzzer = Fuzzer.new(n, Hash => 25, Array => 25, String => 10, Fixnum => 10, Bignum => 10, nil => 5, true => 5, false => 5 ) o1 = fuzzer.fuzz json = JSON.generate o1, MyState.new if $DEBUG puts "-" * 80 puts json, json.size else puts json.size end begin o2 = JSON.parse(json, :max_nesting => false) rescue JSON::ParserError => e puts "Caught #{e.class}: #{e.message}\n#{e.backtrace * "\n"}" puts "o1 = #{o1.inspect}", "json = #{json}", "json_str = #{json.inspect}" puts "locals = #{local_variables.inspect}" exit end if o1 != o2 puts "mismatch", "o1 = #{o1.inspect}", "o2 = #{o2.inspect}", "json = #{json}", "json_str = #{json.inspect}" puts "locals = #{local_variables.inspect}" end end