{"id":13879527,"url":"https://github.com/rantly-rb/rantly","last_synced_at":"2025-05-16T10:05:51.314Z","repository":{"id":34707769,"uuid":"38684025","full_name":"rantly-rb/rantly","owner":"rantly-rb","description":"Ruby Imperative Random Data Generator and Quickcheck","archived":false,"fork":false,"pushed_at":"2024-10-21T13:05:52.000Z","size":7632,"stargazers_count":280,"open_issues_count":10,"forks_count":21,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-16T10:04:54.061Z","etag":null,"topics":["minitest","property-based-testing","quickcheck","random","randomness","randomness-generation","randomness-testing","rspec","ruby","test-unit"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rantly-rb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-07-07T11:41:46.000Z","updated_at":"2025-04-25T11:40:28.000Z","dependencies_parsed_at":"2022-08-28T13:01:33.109Z","dependency_job_id":"5816ed8b-2823-4a24-98ff-1481a1746a7a","html_url":"https://github.com/rantly-rb/rantly","commit_stats":{"total_commits":193,"total_committers":28,"mean_commits":6.892857142857143,"dds":0.6839378238341969,"last_synced_commit":"944788abdf954343450851d4e37cd96523aefe02"},"previous_names":["abargnesi/rantly"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rantly-rb%2Frantly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rantly-rb%2Frantly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rantly-rb%2Frantly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rantly-rb%2Frantly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rantly-rb","download_url":"https://codeload.github.com/rantly-rb/rantly/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254509476,"owners_count":22082891,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["minitest","property-based-testing","quickcheck","random","randomness","randomness-generation","randomness-testing","rspec","ruby","test-unit"],"created_at":"2024-08-06T08:02:23.998Z","updated_at":"2025-05-16T10:05:51.294Z","avatar_url":"https://github.com/rantly-rb.png","language":"Ruby","readme":"[![Gem version](https://badge.fury.io/rb/rantly.svg)](https://badge.fury.io/rb/rantly)\n[![Build Status](https://travis-ci.org/rantly-rb/rantly.svg?branch=master)](https://travis-ci.org/rantly-rb/rantly)\n[![Coverage Status](https://coveralls.io/repos/github/rantly-rb/rantly/badge.svg?branch=master)](https://coveralls.io/github/rantly-rb/rantly?branch=master)\n\n# Imperative Random Data Generator and Quickcheck\n\nYou can use Rantly to generate random test data, and use its Test::Unit extension for property-based testing.\n\nRantly is basically a recursive descent interpreter, each of its method returns a random value of some type (string, integer, float, etc.).\n\nIts implementation has no alien mathematics inside. Completely side-effect-free-free.\n\n![img](/logo/Rantly.png)\n\n\n# Install\n\nRantly requires Ruby 3.2 or higher. To install Rantly add it to your Gemfile or run:\n\n```ruby\n$ gem install rantly\n```\n\nYou can try it in the console by running:\n\n```ruby\n$ irb -rrantly\n\u003e Rantly { [integer,float] } # same as Rantly.value { integer }\n=\u003e [20991307, 0.025756845811823]\n\u003e Rantly { [integer,float]}\n=\u003e [-376856492, 0.452245765751706]\n\u003e Rantly(5) { integer } # same as Rantly.map(5) { integer }\n=\u003e [-1843396915550491870, -1683855015308353854, -2291347782549033959, -951461511269053584, 483265231542292652]\n```\n\n\n# Data Generation\n\n## Getting Random Data Values\n\n```ruby\nRantly#map(n,limit=10,\u0026block)\n  call the generator n times, and collect values\nRantly#each(n,limit=10,\u0026block)\n  call a random block n times\nRantly#value(limit=10,\u0026block)\n  call a random block once, and get its value.\n```\n\nTo collect an array of random data,\n\n```ruby\n# we want 5 random integers\n\u003e Rantly(5) { integer }\n=\u003e [-380638946, -29645239, 344840868, 308052180, -154360970]\n```\n\nTo iterate over random data,\n\n```ruby\n\u003e Rantly.each(5) { puts integer }\n296971291\n504994512\n-402790444\n113152364\n502842783\n=\u003e nil\n```\n\nTo get one value of random data,\n\n```ruby\n\u003e Rantly { integer }\n=\u003e 278101042\n```\n\nThe 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.\n\nThis almost always succeeds,\n\n```ruby\n\u003e Rantly(5) { i = integer; guard i \u003e 0; i }\n=\u003e [511765059, 250554234, 305947804, 127809156, 285960387]\n```\n\nThis always fails,\n\n```ruby\n\u003e Rantly(10) { guard integer.is_a?(Float) }\nRantly::TooManyTries: Exceed gen limit 100: 101 failed guards)\n```\n\n## Random Generating Methods\n\nThe 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.\n\n## Simple Randomness\n\n```ruby\nRantly#integer(n=nil)\n  random positive or negative integer. Fixnum only.\nRantly#range(lo,hi)\n  random integer between lo and hi.\nRantly#float\n  random float\nRantly#boolean\n  true or false\nRantly#literal(value)\n  No-op. returns value.\nRantly#choose(*vals)\n  Pick one value from among vals.\n```\n\n## Meta Randomness\n\nA rant generator is just a mini interpreter. It's often useful to go meta,\n\n```ruby\nRantly#call(gen)\n  If gen is a Symbol, just do a method call with send.\n  If gen is an Array, the first element of the array is the method name, the rest are args.\n  If gen is a Proc, instance_eval it with the generator.\n```\n\n```ruby\n\u003e Rantly { call(:integer) }\n=\u003e -240998958\n```\n\n```ruby\n\u003e Rantly { call([:range,0,10]) }\n=\u003e 2\n```\n\n```ruby\n\u003e Rantly { call(Proc.new { [integer] })}\n=\u003e [522807620]\n```\n\nThe `call` method is useful to implement other abstractions (See next subsection).\n\n```ruby\nRantly#branch(*args)\n  Pick a random arg among args, and Rantly#call it.\n```\n\n50-50 chance getting an integer or float,\n\n```ruby\n\u003e Rantly { branch :integer, :float }\n=\u003e 0.0489446702931332\n\u003e Rantly { branch :integer, :float }\n=\u003e 494934533\n```\n\n\n## Frequencies\n\n```ruby\nRantly#freq(*pairs)\n  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.\n```\n\nTwice as likely to get a float than integer. Never gets a ranged integer.\n\n```ruby\n\u003e Rantly { freq [1,:integer], [2,:float], [0,:range,0,10] }\n```\n\nIf the \"pair\" is not an array, but just a symbol, `freq` assumes that the weight is 1.\n\n```ruby\n# 50-50 between integer and float\n\u003e Rantly { freq :integer, :float }\n```\n\nIf 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.\n\n```ruby\n# 50-50 chance generating integer limited by 10, or by 20.\n\u003e Rantly { freq [:integer,10], [:integer 20] }\n```\n\n\n## Sized Structure\n\nA Rantly generator keeps track of how large a datastructure it should generate with its `size` attribute.\n\n```ruby\nRantly#size\n returns the current size\nRantly#sized(n,\u0026block)\n sets the size for the duration of recursive call of block. Block is instance_eval with the generator.\n```\n\nRantly provides two methods that depends on the size\n\n```ruby\nRantly#array(size=default_size,\u0026block)\n  returns a sized array consisted of elements by Rantly#calling random branches.\nRantly#string(char_class=:print)\n  returns a sized random string, consisted of only chars from a char_class.\nRantly#dict(size=default_size,\u0026block)\n  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).\n```\n\nThe avaiable char classes for strings are:\n\n```ruby\n:alnum\n:alpha\n:blank\n:cntrl\n:digit\n:graph\n:lower\n:print\n:punct\n:space\n:upper\n:xdigit\n:ascii\n```\n\n```ruby\n# sized 10 array of integers\n\u003e Rantly { array(10) { integer }}\n=\u003e [417733046, -375385433, 0.967812380000118, 26478621, 0.888588160450082, 250944144, 305584916, -151858342, 0.308123867823313, 0.316824642414253]\n```\n\nIf you set the size once, it applies to all subsequent recursive structures. Here's a sized 10 array of sized 10 strings,\n\n```ruby\n\u003e Rantly { sized(10) { array {string}} }\n=\u003e [\"1c}C/,9I#}\", \"hpA/UWPJ\\\\j\", \"H'~ERtI`|]\", \"%OUaW\\\\%uQZ\", \"Z2QdY=G~G!\", \"H\u003co|\u003cFARGQ\", \"g\u003eojnxGDT3\", \"]a:L[B\u003ebhb\", \"_Kl=\u0026{tH^\u003c\", \"ly]Yfb?`6c\"]\n```\n\nOr a sized 10 array of sized 5 strings,\n\n```ruby\n\u003e Rantly {array(10){sized(5) {string}}}\n=\u003e [\"S\\\"jf \", \"d\\\\F-$\", \"-_8pa\", \"IN0iF\", \"SxRV$\", \".{kQ7\", \"6\u003e;fo\", \"}.D8)\", \"P(tS'\", \"y0v/v\"]\n```\n\nGenerate a hash that has 5 elements,\n\n```ruby\n\u003e Rantly { dict { [string,integer] }}\n{\"bR\\\\qHn\"=\u003e247003509502595457,\n \"-Mp '.\"=\u003e653206579583741142,\n \"gY%\u003cSV\"=\u003e-888111605212388599,\n \"+SMn:r\"=\u003e-1159506450084197716,\n \"^3gYfQ\"=\u003e-2154064981943219558,\n \"= :/\\\\,\"=\u003e433790301059833691}\n```\n\nThe `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:\n\n```ruby\n\u003e Rantly { dict { [\"a\",integer] }}\nRantly::TooManyTries: Exceed gen limit 60: 60 failed guards)\n```\n\n\n# Property Testing\n\nRantly extends Test::Unit and MiniTest::Test (5.0)/MiniTest::Unit::TestCase (\u003c 5.0) for property testing. The extensions are in their own modules. So you need to require them explicitly:\n\n```ruby\nrequire 'rantly/testunit_extensions' # for 'test/unit'\nrequire 'rantly/minitest_extensions' # for 'minitest'\nrequire 'rantly/rspec_extensions'    # for RSpec\n```\n\nThey define:\n\n```ruby\nTest::Unit::Assertions#property_of(\u0026block)\n  The block is used to generate random data with a generator. The method returns a Rantly::Property instance, that has the method 'check'.\n```\n\nProperty assertions within Test::Unit could be done like this,\n\n```ruby\n# checks that integer only generates fixnum.\nproperty_of {\n  integer\n}.check { |i|\n  assert(i.is_a?(Integer), \"integer property did not return Integer type\")\n}\n```\n\nProperty assertions within Minitest could be done like this,\n\n```ruby\n# checks that integer only generates fixnum.\nproperty_of {\n  integer\n}.check { |i|\n  assert_kind_of Integer, i, \"integer property did not return Integer type\"\n}\n```\n\nProperty assertions within RSpec could be done like this,\n\n```ruby\n# checks that integer only generates fixnum.\nit \"integer property only returns Integer type\" do\n   property_of {\n     integer\n   }.check { |i|\n     expect(i).to be_a(Integer)\n   }\nend\n```\n\nThe 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,\n\n```ruby\nproperty_of {\n  len = integer\n  [len,array(len){integer}]\n}.check { |(len,arr)|\n  assert_equal len, arr.length\n}\n```\n\nTo control the number of property tests to generate, you have three options. In order of precedence:\n\n1. Pass an integer argument to `check`\n\n```ruby\nproperty_of {\n  integer\n}.check(9000) { |i|\n  assert_kind_of Integer, i\n}\n```\n\n2. Set the `RANTLY_COUNT` environment variable\n\n```ruby\nRANTLY_COUNT=9000 ruby my_property_test.rb\n```\n\n3. If neither of the above are set, the default will be to run the `check` block 100 times.\n\nIf you wish to have quiet output from Rantly, set environmental variable:\n```ruby\nRANTLY_VERBOSE=0 # silent\nRANTLY_VERBOSE=1 # verbose and default if env is not set\n```\nThis will silence the puts, print, and pretty_print statements in property.rb.\n\n# Shrinking\n\nShrinking reduces the value of common types to some terminal lower bound. These functions are added to the Ruby types `Integer`, `String`, `Array`, and `Hash`.\n\nFor example a `String` is shrinkable until it is empty (e.g. `\"\"`),\n\n```ruby\n\"foo\".shrinkable?     # =\u003e true\n\"foo\".shrink          # =\u003e \"fo\"\n\"fo\".shrink           # =\u003e \"f\"\n\"f\".shrink            # =\u003e \"\"\n\"\".shrinkable?        # =\u003e false\n```\n\nShrinking allows `Property#check` to find a reduced value that still fails the condition. The value is not truely minimal because:\n\n* we do not perform a complete in-depth traversal of the failure tree\n* we limit the search to a maximum 1024 shrinking operations\n\nbut is usually reduced enough to start debugging.\n\nEnable shrinking with\n\n```ruby\nrequire 'rantly/shrinks'\n```\n\nUse `Tuple` class if you want an array whose elements are individually shrinked, but are not removed. Example:\n\n```ruby\nproperty_of {\n  len = range(0, 10)\n  Tuple.new( array(len) { integer } )\n}.check {\n  # .. property check here ..\n}\n```\n\nUse `Deflating` class if you want an array whose elements are individully shrinked whenever possible, and removed otherwise. Example:\n\n```ruby\nproperty_of {\n  len = range(0, 10)\n  Deflating.new( array(len) { integer } )\n}.check {\n  # .. property check here ..\n}\n```\n\nNormal arrays or hashes are not shrinked.\n\n\n# Contributors\n\nThanks to [all contributors](https://github.com/rantly-rb/rantly/graphs/contributors). :cupid: New contributors are welcome! :wink:\n\n[Logotype](/logo) designed by: [@Richardbmx](https://github.com/richardbmx)\n\n\n# License\n\nCode published under MIT License, Copyright (c) 2009 Howard Yeh. See [LICENSE](/LICENSE).\n\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frantly-rb%2Frantly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frantly-rb%2Frantly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frantly-rb%2Frantly/lists"}