
Security News
AGENTS.md Gains Traction as an Open Format for AI Coding Agents
AGENTS.md is a fast-growing open format giving AI coding agents a shared, predictable way to understand project setup, style, and workflows.
h1. Imperative Random Data Generator and Quickcheck
You can use Rant to generate random test data, and use its Test::Unit extension for property-based testing.
Rant 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.
h1. Install
$ gem install hayeah-rant --source http://gems.github.com
$ irb
> gem 'rant'
> require 'rant'
> Rant.gen.value { [integer,float] }
=> [20991307, 0.025756845811823]
> Rant.gen.value { [integer,float]}
=> [-376856492, 0.452245765751706]
h1. Data Generation
You can create random generators from the Rant class. Rant.gen is just returns a class instance of Rant.
> gen = Rant.new
> gen.value { [integer,float] }
=> [-163260081, 0.356075765934108]
h2. Getting Random Data Values
Rant#map(n,limit=10)
call the generator n times, and collect values
Rant#each(n,limit=10)
call a random block n times
Rant#value(limit=10)
call a random block once, and get its value.
To collect an array of random data,
# we want 5
> gen.map(5) { integer }
=> [-380638946, -29645239, 344840868, 308052180, -154360970]
To iterate over random data,
> gen.each(5) { puts integer }
296971291
504994512
-402790444
113152364
502842783
=> nil
To get one value of random data,
> gen.value { 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,
> gen.map(5) { i = integer; guard i > 0; i }
=> [511765059, 250554234, 305947804, 127809156, 285960387]
This always fails,
> gen.map(10) { guard integer.is_a?(Float) }
Rant::TooManyTries: Exceed gen limit 100: 101 failed guards)
h2. 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.
h3. Simple Randomness
Rant#integer(n=nil)
random positive or negative integer. Fixnum only.
Rant#range(lo,hi)
random integer between lo and hi.
Rant#float
random float
Rant#bool
true or false
Rant#literal(value)
No-op. returns value.
Rant#choose(*vals)
Pick one value from among vals.
h3. Meta Randomness
A rant generator is just a mini interpreter. It's often useful to go meta,
Rant#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.
> gen.value { call(:integer) }
=> -240998958
> gen.value { call([:range,0,10]) }
=> 2
> gen.value { call(Proc.new { [integer] })}
=> [522807620]
The @call@ method is useful to implement other abstractions (See next subsection).
Rant#branch(*args)
Pick a random arg among args, and Rant#call it.
50-50 chance getting an integer or float,
> gen.value { branch :integer, :float }
=> 0.0489446702931332
> gen.value { branch :integer, :float }
=> 494934533
h3. Frequencies
Rant#freq(*pairs)
Takes a list of 2-tuples, the first of which is the weight, and the second a Rant#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.
> gen.value { 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
> gen.value { freq :integer, :float }
If a "pair" is an Array, but the first element is not an Integer, @freq@ assumes that it's a Rant method-call with arguments, and the weight is one.
# 50-50 chance generating integer limited by 10, or by 20.
> gen.value { freq [:integer,10], [:integer 20] }
h3. Sized Structure
A Rant generator keeps track of how large a datastructure it should generate with its @size@ attribute.
Rant#size
returns the current size
Rant#sized(n,&block)
sets the size for the duration of recursive call of block. Block is instance_eval with the generator.
Rant provides two methods that depends on the size
Rant#array(*branches)
returns a sized array consisted of elements by Rant#calling random branches.
Rant#string(char_class=:print)
returns a sized random string, consisted of only chars from a char_class.
The avaiable char classes for strings are:
:alnum
:alpha
:blank
:cntrl
:digit
:graph
:lower
:print
:punct
:space
:upper
:xdigit
:ascii
# sized 10 array of integer or float
> gen.value { sized(10) { array(:integer,:float)}}
=> [417733046, -375385433, 0.967812380000118, 26478621, 0.888588160450082, 250944144, 305584916, -151858342, 0.308123867823313, 0.316824642414253]
# fails if you forget to set the size.
> gen.value { array(:integer,:float)}
RuntimeError: size not set
If you set the size once, it applies to all subsequent recursive structures. Here's a sized 10 array of sized 10 strings,
> gen.value { 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,
> gen.value { sized(10) { array Proc.new {sized(5) {string}}}}
=> ["S\"jf ", "d\\F-$", "-_8pa", "IN0iF", "SxRV$", ".{kQ7", "6>;fo", "}.D8)", "P(tS'", "y0v/v"]
Rant#array actually just delegate to Rant#freq, so you can use freq pairs:
> gen.value { sized(10) {array [1,:integer],[2,:float] }}
=> [0.983334733158678, -418176338, 0.976947175363592, 0.703390570421286, -478680395, 5483631, 0.966944106783513, 110469205, 0.540859146793544, 0.521813810037025]
h1. Property Testing
Rant extends Test::Unit for property testing. The extension is in its own module. So you need to require it.
require 'rant/check'
It defines,
Test::Unit::Assertions#property_of(&block)
The block is used to generate random data with a generator. The method returns a Rant::Property instance, that has the method 'check'.
It's like this, using the gem 'shoulda'
# checks that integer only generates fixnum.
should "generate Fixnum only" do
property_of { integer }.check { |i| assert i.is_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 Rant#array generates the right sized array, I could say,
should "generate right sized array" do
property_of {
len = integer
[len,sized(len) { array :integer }]
}.check { |(len,arr)|
assert_equal len, arr.length
}
end
That's about it. Enjoy :)
h1. Copyright
Copyright (c) 2009 Howard Yeh. See LICENSE for details.
FAQs
Unknown package
We found that hayeah-Rant demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
AGENTS.md is a fast-growing open format giving AI coding agents a shared, predictable way to understand project setup, style, and workflows.
Security News
/Research
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.