rantly-3.0.0/0000755000004100000410000000000014717634601013064 5ustar www-datawww-datarantly-3.0.0/VERSION.yml0000644000004100000410000000005214717634601014731 0ustar www-datawww-data--- :build: :major: 3 :minor: 0 :patch: 0 rantly-3.0.0/.document0000644000004100000410000000007414717634601014704 0ustar www-datawww-dataREADME.rdoc lib/**/*.rb bin/* features/**/*.feature LICENSE rantly-3.0.0/lib/0000755000004100000410000000000014717634601013632 5ustar www-datawww-datarantly-3.0.0/lib/rantly/0000755000004100000410000000000014717634601015143 5ustar www-datawww-datarantly-3.0.0/lib/rantly/generator.rb0000644000004100000410000001300514717634601017455 0ustar www-datawww-dataclass Rantly class << self attr_writer :default_size def singleton @singleton ||= Rantly.new @singleton end def default_size @default_size || 6 end def each(n, limit = 10, &block) gen.each(n, limit, &block) end def map(n, limit = 10, &block) gen.map(n, limit, &block) end def value(limit = 10, &block) gen.value(limit, &block) end def gen singleton end end class GuardFailure < RuntimeError end class TooManyTries < RuntimeError def initialize(limit, nfailed) @limit = limit @nfailed = nfailed end def tries @nfailed end attr_reader :limit end # limit attempts to 10 times of how many things we want to generate def each(n, limit = 10, &block) generate(n, limit, block) end def map(n, limit = 10, &block) acc = [] generate(n, limit, block) do |val| acc << val end acc end def value(limit = 10, &block) generate(1, limit, block) do |val| return val end end def generate(n, limit_arg, gen_block, &handler) limit = n * limit_arg nfailed = 0 nsuccess = 0 while nsuccess < n raise TooManyTries.new(limit_arg * n, nfailed) if limit.zero? begin val = instance_eval(&gen_block) rescue GuardFailure nfailed += 1 limit -= 1 next end nsuccess += 1 limit -= 1 yield(val) if handler end end attr_accessor :classifiers def initialize reset end def reset @size = nil @classifiers = Hash.new(0) end def classify(classifier) @classifiers[classifier] += 1 end def guard(test) return true if test raise GuardFailure end def size @size || Rantly.default_size end def sized(n, &block) raise 'size needs to be greater than zero' if n.negative? old_size = @size @size = n r = instance_eval(&block) @size = old_size r end # wanna avoid going into Bignum when calling range with these. INTEGER_MAX = (2**(0.size * 8 - 2) - 1) / 2 INTEGER_MIN = -INTEGER_MAX def integer(limit = nil) case limit when Range hi = limit.end lo = limit.begin when Integer raise 'n should be greater than zero' if limit.negative? hi = limit lo = -limit else hi = INTEGER_MAX lo = INTEGER_MIN end range(lo, hi) end def positive_integer range(0) end def float(distribution = nil, params = {}) case distribution when :normal params[:center] ||= 0 params[:scale] ||= 1 raise 'The distribution scale should be greater than zero' if params[:scale].negative? # Sum of 6 draws from a uniform distribution give as a draw of a normal # distribution centered in 3 (central limit theorem). ([rand, rand, rand, rand, rand, rand].sum - 3) * params[:scale] + params[:center] else rand end end def range(lo = INTEGER_MIN, hi = INTEGER_MAX) rand(lo..hi) end def call(gen, *args) case gen when Symbol send(gen, *args) when Array raise 'empty array' if gen.empty? send(gen[0], *gen[1..-1]) when Proc instance_eval(&gen) else raise "don't know how to call type: #{gen}" end end def branch(*gens) call(choose(*gens)) end def choose(*vals) vals[range(0, vals.length - 1)] if vals.length.positive? end def literal(value) value end def boolean range(0, 1).zero? end def freq(*pairs) pairs = pairs.map do |pair| case pair when Symbol, String, Proc [1, pair] when Array if pair.first.is_a?(Integer) pair else [1] + pair end end end total = pairs.inject(0) { |sum, p| sum + p.first } raise("Illegal frequency:#{pairs.inspect}") if total.zero? pos = range(1, total) pairs.each do |p| weight, gen, *args = p return call(gen, *args) if pos <= p[0] pos -= weight end end def array(n = size, &block) n.times.map { instance_eval(&block) } end def dict(n = size, &block) h = {} each(n) do k, v = instance_eval(&block) h[k] = v if guard(!h.key?(k)) end h end module Chars class << self ASCII = (0..127).to_a.each_with_object('') { |i, obj| obj << i } def of(regexp) ASCII.scan(regexp).to_a.map! { |char| char[0].ord } end end ALNUM = Chars.of(/[[:alnum:]]/) ALPHA = Chars.of(/[[:alpha:]]/) BLANK = Chars.of(/[[:blank:]]/) CNTRL = Chars.of(/[[:cntrl:]]/) DIGIT = Chars.of(/[[:digit:]]/) GRAPH = Chars.of(/[[:graph:]]/) LOWER = Chars.of(/[[:lower:]]/) PRINT = Chars.of(/[[:print:]]/) PUNCT = Chars.of(/[[:punct:]]/) SPACE = Chars.of(/[[:space:]]/) UPPER = Chars.of(/[[:upper:]]/) XDIGIT = Chars.of(/[[:xdigit:]]/) ASCII = Chars.of(/./) CLASSES = { alnum: ALNUM, alpha: ALPHA, blank: BLANK, cntrl: CNTRL, digit: DIGIT, graph: GRAPH, lower: LOWER, print: PRINT, punct: PUNCT, space: SPACE, upper: UPPER, xdigit: XDIGIT, ascii: ASCII }.freeze end def string(char_class = :print) chars = case char_class when Regexp Chars.of(char_class) when Symbol Chars::CLASSES[char_class] end raise 'bad arg' unless chars char_strings = chars.map(&:chr) str = Array.new(size) size.times { |i| str[i] = char_strings.sample } str.join end end rantly-3.0.0/lib/rantly/shrinks.rb0000644000004100000410000000526014717634601017154 0ustar www-datawww-data# Integer : shrink to zero class Integer def shrink if self > 8 self / 2 elsif self > 0 self - 1 elsif self < -8 (self + 1) / 2 elsif self < 0 self + 1 else 0 end end def retry? false end def shrinkable? self != 0 end end # String : shrink to "" class String def shrink shrunk = dup unless empty? idx = Random.rand(size) shrunk[idx] = '' end shrunk end def retry? false end def shrinkable? self != '' end end # Array where elements can be shrunk but not removed class Tuple def initialize(a) @array = a @position = a.size - 1 end def [](i) @array[i] end def []=(i, value) @array[i] = value end def length @array.length end def size length end def to_s @array.to_s.insert(1, 'T ') end def inspect to_s end def each(&block) @array.each(&block) end attr_reader :array def shrink shrunk = @array.dup while @position >= 0 e = @array.at(@position) break if e.respond_to?(:shrinkable?) && e.shrinkable? @position -= 1 end if @position >= 0 shrunk[@position] = e.shrink @position -= 1 end Tuple.new(shrunk) end def retry? @position >= 0 end def shrinkable? @array.any? { |e| e.respond_to?(:shrinkable?) && e.shrinkable? } end end # Array where the elements that can't be shrunk are removed class Deflating def initialize(a) @array = a @position = a.size - 1 end def [](i) @array[i] end def []=(i, value) @array[i] = value end def length @array.length end def size length end def to_s @array.to_s.insert(1, 'D ') end def inspect to_s end def each(&block) @array.each(&block) end attr_reader :array def shrink shrunk = @array.dup if @position >= 0 e = @array.at(@position) if e.respond_to?(:shrinkable?) && e.shrinkable? shrunk[@position] = e.shrink else shrunk.delete_at(@position) end @position -= 1 end Deflating.new(shrunk) end def retry? @position >= 0 end def shrinkable? !@array.empty? end end class Hash def shrink if any? { |_, v| v.respond_to?(:shrinkable?) && v.shrinkable? } key, = detect { |_, v| v.respond_to?(:shrinkable?) && v.shrinkable? } clone = dup clone[key] = clone[key].shrink clone elsif !empty? key = keys.first h2 = dup h2.delete(key) h2 else self end end def shrinkable? any? { |_, v| v.respond_to?(:shrinkable?) && v.shrinkable? } || !empty? end def retry? false end end rantly-3.0.0/lib/rantly/property.rb0000644000004100000410000000442514717634601017361 0ustar www-datawww-datarequire 'rantly' require 'pp' require 'stringio' class Rantly::Property attr_reader :failed_data, :shrunk_failed_data VERBOSITY = ENV.fetch('RANTLY_VERBOSE', 1).to_i RANTLY_COUNT = ENV.fetch('RANTLY_COUNT', 100).to_i def io @io ||= if VERBOSITY >= 1 $stdout else StringIO.new end end def pretty_print(object) PP.pp(object, io) end def initialize(property) @property = property end def check(n = RANTLY_COUNT, limit = 10, &assertion) i = 0 test_data = nil begin Rantly.singleton.generate(n, limit, @property) do |val| test_data = val yield(val) if assertion io.puts '' if (i % 100).zero? io.print '.' if (i % 10).zero? i += 1 end io.puts io.puts "SUCCESS - #{i} successful tests" rescue Rantly::TooManyTries => e io.puts io.puts "FAILURE - #{i} successful tests, too many tries: #{e.tries}" raise e.exception("#{i} successful tests, too many tries: #{e.tries} (limit: #{e.limit})") rescue Exception => e io.puts io.puts "FAILURE - #{i} successful tests, failed on:" pretty_print test_data @failed_data = test_data if @failed_data.respond_to?(:shrink) @shrunk_failed_data, @depth = shrinkify(assertion, @failed_data) io.puts "Minimal failed data (depth #{@depth}) is:" pretty_print @shrunk_failed_data end raise e.exception("#{i} successful tests, failed on:\n#{test_data}\n\n#{e}\n") end end # Explore the failures tree def shrinkify(assertion, data, depth = 0, iteration = 0) min_data = data max_depth = depth if data.shrinkable? while iteration < 1024 # We assume that data.shrink is non-destructive shrunk_data = data.shrink begin assertion.call(shrunk_data) rescue Exception # If the assertion was verified, recursively shrink failure case branch_data, branch_depth, iteration = shrinkify(assertion, shrunk_data, depth + 1, iteration + 1) if branch_depth > max_depth min_data = branch_data max_depth = branch_depth end end break unless data.retry? end end [min_data, max_depth, iteration] end end rantly-3.0.0/lib/rantly/rspec_extensions.rb0000644000004100000410000000022414717634601021061 0ustar www-datawww-datarequire 'rspec/core' require 'rantly/property' class RSpec::Core::ExampleGroup def property_of(&block) Rantly::Property.new(block) end end rantly-3.0.0/lib/rantly/rspec.rb0000644000004100000410000000004214717634601016600 0ustar www-datawww-datarequire 'rantly/rspec_extensions' rantly-3.0.0/lib/rantly/spec.rb0000644000004100000410000000025714717634601016426 0ustar www-datawww-datarequire 'rantly' module Rantly::Check def check(n = 100, &block) Rantly.gen.each(n, &block) end def sample(n = 100, &block) Rantly.gen.map(n, &block) end end rantly-3.0.0/lib/rantly/data.rb0000644000004100000410000000033314717634601016400 0ustar www-datawww-datamodule Rantly::Data def email "#{string(:alnum)}@#{string(:alnum)}.#{sized(3) { string(:alpha) }}".downcase end def password sized(8) { string(:alnum) } end end class Rantly include Rantly::Data end rantly-3.0.0/lib/rantly/minitest.rb0000644000004100000410000000004514717634601017323 0ustar www-datawww-datarequire 'rantly/minitest_extensions' rantly-3.0.0/lib/rantly/silly.rb0000644000004100000410000000627514717634601016636 0ustar www-datawww-datarequire 'rantly' module Rantly::Silly class << self def love_letter(n) Rantly.new.extend(Rantly::Silly::Love).value { letter(n) } end end end module Rantly::Silly::Love def letter(n = 3) body = array(n) { paragraph }.join "\n\n" <<~EOS #{address}: #{body} #{sign} #{post_script} EOS end def address "my #{extremifier} #{pedestal_label}" end def extremifier choose 'most', 'ultimate', 'unbelievable', 'incredible', 'burning' end def pedestal_label choose 'beloved', 'desire', 'dove', 'virgin goddess', 'existential solution', 'lighthouse', 'beacon', 'holy mother', 'queen', 'mistress' end def double_plus_good choose 'holy', 'shiny', 'glittering', 'joyous', 'delicious' end def how_i_feel choose 'my heart aches', 'my spine pines', 'my spirit wanders and wonders', 'my soul is awed', 'my loin burns' end def paragraph array(range(2, 4)) { sentence }.join ' ' end def sentence freq \ proc { "when #{how_i_feel}, my #{pedestal_label}, i feel the need to #{stalk_action},"\ "but this is not because #{how_i_feel}, but rather a symptom of my being your #{whoami}." }, proc { "because you are my #{pedestal_label}, and i am your #{whoami}, no, rather your #{whoami}, #{fragment}." }, proc { "do not think that saying '#{how_i_feel}' suffices to show the depth of how #{how_i_feel}, because more than that, #{fantasy}" }, proc { "as a #{whoami}, that #{how_i_feel} is never quite enough for you, my #{double_plus_good} #{pedestal_label}." } end def fragment fun = fantasy choose "i hope to god #{fun}", "i believe #{fun}", "i will that #{fun}" end def caused_by; end def whoami "#{extremifier} #{humbleizer} #{groveler}" end def sign "your #{whoami}" end def humbleizer choose 'undeserving', 'insignificant', 'unremarkable', 'fearful', 'menial' end def groveler choose 'slave', 'servant', 'captive', 'lapdog' end def post_script "ps: #{i_am_stalking_you}, and hope that #{fantasy}" end def i_am_stalking_you "every #{time_duration} i #{stalk_action}" end def fantasy freq \ proc { make = choose 'raise', 'nurture', 'bring into the world' babies = choose 'brood of babies', "#{double_plus_good} angels" good = double_plus_good effect = choose "the world becomes all the more #{good}", "we may at the end of our lives rest in #{good} peace.", "you, my #{pedestal_label}, would continue to live." "we would #{make} #{babies}, so #{effect}." }, proc { do_thing = choose('kiss', 'hug', 'read poetry to each other', 'massage', "whisper empty nothings into each others' ears", 'be with each other, and oblivious to the entire world') affect = choose 'joy', 'mindfulness', 'calm', 'sanctity' "we would #{do_thing} with #{double_plus_good} #{affect}" } end def stalk_action choose 'think of you', 'dream of us together', 'look at your picture and sigh' end def time_duration choose 'once in a while', 'night', 'day', 'hour', 'minute' end end rantly-3.0.0/lib/rantly/minitest_extensions.rb0000644000004100000410000000051414717634601021603 0ustar www-datawww-datarequire 'minitest' require 'rantly/property' require "minitest/unit" unless defined?(MiniTest) test_class = if defined?(MiniTest::Test) MiniTest::Test else MiniTest::Unit::TestCase end test_class.class_eval do def property_of(&blk) Rantly::Property.new(blk) end end rantly-3.0.0/lib/rantly/testunit_extensions.rb0000644000004100000410000000022114717634601021621 0ustar www-datawww-datarequire 'test/unit' require 'rantly/property' module Test::Unit::Assertions def property_of(&block) Rantly::Property.new(block) end end rantly-3.0.0/lib/rantly.rb0000644000004100000410000000043014717634601015465 0ustar www-datawww-data$LOAD_PATH.unshift(File.dirname(__FILE__)) unless $LOAD_PATH.include?(File.dirname(__FILE__)) || $LOAD_PATH.include?(__dir__) class Rantly end require 'rantly/generator' def Rantly(n = 1, &block) if n > 1 Rantly.map(n, &block) else Rantly.value(&block) end end rantly-3.0.0/test/0000755000004100000410000000000014717634601014043 5ustar www-datawww-datarantly-3.0.0/test/shrinks_test.rb0000644000004100000410000000504514717634601017114 0ustar www-datawww-datarequire 'test_helper' require 'rantly/shrinks' require 'rantly/minitest_extensions' module RantlyTest end module RantlyTest::Shrinkers end describe Integer do it 'not be able to shrink 0 integer' do assert !0.shrinkable? end it 'shrink positive integers to something less than itself' do assert(3.shrink < 3) assert(2.shrink < 2) assert_equal(0, 1.shrink) end it 'shrink negative integers to something larger than itself' do assert(-3.shrink > -3) assert(-2.shrink > -2) assert_equal(0, -1.shrink) end it 'shrink 0 to itself' do # hmm. should this be undefined? assert_equal 0.shrink, 0 end end describe String do it 'not be able to shrink empty string' do assert !''.shrinkable? end it 'shrink a string one char shorter' do property_of do sized(10) { string } end.check do |str| assert_equal 9, str.shrink.length end end end describe Tuple do it 'not be able to shrink empty tuple' do assert !Tuple.new([]).shrinkable? end it 'shrink tuple by trying to shrink the last shrinkable element available' do assert_equal [1, 0], Tuple.new([1, 1]).shrink.array assert_equal [1, 0, 0], Tuple.new([1, 1, 0]).shrink.array end it 'do not remove element from array when no element is shrinkable' do property_of do n = integer(1..10) a = Tuple.new(Array.new(n, 0)) [n, a] end.check do |n, a| assert_equal n, a.shrink.length end end end describe Hash do it 'not be able to shrink empty hash' do assert !{}.shrinkable? end it 'shrink a value if one of the values is shrinkable' do assert_equal({ foo: 0, bar: 0 }, { foo: 1, bar: 0 }.shrink) assert_equal({ foo: 0, bar: 0 }, { foo: 0, bar: 1 }.shrink) end it 'shrink by deleting an element in it if none of the values is shrinkable' do assert_equal({}, { foo: 0 }.shrink) end end describe 'Shrinker Test' do it 'shrink data to smallest value that fails assertion' do print "\n### TESTING A FAILING CASE, do not get scared" # We try to generate an array of 10 elements, filled with ones. # The property we try to test is that non of the element is # larger than 1, and the array's length is less than 4. test = property_of do a = Deflating.new(Array.new(10, 1)) i = Random.rand(a.length) a[i] = 1 a end assert_raises MiniTest::Assertion do test.check do |a| assert(a.array.none?(&:positive?) && a.length < 4, 'contains 1') end end assert_equal [1], test.shrunk_failed_data.array end end rantly-3.0.0/test/test_helper.rb0000644000004100000410000000051214717634601016704 0ustar www-datawww-data# Require simplecov and coveralls before rantly application code. require 'simplecov' SimpleCov.start begin # Coveralls is marked as an _optional_ dependency, so don't # throw a fit if it's not there. require 'coveralls' Coveralls.wear! rescue LoadError end # Require rantly. require 'minitest/autorun' require 'rantly' rantly-3.0.0/test/rantly_test.rb0000644000004100000410000000661614717634601016751 0ustar www-datawww-datarequire 'test_helper' require 'rantly/minitest_extensions' module RantlyTest end describe Rantly::Property do before do Rantly.gen.reset end it 'fail test generation' do print "\n### TESTING A FAILING CASE, do not get scared" assert_raises(Rantly::TooManyTries) do property_of { guard range(0, 1).negative? }.check end end # call it 'call Symbol as method call (no arg)' do property_of { call(:integer) }.check { |i| i.is_a?(Integer) } end it 'call Symbol as method call (with arg)' do property_of do n = range(0, 100) [n, call(:integer, n)] end.check do |(n, i)| assert n.abs >= i.abs end end it 'call Array by calling first element as method, the rest as args' do assert_raises(RuntimeError) do Rantly.gen.value do call [] end end property_of do i = integer [i, call(choose([:literal, i], [:range, i, i]))] end.check do |(a, b)| assert_equal a, b end end it 'call Proc with generator.instance_eval' do property_of do call proc { true } end.check do |o| assert_equal true, o end property_of do i0 = range(0, 100) i1 = call proc { range(i0 + 1, i0 + 100) } [i0, i1] end.check do |(i0, i1)| assert i0.is_a?(Integer) && i1.is_a?(Integer) assert i1 > i0 assert i1 <= (i0 + 100) end end it 'raise if calling on any other value' do assert_raises(RuntimeError) do Rantly.gen.call 0 end end # branch it 'branch by Rantly#calling one of the args' do property_of do branch :integer, :integer, :integer end.check do |o| assert o.is_a?(Integer) end property_of do sized(10) { branch :integer, :string } end.check do |o| assert o.is_a?(Integer) || o.is_a?(String) end end # choose it 'choose a value from args ' do property_of do choose end.check do |o| assert_nil o end property_of do choose 1 end.check do |o| assert_equal 1, o end property_of do choose 1, 2 end.check do |o| assert [1, 2].include? o end property_of do arr = sized(10) { array { integer } } choose(*arr) end.check do |o| assert o.is_a?(Integer) end property_of do # array of array of ints arr = sized(10) { array { array { integer } } } # choose an array from an array of arrays of ints choose(*arr) end.check do |arr| assert arr.is_a?(Array) assert arr.all? { |o| o.is_a?(Integer) } end end # freq it 'not pick an element with 0 frequency' do property_of do sized(10) do array { freq([0, :string], [1, :integer]) } end end.check do |arr| assert arr.all? { |o| o.is_a?(Integer) } end end it 'handle degenerate freq pairs' do assert_raises(RuntimeError) do Rantly.gen.value do freq end end property_of do i = integer [i, freq([:literal, i])] end.check do |(a, b)| assert_equal a, b end end end # TODO: Determine type of tests required here. # check we generate the right kind of data. ## doesn't check for distribution class RantlyTest::Generator < Minitest::Test def setup Rantly.gen.reset end end # TODO: check that distributions of different methods look roughly correct. class RantlyTest::Distribution < Minitest::Test end rantly-3.0.0/Rakefile0000644000004100000410000000156214717634601014535 0ustar www-datawww-datarequire 'rake' task default: %i[test rubocop] require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' test.pattern = 'test/**/*_test.rb' test.verbose = true end require 'rubocop/rake_task' desc 'Run RuboCop' RuboCop::RakeTask.new(:rubocop) do |task| task.options = ['--display-cop-names'] end RuboCop::RakeTask.new('rubocop:auto_gen_config') do |task| task.options = ['--display-cop-names', '--auto-gen-config', '--auto-gen-only-exclude'] end require 'rdoc/task' Rake::RDocTask.new do |rdoc| require 'yaml' if File.exist?('VERSION.yml') config = YAML.load(File.read('VERSION.yml')) version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}" else version = '' end rdoc.rdoc_dir = 'rdoc' rdoc.title = "rant #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end rantly-3.0.0/Gemfile0000644000004100000410000000037714717634601014366 0ustar www-datawww-datasource 'https://rubygems.org' group :development, :test do gem 'coveralls', '>= 0', require: false gem 'minitest', '~> 5.10.0' gem 'rake', '~> 12.3.3' gem 'simplecov', '>= 0' gem 'rubocop' gem 'rubocop-performance', require: false end rantly-3.0.0/LICENSE0000644000004100000410000000206614717634601014075 0ustar www-datawww-data The MIT License (MIT) Copyright (c) 2009 Howard Yeh Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rantly-3.0.0/rantly.gemspec0000644000004100000410000000235314717634601015745 0ustar www-datawww-dataGem::Specification.new do |s| s.name = 'rantly' s.summary = 'Ruby Imperative Random Data Generator and Quickcheck' s.homepage = 'https://github.com/rantly-rb/rantly' s.version = '3.0.0' s.license = 'MIT' s.require_paths = ['lib'] s.authors = ['Ana María Martínez Gómez', 'Howard Yeh', 'Anthony Bargnesi', 'Eric Bischoff'] s.email = ['anamma06@gmail.com', 'hayeah@gmail.com', 'abargnesi@gmail.com', 'ebischoff@nerim.net'] s.extra_rdoc_files = [ 'LICENSE', 'README.md', 'CHANGELOG.md' ] s.files = [ '.document', '.travis.yml', 'Gemfile', 'LICENSE', 'README.md', 'CHANGELOG.md', 'Rakefile', 'VERSION.yml', 'lib/rantly.rb', 'lib/rantly/data.rb', 'lib/rantly/generator.rb', 'lib/rantly/minitest.rb', 'lib/rantly/minitest_extensions.rb', 'lib/rantly/property.rb', 'lib/rantly/rspec.rb', 'lib/rantly/rspec_extensions.rb', 'lib/rantly/shrinks.rb', 'lib/rantly/silly.rb', 'lib/rantly/spec.rb', 'lib/rantly/testunit_extensions.rb', 'rantly.gemspec', 'test/rantly_test.rb', 'test/shrinks_test.rb', 'test/test_helper.rb' ] s.required_ruby_version = '>= 3.3.0' end rantly-3.0.0/.travis.yml0000644000004100000410000000033714717634601015200 0ustar www-datawww-datalanguage: ruby sudo: false cache: bundler after_success: - coveralls rvm: - 2.4.0 - 2.5.1 - ruby-head - jruby-9.2.0.0 - jruby-head matrix: allow_failures: - rvm: jruby-head - rvm: ruby-head rantly-3.0.0/README.md0000644000004100000410000002522214717634601014346 0ustar www-datawww-data[![Gem version](https://badge.fury.io/rb/rantly.svg)](https://badge.fury.io/rb/rantly) [![Build Status](https://travis-ci.org/rantly-rb/rantly.svg?branch=master)](https://travis-ci.org/rantly-rb/rantly) [![Coverage Status](https://coveralls.io/repos/github/rantly-rb/rantly/badge.svg?branch=master)](https://coveralls.io/github/rantly-rb/rantly?branch=master) # Imperative Random Data Generator and Quickcheck You can use Rantly to generate random test data, and use its Test::Unit extension for property-based testing. Rantly is basically a recursive descent interpreter, each of its method returns a random value of some type (string, integer, float, etc.). Its implementation has no alien mathematics inside. Completely side-effect-free-free. ![img](/logo/Rantly.png) # Install Rantly requires Ruby 3.2 or higher. To install Rantly add it to your Gemfile or run: ```ruby $ gem install rantly ``` You can try it in the console by running: ```ruby $ irb -rrantly > Rantly { [integer,float] } # same as Rantly.value { integer } => [20991307, 0.025756845811823] > Rantly { [integer,float]} => [-376856492, 0.452245765751706] > Rantly(5) { integer } # same as Rantly.map(5) { integer } => [-1843396915550491870, -1683855015308353854, -2291347782549033959, -951461511269053584, 483265231542292652] ``` # Data Generation ## Getting Random Data Values ```ruby Rantly#map(n,limit=10,&block) call the generator n times, and collect values Rantly#each(n,limit=10,&block) call a random block n times Rantly#value(limit=10,&block) call a random block once, and get its value. ``` To collect an array of random data, ```ruby # we want 5 random integers > Rantly(5) { integer } => [-380638946, -29645239, 344840868, 308052180, -154360970] ``` To iterate over random data, ```ruby > Rantly.each(5) { puts integer } 296971291 504994512 -402790444 113152364 502842783 => nil ``` To get one value of random data, ```ruby > Rantly { integer } => 278101042 ``` The optional argument `limit` is used with generator guard. By default, if you want to generate n items, the generator tries at most n * 10 times. This almost always succeeds, ```ruby > Rantly(5) { i = integer; guard i > 0; i } => [511765059, 250554234, 305947804, 127809156, 285960387] ``` This always fails, ```ruby > Rantly(10) { guard integer.is_a?(Float) } Rantly::TooManyTries: Exceed gen limit 100: 101 failed guards) ``` ## Random Generating Methods The API is similiar to QuickCheck, but not exactly the same. In particular `choose` picks a random element from an array, and `range` picks a integer from an interval. ## Simple Randomness ```ruby Rantly#integer(n=nil) random positive or negative integer. Fixnum only. Rantly#range(lo,hi) random integer between lo and hi. Rantly#float random float Rantly#boolean true or false Rantly#literal(value) No-op. returns value. Rantly#choose(*vals) Pick one value from among vals. ``` ## Meta Randomness A rant generator is just a mini interpreter. It's often useful to go meta, ```ruby Rantly#call(gen) If gen is a Symbol, just do a method call with send. If gen is an Array, the first element of the array is the method name, the rest are args. If gen is a Proc, instance_eval it with the generator. ``` ```ruby > Rantly { call(:integer) } => -240998958 ``` ```ruby > Rantly { call([:range,0,10]) } => 2 ``` ```ruby > Rantly { call(Proc.new { [integer] })} => [522807620] ``` The `call` method is useful to implement other abstractions (See next subsection). ```ruby Rantly#branch(*args) Pick a random arg among args, and Rantly#call it. ``` 50-50 chance getting an integer or float, ```ruby > Rantly { branch :integer, :float } => 0.0489446702931332 > Rantly { branch :integer, :float } => 494934533 ``` ## Frequencies ```ruby Rantly#freq(*pairs) Takes a list of 2-tuples, the first of which is the weight, and the second a Rantly#callable value, and returns a random value picked from the pairs. Follows the distribution pattern specified by the weights. ``` Twice as likely to get a float than integer. Never gets a ranged integer. ```ruby > Rantly { freq [1,:integer], [2,:float], [0,:range,0,10] } ``` If the "pair" is not an array, but just a symbol, `freq` assumes that the weight is 1. ```ruby # 50-50 between integer and float > Rantly { freq :integer, :float } ``` If a "pair" is an Array, but the first element is not an Integer, `freq` assumes that it's a Rantly method-call with arguments, and the weight is one. ```ruby # 50-50 chance generating integer limited by 10, or by 20. > Rantly { freq [:integer,10], [:integer 20] } ``` ## Sized Structure A Rantly generator keeps track of how large a datastructure it should generate with its `size` attribute. ```ruby Rantly#size returns the current size Rantly#sized(n,&block) sets the size for the duration of recursive call of block. Block is instance_eval with the generator. ``` Rantly provides two methods that depends on the size ```ruby Rantly#array(size=default_size,&block) returns a sized array consisted of elements by Rantly#calling random branches. Rantly#string(char_class=:print) returns a sized random string, consisted of only chars from a char_class. Rantly#dict(size=default_size,&block) returns a sized random hash. The generator block should generate tuples of keys and values (arrays that have two elements, the first one is used as key, and the second as value). ``` The avaiable char classes for strings are: ```ruby :alnum :alpha :blank :cntrl :digit :graph :lower :print :punct :space :upper :xdigit :ascii ``` ```ruby # sized 10 array of integers > Rantly { array(10) { integer }} => [417733046, -375385433, 0.967812380000118, 26478621, 0.888588160450082, 250944144, 305584916, -151858342, 0.308123867823313, 0.316824642414253] ``` If you set the size once, it applies to all subsequent recursive structures. Here's a sized 10 array of sized 10 strings, ```ruby > Rantly { sized(10) { array {string}} } => ["1c}C/,9I#}", "hpA/UWPJ\\j", "H'~ERtI`|]", "%OUaW\\%uQZ", "Z2QdY=G~G!", "HojnxGDT3", "]a:L[B>bhb", "_Kl=&{tH^<", "ly]Yfb?`6c"] ``` Or a sized 10 array of sized 5 strings, ```ruby > Rantly {array(10){sized(5) {string}}} => ["S\"jf ", "d\\F-$", "-_8pa", "IN0iF", "SxRV$", ".{kQ7", "6>;fo", "}.D8)", "P(tS'", "y0v/v"] ``` Generate a hash that has 5 elements, ```ruby > Rantly { dict { [string,integer] }} {"bR\\qHn"=>247003509502595457, "-Mp '."=>653206579583741142, "gY%-888111605212388599, "+SMn:r"=>-1159506450084197716, "^3gYfQ"=>-2154064981943219558, "= :/\\,"=>433790301059833691} ``` The `dict` generator retries if a key is duplicated. If it fails to generate a unique key after too many tries, it gives up by raising an error: ```ruby > Rantly { dict { ["a",integer] }} Rantly::TooManyTries: Exceed gen limit 60: 60 failed guards) ``` # Property Testing Rantly extends Test::Unit and MiniTest::Test (5.0)/MiniTest::Unit::TestCase (< 5.0) for property testing. The extensions are in their own modules. So you need to require them explicitly: ```ruby require 'rantly/testunit_extensions' # for 'test/unit' require 'rantly/minitest_extensions' # for 'minitest' require 'rantly/rspec_extensions' # for RSpec ``` They define: ```ruby Test::Unit::Assertions#property_of(&block) The block is used to generate random data with a generator. The method returns a Rantly::Property instance, that has the method 'check'. ``` Property assertions within Test::Unit could be done like this, ```ruby # checks that integer only generates fixnum. property_of { integer }.check { |i| assert(i.is_a?(Integer), "integer property did not return Integer type") } ``` Property assertions within Minitest could be done like this, ```ruby # checks that integer only generates fixnum. property_of { integer }.check { |i| assert_kind_of Integer, i, "integer property did not return Integer type" } ``` Property assertions within RSpec could be done like this, ```ruby # checks that integer only generates fixnum. it "integer property only returns Integer type" do property_of { integer }.check { |i| expect(i).to be_a(Integer) } end ``` The check block takes the generated data as its argument. One idiom I find useful is to include a parameter of the random data for the check argument. For example, if I want to check that Rantly#array generates the right sized array, I could say, ```ruby property_of { len = integer [len,array(len){integer}] }.check { |(len,arr)| assert_equal len, arr.length } ``` To control the number of property tests to generate, you have three options. In order of precedence: 1. Pass an integer argument to `check` ```ruby property_of { integer }.check(9000) { |i| assert_kind_of Integer, i } ``` 2. Set the `RANTLY_COUNT` environment variable ```ruby RANTLY_COUNT=9000 ruby my_property_test.rb ``` 3. If neither of the above are set, the default will be to run the `check` block 100 times. If you wish to have quiet output from Rantly, set environmental variable: ```ruby RANTLY_VERBOSE=0 # silent RANTLY_VERBOSE=1 # verbose and default if env is not set ``` This will silence the puts, print, and pretty_print statements in property.rb. # Shrinking Shrinking reduces the value of common types to some terminal lower bound. These functions are added to the Ruby types `Integer`, `String`, `Array`, and `Hash`. For example a `String` is shrinkable until it is empty (e.g. `""`), ```ruby "foo".shrinkable? # => true "foo".shrink # => "fo" "fo".shrink # => "f" "f".shrink # => "" "".shrinkable? # => false ``` Shrinking allows `Property#check` to find a reduced value that still fails the condition. The value is not truely minimal because: * we do not perform a complete in-depth traversal of the failure tree * we limit the search to a maximum 1024 shrinking operations but is usually reduced enough to start debugging. Enable shrinking with ```ruby require 'rantly/shrinks' ``` Use `Tuple` class if you want an array whose elements are individually shrinked, but are not removed. Example: ```ruby property_of { len = range(0, 10) Tuple.new( array(len) { integer } ) }.check { # .. property check here .. } ``` Use `Deflating` class if you want an array whose elements are individully shrinked whenever possible, and removed otherwise. Example: ```ruby property_of { len = range(0, 10) Deflating.new( array(len) { integer } ) }.check { # .. property check here .. } ``` Normal arrays or hashes are not shrinked. # Contributors Thanks to [all contributors](https://github.com/rantly-rb/rantly/graphs/contributors). :cupid: New contributors are welcome! :wink: [Logotype](/logo) designed by: [@Richardbmx](https://github.com/richardbmx) # License Code published under MIT License, Copyright (c) 2009 Howard Yeh. See [LICENSE](/LICENSE). rantly-3.0.0/CHANGELOG.md0000644000004100000410000000772314717634601014706 0ustar www-datawww-data# Change Log All notable changes to rantly will be documented in this file. The curated log begins at changes to version 0.4.0. This project adheres to [Semantic Versioning](http://semver.org/). ## [Master](https://github.com/rantly-rb/rantly/compare/2.0.0...master) (unreleased) ### New features ### Bug fixes ### Changes ## [2.0.0](https://github.com/rantly-rb/rantly/compare/1.2.0...2.0.0) - 2019-01-08 ### New features - Add support for float ranges to `range` generator - [Issue #60](https://github.com/rantly-rb/rantly/issues/60) - thanks [Trevor Brown][Trevor Brown] ### Bug fixes - `range` generator returns `nil` for invalid ranges - [Issue #60](https://github.com/rantly-rb/rantly/issues/60) - thanks [Ana María Martínez Gómez][Ana María Martínez Gómez] - `choose` generator returns `nil` when no values are given - thanks [Ana María Martínez Gómez][Ana María Martínez Gómez] ### Changes - Only support for Ruby >= 2.4 and JRuby >= 9.2 - [Issue #42](https://github.com/rantly-rb/rantly/issues/42) and [issue #37](https://github.com/rantly-rb/rantly/issues/37) - Do not render all shrinking levels, only the failing case and the minimal failed data. - thanks [Ana María Martínez Gómez][Ana María Martínez Gómez] - Improve failure/success messages - thanks [Ana María Martínez Gómez][Ana María Martínez Gómez] ## [1.2.0](https://github.com/abargnesi/rantly/compare/1.1.0...1.2.0) - 2018-08-29 ### New features - Allow to generate floats using Gaussian distribution - [Issue #29](https://github.com/rantly-rb/rantly/issues/29) - thanks [Ana María Martínez Gómez][Ana María Martínez Gómez] and [Víctor Gallego][Víctor Gallego] ### Bug fixes - `NoMethodError` - undefined method `retry?` - when a test using `dict` fails - [Issue #39](https://github.com/rantly-rb/rantly/issues/39) - thanks [Ana María Martínez Gómez][Ana María Martínez Gómez] ### Changes - Correct typo in _Too many tries_ message - thanks [Ana María Martínez Gómez][Ana María Martínez Gómez] ## [1.1.0][1.1.0] - 2017-04-18 ### Improved - Include failed example and number of example run in failure message. - [Issue #21][21] - thanks [Ana María Martínez Gómez][Ana María Martínez Gómez] - Improve run-time for generation of strings. - [Issue #19][19] ## [1.0.0][1.0.0] - 2016-07-06 ### Added - Trying harder to shrink collections instead of giving up on first success of property. - thanks [Eric Bischoff][Eric Bischoff] - Added convenience classes Deflating and Tuple for more control on shrinking. - thanks [Eric Bischoff][Eric Bischoff] - Added usage examples for Deflating and Tuple shrinking strategies. - thanks [Oleksii Fedorov][Oleksii Fedorov] - `Property#check` will now use the `RANTLY_COUNT` environment variable to control the number of values generated. - thanks [Jamie English][Jamie English] ### Major changes - Array shrink was removed in favor of Tuple and Deflating. ## [0.3.2][0.3.2] - 2015-09-16 ### Added - Ability to shrink an object (`Integer`, `String`, `Array`, `Hash`). This is useful in finding the minimum value that fails a property check condition. ### Changed - Improved RSpec and Minitest test extensions. - Improved readability and execution of test suite. - [Issue #4][4] - Updates to documentation. [1.0.0]: https://github.com/abargnesi/rantly/compare/0.3.2...1.0.0 [0.3.2]: https://github.com/abargnesi/rantly/compare/0.3.1...0.3.2 [4]: https://github.com/abargnesi/rantly/issues/4 [19]: https://github.com/abargnesi/rantly/issues/19 [21]: https://github.com/abargnesi/rantly/issues/21 [Eric Bischoff]: https://github.com/Bischoff [Jamie English]: https://github.com/english [Oleksii Fedorov]: https://github.com/waterlink [Ana María Martínez Gómez]: https://github.com/Ana06 [Víctor Gallego]: https://github.com/vicgalle [Trevor Brown]: https://github.com/Stratus3D