Git Product home page Git Product logo

rantly's Introduction

Gem version Build Status Coverage Status

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

Install

Rantly requires Ruby 2.4 or higher. To install Rantly add it to your Gemfile or run:

$ gem install rantly

You can try it in the console by running:

$ 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

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,

# we want 5 random integers
> Rantly(5) { integer }
=> [-380638946, -29645239, 344840868, 308052180, -154360970]

To iterate over random data,

> Rantly.each(5) { puts integer }
296971291
504994512
-402790444
113152364
502842783
=> nil

To get one value of random data,

> 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,

> Rantly(5) { i = integer; guard i > 0; i }
=> [511765059, 250554234, 305947804, 127809156, 285960387]

This always fails,

> 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

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,

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.
> Rantly { call(:integer) }
=> -240998958
> Rantly { call([:range,0,10]) }
=> 2
> Rantly { call(Proc.new { [integer] })}
=> [522807620]

The call method is useful to implement other abstractions (See next subsection).

Rantly#branch(*args)
  Pick a random arg among args, and Rantly#call it.

50-50 chance getting an integer or float,

> Rantly { branch :integer, :float }
=> 0.0489446702931332
> Rantly { branch :integer, :float }
=> 494934533

Frequencies

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.

> 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.

# 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.

# 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.

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

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:

:alnum
:alpha
:blank
:cntrl
:digit
:graph
:lower
:print
:punct
:space
:upper
:xdigit
:ascii
# 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,

> Rantly { sized(10) { array {string}} }
=> ["1c}C/,9I#}", "hpA/UWPJ\\j", "H'~ERtI`|]", "%OUaW\\%uQZ", "Z2QdY=G~G!", "H<o|<FARGQ", "g>ojnxGDT3", "]a:L[B>bhb", "_Kl=&{tH^<", "ly]Yfb?`6c"]

Or a sized 10 array of sized 5 strings,

> 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,

> Rantly { dict { [string,integer] }}
{"bR\\qHn"=>247003509502595457,
 "-Mp '."=>653206579583741142,
 "gY%<SV"=>-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:

> 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:

require 'rantly/testunit_extensions' # for 'test/unit'
require 'rantly/minitest_extensions' # for 'minitest'
require 'rantly/rspec_extensions'    # for RSpec

They define:

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,

# 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,

# 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,

# 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,

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
property_of {
  integer
}.check(9000) { |i|
  assert_kind_of Integer, i
}
  1. Set the RANTLY_COUNT environment variable
RANTLY_COUNT=9000 ruby my_property_test.rb
  1. 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:

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. ""),

"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

require 'rantly/shrinks'

Use Tuple class if you want an array whose elements are individually shrinked, but are not removed. Example:

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:

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. ๐Ÿ’˜ New contributors are welcome! ๐Ÿ˜‰

Logotype designed by: @Richardbmx

License

Code published under MIT License, Copyright (c) 2009 Howard Yeh. See LICENSE.

rantly's People

Contributors

abargnesi avatar abotalov avatar akiomik avatar amcvega avatar amolith avatar ana06 avatar cbliard avatar chrisbr avatar cjstadler avatar dennyabraham avatar diasbruno avatar dodecaphonic avatar english avatar frankshearar avatar hayeah avatar jessitron avatar mwotton avatar nessamurmur avatar rafadc avatar richardbmx avatar stratus3d avatar thedrow avatar waterlink avatar zph avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

rantly's Issues

Logo

Rantly need a cool logo. ๐Ÿค” We should add a license for the logo.

Any designer willing to help? ๐Ÿ˜„

Range for floats?

I've been using the range method to select integers within a specified range, and I see there is a float method that returns a random float, but I need a random float within a specified range. Something like:

range_float(23.4, 56.7) 
# => 34.56

Is this something that exists in Rantly? And if not would a contribution to add this method be welcome? Or is there a better way of doing this outside of Rantly?

Update documentation on shrinking

It seems like the documentation on shrinking is outdated: Array isn't monkey patched any more and the retry? function now needs to be implemented.

Review markdown in CHANGELOG

I think we should inline the urls to Github, as some of them are just used one and it is obfuscating was it is done. How the authors are referenced can also be improved, check for example how it is done in Rubocop.

only support Ruby 2.4 and 2.5

It is really a pain not being able to use new Ruby methods because of supporting old Ruby versions. Our code is getting outdated because of that. I would suggest that we get rid or Ruby 2.0-2.3, keep supporting Ruby 2.4 and start supporting Ruby 2.5.

Rantly makes Rubocop sad

Using Rubocop default configuration, which I understand is the default one and the one more people use, when using the syntax documented in Rantly for rspec there are offenses in two cops: Style/BlockDelimiters and Style/MultilineBlockChain.

So, when using Rubocop together with Rantly for writing rspec tests, there are two options. Using the Rubocop suggested syntax. I don't like this option because someone else working on the project may read the code and in case of doubt he will come to Rantly documentation and using different syntax make it more difficult to understand. The second option is to disable the cops for the Rantly code or for the whole spec folder, which is also not nice.

I would suggest changing the documentation in Rantly to use Rubocop defaults. ๐Ÿ˜‰

Render failures details in rspec failures section

Could we render the failure example in the rspec details of the failures instead of doing it when running the test? Because currently it is render with puts and pretty_print (https://github.com/hayeah/rantly/blob/edd7289d26171ad6aa9d03e0e4fb67091c09798d/lib/rantly/property.rb#L46) and it breaks the output of the RSpec. For example:

With the default rspec output:
image

With nyan-cat-formatter gem:
screenshot_20170119_092441

So it would be great if the number of tests run and the example that failed (if it fails) were render in the failures.

New release please?

There has been some changes since the last release. Can we release again?

NoMethodError: undefined method `retry?' for {"N-<o`e"=>""}:Hash

Using Rantly 1.1.0, and ruby 2.4.1p111, I tried a simple test that would fail:

require 'rantly/shrinks'
require 'rantly/rspec_extensions'

describe "Test" do
  it "fails" do
     property_of {
       dict { [string, string] }
     }.check { |i|
       expect(i).to be_a(Integer)
     }
  end
end

will give me the output:


failure: 0 tests, on:
{"?A11+I"=>"\\=>B{A",
 "6xpu|C"=>".\\T7^k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 0:
{"?A11+I"=>"\\=>B{A",
 "6xpu|C"=>".\\T7^k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 1:
{"?A11+I"=>"\\=B{A",
 "6xpu|C"=>".\\T7^k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 2:
{"?A11+I"=>"\\=B{",
 "6xpu|C"=>".\\T7^k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 3:
{"?A11+I"=>"\\={",
 "6xpu|C"=>".\\T7^k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 4:
{"?A11+I"=>"\\=",
 "6xpu|C"=>".\\T7^k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 5:
{"?A11+I"=>"\\",
 "6xpu|C"=>".\\T7^k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 6:
{"?A11+I"=>"",
 "6xpu|C"=>".\\T7^k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 7:
{"?A11+I"=>"",
 "6xpu|C"=>"\\T7^k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 8:
{"?A11+I"=>"",
 "6xpu|C"=>"\\T7k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 9:
{"?A11+I"=>"",
 "6xpu|C"=>"T7k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 10:
{"?A11+I"=>"",
 "6xpu|C"=>"Tk",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 11:
{"?A11+I"=>"",
 "6xpu|C"=>"k",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 12:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"G'j-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 13:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"G'-Q`",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 14:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"G'-Q",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 15:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"G'Q",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 16:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"'Q",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 17:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"'",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 18:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"px p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 19:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"p p-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 20:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"pp-c",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 21:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"pp-",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 22:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"p-",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 23:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"-",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 24:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"X\"G~NZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 25:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"X\"GNZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 26:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"\"GNZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 27:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"\"GZ",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 28:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"\"Z",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 29:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"\"",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 30:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"",
 "rg\\K$h"=>"|n3!]W"}
Shrinking at depth 31:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"",
 "rg\\K$h"=>"n3!]W"}
Shrinking at depth 32:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"",
 "rg\\K$h"=>"n!]W"}
Shrinking at depth 33:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"",
 "rg\\K$h"=>"n]W"}
Shrinking at depth 34:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"",
 "rg\\K$h"=>"nW"}
Shrinking at depth 35:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"",
 "rg\\K$h"=>"W"}
Shrinking at depth 36:
{"?A11+I"=>"",
 "6xpu|C"=>"",
 "!Jo/Ly"=>"",
 "WuEo5C"=>"",
 ".]uXT8"=>"",
 "rg\\K$h"=>""}
Shrinking at depth 37:
{"6xpu|C"=>"", "!Jo/Ly"=>"", "WuEo5C"=>"", ".]uXT8"=>"", "rg\\K$h"=>""}
Shrinking at depth 38:
{"!Jo/Ly"=>"", "WuEo5C"=>"", ".]uXT8"=>"", "rg\\K$h"=>""}
Shrinking at depth 39:
{"WuEo5C"=>"", ".]uXT8"=>"", "rg\\K$h"=>""}
Shrinking at depth 40:
{".]uXT8"=>"", "rg\\K$h"=>""}
Shrinking at depth 41:
{"rg\\K$h"=>""}
Shrinking at depth 42:
{}
F

Failures:

  1) Reports that only have new_service events integer property only returns Integer type
     Failure/Error:
       property_of {
         dict { [string, string] }
       }.check { |i|
         expect(i).to be_a(Integer)
       }
     
     NoMethodError:
       undefined method `retry?' for {"rg\\K$h"=>""}:Hash
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:79:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:73:in `rescue in shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:69:in `shrinkify'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:50:in `rescue in check'
     # /home/mauricio/.rvm/gems/ruby-2.4.1/gems/rantly-1.1.0/lib/rantly/property.rb:30:in `check'
     # ./spec/properties/new_service_spec.rb:9:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     #   expected {"?A11+I"=>"\\=>B{A", "6xpu|C"=>".\\T7^k", "!Jo/Ly"=>"G'j-Q`", "WuEo5C"=>"px p-c", ".]uXT8"=>"X\"G~NZ", "rg\\K$h"=>"|n3!]W"} to be a kind of Integer
     #   ./spec/properties/new_service_spec.rb:10:in `block (3 levels) in <top (required)>'

Finished in 0.03759 seconds (files took 0.10705 seconds to load)
1 example, 1 failure

Make tests runnable.

  • Update to latest minitest gem.
  • Use minitest spec-style testing to match shoulda appeal.
  • Remove shoulda gem. The transitive dependencies are unnecessary and rely on an old version of minitest.
  • Update README documentation.

Only support JRuby 9.1.7.0 and 9.2.0.0

In the same way as in #37, we should stop supporting the old jruby-1.7.26. It is using Ruby 1.9.3! ๐Ÿ˜ฑ Instead, I would like to support 9.2.0.0, the first version which supports Ruby 2.5! ๐ŸŽ‰

Maybe improve shrinking algorithm?

For integer/float values it is possible to do shrinking not by 1, but using kind of binary search:

  1. initialize step = 1
  2. change value by step, is property still failing with new value?
    • yes: multiply step by 2;
    • no: divide step by 2;
  3. is step == 0?
    • yes: return last seen failing value
    • no: go to step 2

For arrays, currently last element is removed if it is shrinked to its "zero" value (0, "", etc.). This is not the most useful strategy, since usually properties on arrays verify relationship between different elements. That means that if some relation is failing between only 3 items, potentially all but these 3 items can be safely removed, so the algorithm for this is:

  1. new_value = []
  2. head = current_value.shift
  3. if property with current_value still failing?
    • no: append head to new_value
  4. if current_value is empty?
    • yes: return new_value
    • no: go to step 2

This algorithm works really well, it is very fast and still allows for individual shrinking for each element after it is done.


@abargnesi So what do you think? I could implement these (or one of these) over this weekend.

Size greater than

I would like to generate string with size greater than 200, is that possible? ๐Ÿ˜•

Remove rbx-2.5.8 and rbx-3.70

Should we remove rbx-2.5.8 and rbx-3.70 from Travis? they are failing and we are ignoring them anyway. And they are making that the result from Travis takes longer to be reported. ๐Ÿค“

Moved repository to rantly-rb organization.

Hey everybody. Due to the group of contributors so far it is time to create a rantly github organization. The abargnesi/rantly repository now redirects to rantly-rb/rantly. Valuable work shouldn't be held up by one person when there is an active group of contributors.

Would anyone like to be added to the organization as a contributor?

Unable to call method in property_of block

I have a property_of block that needs to contain some special logic I have for choosing a state and city. I need to make sure the city selected is within the chosen state. I want to do something like this:

properties = property_of do
    state = choose(*states)
    city = choose(*cities_within_state(state))

    [state, city]
end

This does not work because cities_within_state is a method defined in an included module, which doesn't seem to be available within the context of the property_of block (though it is available outside it). I need to invoke the method within the property_of block because that is the only scope in which the state variable exists.

I was able to come up with a workaround to invoke the the method within the block:

properties = property_of do
    state = choose(*states)
    cities = Class.new.extend(ModuleContainingMethod).cities_within_state(state)
    city = choose(*cities)

    [state, city]
end

Is there a better way of doing this?

Using rantly with rspec-rails

Hi,

I'm not sure if there is a problem with my project's setup (rails app), but when I try to use
this package I got an error like cannot find -- rspec.

It seems that rspec-rails is its own project (use the base package like rspec-core,
rspec-expectations and so on...).

By changing the rspec package to the rspec-rails everything works (see diasbruno/rantly#17111a38e8e98c97230ecf32454f0d666918ef77)

So, I don't know if this package should also provide an extension for this case: rspec_rails_extensions.rb.

Add Rubocop

It would be nice to follow some style rules and that they are defined somewhere. I would suggest to use Rubocop with the default configuration. ๐Ÿ˜‰

Inconsistency in behaviour for `Rantly(number) { ... }`

Hi Rantly team,

I found an inconsistency in the behaviour or Rantly(number) { ... }.

The documentation said:

Rantly(5) { integer } # same as Rantly.map(5) { integer }
=> [-1843396915550491870, -1683855015308353854, -2291347782549033959, -951461511269053584, 483265231542292652]

So I expect Rantly(number) { ... } to always return an array, however Rantly(0) { ... } and Rantly(1) { ... } will only return the element and not wrapped in an array.

Please refer to this console output:

irb(main):001:0> require 'rantly'
=> true
irb(main):002:0> Rantly(0) { string }
=> "e7j1\"6"
irb(main):003:0> Rantly(1) { string }
=> "6%H)H."
irb(main):004:0> Rantly(2) { string }
=> [".3*m|m", "\\*'#w+"] 
irb(main):005:0> Rantly.map(0) { string }
=> []
irb(main):006:0> Rantly.map(1) { string }
=> ["jmdPnP"]
irb(main):007:0> Rantly.map(2) { string }
=> ["dO?Ndy", "w !\\7j"]
irb(main):008:0>

I found that the Rantly.map(number) { ... } is more consistent.

Thanks.

Seed parameter to reproduce a failed test

Say you have a lot of data as your test input. When you get a failure message, rantly/rantly-rspec ends up telling you so much that you can't use it for reproducing regressions:

3 successful tests, failed on:
       [#<ActiveRecord::Relation [#<SomeRecord id: nil, label: "some_label", value: 0.1241e4>, #<SomeRecord id: nil, label: "some_other_label", value: 0.2577e4>, #<SomeRecord id: nil, label: "some_label", value: 0.1233e4>, #<SomeRecord id: nil, label: "some_other_label", value: 0.1143e4>, #<SomeRecord id: nil, label: "some_label", value: 0.2001e4>, #<SomeRecord id: nil, label: "some_other_label", value: 0.596e3>, #<SomeRecord id: nil, label: "some_label", value: 0.1926e4>, #<SomeRecord id: nil, label: "some_other_label", value: 0.1309e4>, #<SomeRecord id: nil, label: "some_label", value: 0.2292e4>, #<SomeRecord id: nil, label: "some_other_label", value: 0.1136e4>, ...]>, "day"]

...

Randomized with seed 61833

It turns out that running rspec --seed 61833 doesn't reproduce this input set either.

Is there a shorthand way to tell rspec + rantly to run this exact test again?

Generate number with different distributions - Normal/Gaussian

Currently Rantly only allow to generate numbers with a uniform distribution. It is not possible to use any other distribution. And I would say this is not possible in Rantly, but also not in the Random Ruby class which seems to be really limited.

I would suggest starting with a normal distribution, also called Gaussian distribution. This distribution could be really useful for property testing, for example for generating random numbers that are closer to a limit case:

For example, if 17 is our limit case, the distribution would look like:

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.