rantly-2.0.0/0000755000175000017500000000000014145512321013232 5ustar terceiroterceirorantly-2.0.0/CHANGELOG.md0000644000175000017500000000772314145512321015054 0ustar terceiroterceiro# 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 rantly-2.0.0/rantly.gemspec0000644000175000017500000000235314145512321016113 0ustar terceiroterceiroGem::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 = '2.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 = '>= 2.4.0' end rantly-2.0.0/test/0000755000175000017500000000000014145512321014211 5ustar terceiroterceirorantly-2.0.0/test/shrinks_test.rb0000644000175000017500000000504514145512321017262 0ustar terceiroterceirorequire '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-2.0.0/test/test_helper.rb0000644000175000017500000000051214145512321017052 0ustar terceiroterceiro# 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-2.0.0/test/rantly_test.rb0000644000175000017500000000661614145512321017117 0ustar terceiroterceirorequire '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-2.0.0/README.md0000644000175000017500000002522214145512321014514 0ustar terceiroterceiro[![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 2.4 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-2.0.0/LICENSE0000644000175000017500000000206614145512321014243 0ustar terceiroterceiro 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-2.0.0/.document0000644000175000017500000000007414145512321015052 0ustar terceiroterceiroREADME.rdoc lib/**/*.rb bin/* features/**/*.feature LICENSE rantly-2.0.0/Gemfile0000644000175000017500000000032314145512321014523 0ustar terceiroterceirosource 'https://rubygems.org' group :development, :test do gem 'coveralls', '>= 0', require: false gem 'minitest', '~> 5.10.0' gem 'rake', '~> 12.0.0' gem 'simplecov', '>= 0' gem 'rubocop' end rantly-2.0.0/lib/0000755000175000017500000000000014145512321014000 5ustar terceiroterceirorantly-2.0.0/lib/rantly/0000755000175000017500000000000014145512321015311 5ustar terceiroterceirorantly-2.0.0/lib/rantly/shrinks.rb0000644000175000017500000000542514145512321017325 0ustar terceiroterceiro# Integer : shrink to zero class Integer def shrink shrunk = if self > 8 self / 2 elsif self > 0 self - 1 elsif self < -8 (self + 1) / 2 elsif self < 0 self + 1 else 0 end shrunk 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-2.0.0/lib/rantly/spec.rb0000644000175000017500000000025714145512321016574 0ustar terceiroterceirorequire '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-2.0.0/lib/rantly/rspec.rb0000644000175000017500000000004214145512321016746 0ustar terceiroterceirorequire 'rantly/rspec_extensions' rantly-2.0.0/lib/rantly/data.rb0000644000175000017500000000033314145512321016546 0ustar terceiroterceiromodule 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-2.0.0/lib/rantly/rspec_extensions.rb0000644000175000017500000000021714145512321021231 0ustar terceiroterceirorequire 'rspec' require 'rantly/property' class RSpec::Core::ExampleGroup def property_of(&block) Rantly::Property.new(block) end end rantly-2.0.0/lib/rantly/minitest_extensions.rb0000644000175000017500000000043214145512321021750 0ustar terceiroterceirorequire 'minitest' require 'rantly/property' 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-2.0.0/lib/rantly/testunit_extensions.rb0000644000175000017500000000022114145512321021767 0ustar terceiroterceirorequire 'test/unit' require 'rantly/property' module Test::Unit::Assertions def property_of(&block) Rantly::Property.new(block) end end rantly-2.0.0/lib/rantly/minitest.rb0000644000175000017500000000004514145512321017471 0ustar terceiroterceirorequire 'rantly/minitest_extensions' rantly-2.0.0/lib/rantly/generator.rb0000644000175000017500000001300414145512321017622 0ustar terceiroterceiroclass 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-2.0.0/lib/rantly/silly.rb0000644000175000017500000000627514145512321017004 0ustar terceiroterceirorequire '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-2.0.0/lib/rantly/property.rb0000644000175000017500000000444314145512321017527 0ustar terceiroterceirorequire '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 => boom 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 boom.exception("#{i} successful tests, failed on:\n#{test_data}\n\n#{boom}\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-2.0.0/lib/rantly.rb0000644000175000017500000000043014145512321015633 0ustar terceiroterceiro$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-2.0.0/VERSION.yml0000644000175000017500000000005414145512321015101 0ustar terceiroterceiro--- :build: :major: 2 :minor: 0 :patch: 0 rantly-2.0.0/.travis.yml0000644000175000017500000000033714145512321015346 0ustar terceiroterceirolanguage: 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-2.0.0/Rakefile0000644000175000017500000000156214145512321014703 0ustar terceiroterceirorequire '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