rantly 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Howard Yeh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,296 @@
1
+ h1. Imperative Random Data Generator and Quickcheck
2
+
3
+ You can use Rant to generate random test data, and use its Test::Unit extension for property-based testing.
4
+
5
+ Rant is basically a recursive descent interpreter, each of its method returns a random value of some type (string, integer, float, etc.).
6
+
7
+ Its implementation has no alien mathematics inside. Completely side-effect-free-free.
8
+
9
+ h1. Install
10
+
11
+ <pre><code>
12
+ $ gem install hayeah-rant --source http://gems.github.com
13
+ </code></pre>
14
+
15
+ <pre><code>
16
+ $ irb
17
+ > gem 'rant'
18
+ > require 'rant'
19
+ > Rant.gen.value { [integer,float] }
20
+ => [20991307, 0.025756845811823]
21
+ > Rant.gen.value { [integer,float]}
22
+ => [-376856492, 0.452245765751706]
23
+ </code></pre>
24
+
25
+
26
+ h1. Data Generation
27
+
28
+ You can create random generators from the Rant class. Rant.gen is just returns a class instance of Rant.
29
+
30
+ <pre><code>
31
+ > gen = Rant.new
32
+ > gen.value { [integer,float] }
33
+ => [-163260081, 0.356075765934108]
34
+ </code></pre>
35
+
36
+ h2. Getting Random Data Values
37
+
38
+ <pre><code>
39
+ Rant#map(n,limit=10)
40
+ call the generator n times, and collect values
41
+ Rant#each(n,limit=10)
42
+ call a random block n times
43
+ Rant#value(limit=10)
44
+ call a random block once, and get its value.
45
+ </code></pre>
46
+
47
+ To collect an array of random data,
48
+
49
+ <pre><code>
50
+ # we want 5
51
+ > gen.map(5) { integer }
52
+ => [-380638946, -29645239, 344840868, 308052180, -154360970]
53
+ </code></pre>
54
+
55
+ To iterate over random data,
56
+
57
+ <pre><code>
58
+ > gen.each(5) { puts integer }
59
+ 296971291
60
+ 504994512
61
+ -402790444
62
+ 113152364
63
+ 502842783
64
+ => nil
65
+ </code></pre>
66
+
67
+ To get one value of random data,
68
+
69
+ <pre><code>
70
+ > gen.value { integer }
71
+ => 278101042
72
+ </code></pre>
73
+
74
+ 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.
75
+
76
+ This almost always succeeds,
77
+
78
+ <pre><code>
79
+ > gen.map(5) { i = integer; guard i > 0; i }
80
+ => [511765059, 250554234, 305947804, 127809156, 285960387]
81
+ </code></pre>
82
+
83
+ This always fails,
84
+
85
+ <pre><code>
86
+ > gen.map(10) { guard integer.is_a?(Float) }
87
+ Rant::TooManyTries: Exceed gen limit 100: 101 failed guards)
88
+ </code></pre>
89
+
90
+ h2. Random Generating Methods
91
+
92
+ 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.
93
+
94
+ h3. Simple Randomness
95
+
96
+ <pre><code>
97
+ Rant#integer(n=nil)
98
+ random positive or negative integer. Fixnum only.
99
+ Rant#range(lo,hi)
100
+ random integer between lo and hi.
101
+ Rant#float
102
+ random float
103
+ Rant#bool
104
+ true or false
105
+ Rant#literal(value)
106
+ No-op. returns value.
107
+ Rant#choose(*vals)
108
+ Pick one value from among vals.
109
+ </code></pre>
110
+
111
+ h3. Meta Randomness
112
+
113
+ A rant generator is just a mini interpreter. It's often useful to go meta,
114
+
115
+ <pre><code>
116
+ Rant#call(gen)
117
+ If gen is a Symbol, just do a method call with send.
118
+ If gen is an Array, the first element of the array is the method name, the rest are args.
119
+ If gen is a Proc, instance_eval it with the generator.
120
+ </code></pre>
121
+
122
+ <pre><code>
123
+ > gen.value { call(:integer) }
124
+ => -240998958
125
+ </code></pre>
126
+
127
+ <pre><code>
128
+ > gen.value { call([:range,0,10]) }
129
+ => 2
130
+ </code></pre>
131
+
132
+ <pre><code>
133
+ > gen.value { call(Proc.new { [integer] })}
134
+ => [522807620]
135
+ </code></pre>
136
+
137
+ The @call@ method is useful to implement other abstractions (See next subsection).
138
+
139
+ <pre><code>
140
+ Rant#branch(*args)
141
+ Pick a random arg among args, and Rant#call it.
142
+ </code></pre>
143
+
144
+ 50-50 chance getting an integer or float,
145
+
146
+ <pre><code>
147
+ > gen.value { branch :integer, :float }
148
+ => 0.0489446702931332
149
+ > gen.value { branch :integer, :float }
150
+ => 494934533
151
+ </code></pre>
152
+
153
+
154
+ h3. Frequencies
155
+
156
+ <pre><code>
157
+ Rant#freq(*pairs)
158
+ 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.
159
+ </code></pre>
160
+
161
+ Twice as likely to get a float than integer. Never gets a ranged integer.
162
+
163
+ <pre><code>
164
+ > gen.value { freq [1,:integer], [2,:float], [0,:range,0,10] }
165
+ </code></pre>
166
+
167
+ If the "pair" is not an array, but just a symbol, @freq@ assumes that the weight is 1.
168
+
169
+ <pre><code>
170
+ # 50-50 between integer and float
171
+ > gen.value { freq :integer, :float }
172
+ </code></pre>
173
+
174
+ 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.
175
+
176
+ <pre><code>
177
+ # 50-50 chance generating integer limited by 10, or by 20.
178
+ > gen.value { freq [:integer,10], [:integer 20] }
179
+ </code></pre>
180
+
181
+
182
+
183
+ h3. Sized Structure
184
+
185
+ A Rant generator keeps track of how large a datastructure it should generate with its @size@ attribute.
186
+
187
+ <pre><code>
188
+ Rant#size
189
+ returns the current size
190
+ Rant#sized(n,&block)
191
+ sets the size for the duration of recursive call of block. Block is instance_eval with the generator.
192
+ </code></pre>
193
+
194
+ Rant provides two methods that depends on the size
195
+
196
+ <pre><code>
197
+ Rant#array(*branches)
198
+ returns a sized array consisted of elements by Rant#calling random branches.
199
+ Rant#string(char_class=:print)
200
+ returns a sized random string, consisted of only chars from a char_class.
201
+ </code></pre>
202
+
203
+ The avaiable char classes for strings are:
204
+
205
+ <pre><code>
206
+ :alnum
207
+ :alpha
208
+ :blank
209
+ :cntrl
210
+ :digit
211
+ :graph
212
+ :lower
213
+ :print
214
+ :punct
215
+ :space
216
+ :upper
217
+ :xdigit
218
+ :ascii
219
+ </code></pre>
220
+
221
+ <pre><code>
222
+ # sized 10 array of integer or float
223
+ > gen.value { sized(10) { array(:integer,:float)}}
224
+ => [417733046, -375385433, 0.967812380000118, 26478621, 0.888588160450082, 250944144, 305584916, -151858342, 0.308123867823313, 0.316824642414253]
225
+
226
+ # fails if you forget to set the size.
227
+ > gen.value { array(:integer,:float)}
228
+ RuntimeError: size not set
229
+
230
+ </code></pre>
231
+
232
+ If you set the size once, it applies to all subsequent recursive structures. Here's a sized 10 array of sized 10 strings,
233
+
234
+ <pre><code>
235
+ > gen.value { sized(10) { array(:string)} }
236
+ => ["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"]
237
+ </code></pre>
238
+
239
+ Or a sized 10 array of sized 5 strings,
240
+
241
+ <pre><code>
242
+ > gen.value { sized(10) { array Proc.new {sized(5) {string}}}}
243
+ => ["S\"jf ", "d\\F-$", "-_8pa", "IN0iF", "SxRV$", ".{kQ7", "6>;fo", "}.D8)", "P(tS'", "y0v/v"]
244
+ </code></pre>
245
+
246
+ Rant#array actually just delegate to Rant#freq, so you can use freq pairs:
247
+
248
+ <pre><code>
249
+ > gen.value { sized(10) {array [1,:integer],[2,:float] }}
250
+ => [0.983334733158678, -418176338, 0.976947175363592, 0.703390570421286, -478680395, 5483631, 0.966944106783513, 110469205, 0.540859146793544, 0.521813810037025]
251
+ </code></pre>
252
+
253
+
254
+ h1. Property Testing
255
+
256
+ Rant extends Test::Unit for property testing. The extension is in its own module. So you need to require it.
257
+
258
+ <pre><code>
259
+ require 'rant/check'
260
+ </code></pre>
261
+
262
+ It defines,
263
+
264
+ <pre><code>
265
+ Test::Unit::Assertions#property_of(&block)
266
+ The block is used to generate random data with a generator. The method returns a Rant::Property instance, that has the method 'check'.
267
+ </code></pre>
268
+
269
+ It's like this, using the gem 'shoulda'
270
+
271
+ <pre><code>
272
+ # checks that integer only generates fixnum.
273
+ should "generate Fixnum only" do
274
+ property_of { integer }.check { |i| assert i.is_a?(Integer) }
275
+ end
276
+ </code></pre>
277
+
278
+ 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,
279
+
280
+ <pre><code>
281
+ should "generate right sized array" do
282
+ property_of {
283
+ len = integer
284
+ [len,sized(len) { array :integer }]
285
+ }.check { |(len,arr)|
286
+ assert_equal len, arr.length
287
+ }
288
+ end
289
+ </code></pre>
290
+
291
+ That's about it. Enjoy :)
292
+
293
+
294
+ h1. Copyright
295
+
296
+ Copyright (c) 2009 Howard Yeh. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rantly"
8
+ gem.summary = %Q{Ruby Imperative Random Data Generator and Quickcheck}
9
+ gem.email = "hayeah@gmail.com"
10
+ gem.homepage = "http://github.com/hayeah/rantly"
11
+ gem.authors = ["Howard Yeh"]
12
+
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/*_test.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+
40
+ task :default => :test
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION.yml')
45
+ config = YAML.load(File.read('VERSION.yml'))
46
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "rant #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
56
+
data/Rant.gemspec ADDED
@@ -0,0 +1,54 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{Rant}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Howard Yeh"]
12
+ s.date = %q{2009-10-01}
13
+ s.email = %q{hayeah@gmail.com}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.textile"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.textile",
23
+ "Rakefile",
24
+ "Rant.gemspec",
25
+ "VERSION.yml",
26
+ "lib/rant.rb",
27
+ "lib/rant/check.rb",
28
+ "lib/rant/data.rb",
29
+ "lib/rant/generator.rb",
30
+ "lib/rant/silly.rb",
31
+ "test/rant_test.rb",
32
+ "test/test_helper.rb"
33
+ ]
34
+ s.has_rdoc = true
35
+ s.homepage = %q{http://github.com/hayeah/rant}
36
+ s.rdoc_options = ["--charset=UTF-8"]
37
+ s.require_paths = ["lib"]
38
+ s.rubygems_version = %q{1.3.1}
39
+ s.summary = %q{Ruby Imperative Random Data Generator and Quickcheck}
40
+ s.test_files = [
41
+ "test/rant_test.rb",
42
+ "test/test_helper.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 2
48
+
49
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
+ else
51
+ end
52
+ else
53
+ end
54
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :build:
5
+ :patch: 0
data/lib/rant.rb ADDED
@@ -0,0 +1,7 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ class Rant
5
+ end
6
+
7
+ require 'rant/generator'
data/lib/rant/data.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Rant::Data
2
+ def email
3
+ "#{string(:alnum)}@#{string(:alnum)}.#{sized(3){string(:alpha)}}".downcase
4
+ end
5
+
6
+ def password
7
+ sized(8) { string(:alnum) }
8
+ end
9
+ end
10
+
11
+ class Rant
12
+ include Rant::Data
13
+ end
@@ -0,0 +1,263 @@
1
+ class Rant
2
+
3
+ class << self
4
+ attr_writer :default_size
5
+ def singleton
6
+ @singleton ||= Rant.new
7
+ @singleton
8
+ end
9
+
10
+ def default_size
11
+ @default_size || 6
12
+ end
13
+
14
+ def gen
15
+ self.singleton
16
+ end
17
+ end
18
+
19
+ class GuardFailure < RuntimeError
20
+ end
21
+
22
+ class TooManyTries < RuntimeError
23
+
24
+ def initialize(limit,nfailed)
25
+ @limit = limit
26
+ @nfailed = nfailed
27
+ end
28
+
29
+ def tries
30
+ @nfailed
31
+ end
32
+
33
+ def to_s
34
+ "Exceed gen limit #{@limit}: #{@nfailed} failed guards)"
35
+ end
36
+ end
37
+
38
+ # limit attempts to 10 times of how many things we want to generate
39
+ def each(n,limit=10,&block)
40
+ generate(n,limit,block)
41
+ end
42
+
43
+ def map(n,limit=10,&block)
44
+ acc = []
45
+ generate(n,limit,block) do |val|
46
+ acc << val
47
+ end
48
+ acc
49
+ end
50
+
51
+ def value(limit=10,&block)
52
+ generate(1,limit,block) do |val|
53
+ return val
54
+ end
55
+ end
56
+
57
+ def generate(n,limit_arg,gen_block,&handler)
58
+ limit = n * limit_arg
59
+ nfailed = 0
60
+ nsuccess = 0
61
+ while nsuccess < n
62
+ raise TooManyTries.new(limit_arg*n,nfailed) if limit < 0
63
+ begin
64
+ val = self.instance_eval(&gen_block)
65
+ rescue GuardFailure
66
+ nfailed += 1
67
+ limit -= 1
68
+ next
69
+ end
70
+ nsuccess += 1
71
+ limit -= 1
72
+ handler.call(val) if handler
73
+ end
74
+ end
75
+
76
+ attr_accessor :classifiers
77
+
78
+ def initialize
79
+ reset
80
+ end
81
+
82
+ def reset
83
+ @size = nil
84
+ @classifiers = Hash.new(0)
85
+ end
86
+
87
+ def classify(classifier)
88
+ @classifiers[classifier] += 1
89
+ end
90
+
91
+ def guard(test)
92
+ raise GuardFailure.new unless test
93
+ end
94
+
95
+ def size
96
+ @size || Rant.default_size
97
+ end
98
+
99
+ def sized(n,&block)
100
+ raise "size needs to be greater than zero" if n < 0
101
+ old_size = @size
102
+ @size = n
103
+ r = self.instance_eval(&block)
104
+ @size = old_size
105
+ return r
106
+ end
107
+
108
+ # wanna avoid going into Bignum when calling range with these.
109
+ INTEGER_MAX = (2**(0.size * 8 -2) -1) / 2
110
+ INTEGER_MIN = -(INTEGER_MAX)
111
+ def integer(limit=nil)
112
+ case limit
113
+ when Range
114
+ hi = limit.end
115
+ lo = limit.begin
116
+ when Integer
117
+ raise "n should be greater than zero" if limit < 0
118
+ hi, lo = limit, -limit
119
+ else
120
+ hi, lo = INTEGER_MAX, INTEGER_MIN
121
+ end
122
+ range(lo,hi)
123
+ end
124
+
125
+ def positive_integer
126
+ range(0)
127
+ end
128
+
129
+ def float
130
+ rand
131
+ end
132
+
133
+ def range(lo=nil,hi=nil)
134
+ lo ||= INTEGER_MIN
135
+ hi ||= INTEGER_MAX
136
+ rand(hi+1-lo) + lo
137
+ end
138
+
139
+ def call(gen,*args)
140
+ case gen
141
+ when Symbol
142
+ return self.send(gen,*args)
143
+ when Array
144
+ raise "empty array" if gen.empty?
145
+ return self.send(gen[0],*gen[1..-1])
146
+ when Proc
147
+ return self.instance_eval(&gen)
148
+ else
149
+ raise "don't know how to call type: #{gen}"
150
+ end
151
+ end
152
+
153
+ def branch(*gens)
154
+ self.call(choose(*gens))
155
+ end
156
+
157
+ def choose(*vals)
158
+ vals[range(0,vals.length-1)]
159
+ end
160
+
161
+ def literal(value)
162
+ value
163
+ end
164
+
165
+ def boolean
166
+ range(0,1) == 0 ? true : false
167
+ end
168
+
169
+ def freq(*pairs)
170
+ pairs = pairs.map do |pair|
171
+ case pair
172
+ when Symbol, String, Proc
173
+ [1,pair]
174
+ when Array
175
+ unless pair.first.is_a?(Integer)
176
+ [1] + pair
177
+ else
178
+ pair
179
+ end
180
+ end
181
+ end
182
+ total = pairs.inject(0) { |sum,p| sum + p.first }
183
+ raise(RuntimeError, "Illegal frequency:#{pairs.inspect}") if total == 0
184
+ pos = range(1,total)
185
+ pairs.each do |p|
186
+ weight, gen, *args = p
187
+ if pos <= p[0]
188
+ return self.call(gen,*args)
189
+ else
190
+ pos -= weight
191
+ end
192
+ end
193
+ end
194
+
195
+ def array(*freq_pairs)
196
+ acc = []
197
+ self.size.times { acc << freq(*freq_pairs) }
198
+ acc
199
+ end
200
+
201
+ module Chars
202
+
203
+ class << self
204
+ ASCII = ""
205
+ (0..127).to_a.each do |i|
206
+ ASCII << i
207
+ end
208
+
209
+ def of(regexp)
210
+ ASCII.scan(regexp).to_a.map! { |char| char[0] }
211
+ end
212
+ end
213
+
214
+ ALNUM = Chars.of /[[:alnum:]]/
215
+ ALPHA = Chars.of /[[:alpha:]]/
216
+ BLANK = Chars.of /[[:blank:]]/
217
+ CNTRL = Chars.of /[[:cntrl:]]/
218
+ DIGIT = Chars.of /[[:digit:]]/
219
+ GRAPH = Chars.of /[[:graph:]]/
220
+ LOWER = Chars.of /[[:lower:]]/
221
+ PRINT = Chars.of /[[:print:]]/
222
+ PUNCT = Chars.of /[[:punct:]]/
223
+ SPACE = Chars.of /[[:space:]]/
224
+ UPPER = Chars.of /[[:upper:]]/
225
+ XDIGIT = Chars.of /[[:xdigit:]]/
226
+ ASCII = Chars.of /./
227
+
228
+
229
+ CLASSES = {
230
+ :alnum => ALNUM,
231
+ :alpha => ALPHA,
232
+ :blank => BLANK,
233
+ :cntrl => CNTRL,
234
+ :digit => DIGIT,
235
+ :graph => GRAPH,
236
+ :lower => LOWER,
237
+ :print => PRINT,
238
+ :punct => PUNCT,
239
+ :space => SPACE,
240
+ :upper => UPPER,
241
+ :xdigit => XDIGIT,
242
+ :ascii => ASCII,
243
+ }
244
+
245
+ end
246
+
247
+ def string(char_class=:print)
248
+ chars = case char_class
249
+ when Regexp
250
+ Chars.of(char_class)
251
+ when Symbol
252
+ Chars::CLASSES[char_class]
253
+ end
254
+ raise "bad arg" unless chars
255
+ str = ""
256
+ size.times do
257
+ str << choose(*chars)
258
+ end
259
+ str
260
+ end
261
+ end
262
+
263
+
@@ -0,0 +1,50 @@
1
+ require 'rant'
2
+ require 'test/unit'
3
+ require 'pp'
4
+
5
+ class Rant::Property
6
+
7
+ def initialize(property)
8
+ @property = property
9
+ end
10
+
11
+ def check(n=100,limit=10,&assertion)
12
+ i = 0
13
+ test_data = nil
14
+ begin
15
+ Rant.singleton.generate(n,limit,@property) do |val|
16
+ test_data = val
17
+ assertion.call(val) if assertion
18
+ puts "" if i % 100 == 0
19
+ print "." if i % 10 == 0
20
+ i += 1
21
+ end
22
+ puts
23
+ puts "success: #{i} tests"
24
+ rescue Rant::TooManyTries => e
25
+ puts
26
+ puts "too many tries: #{e.tries}"
27
+ raise e
28
+ rescue => boom
29
+ puts
30
+ puts "failure: #{i} tests, on:"
31
+ pp test_data
32
+ raise boom
33
+ end
34
+ end
35
+
36
+ def report
37
+ distribs = self.classifiers.sort { |a,b| b[1] <=> a[1] }
38
+ total = distribs.inject(0) { |sum,pair| sum + pair[1]}
39
+ distribs.each do |(classifier,count)|
40
+ format "%10.5f%% of => %s", count, classifier
41
+ end
42
+ end
43
+ end
44
+
45
+ module Test::Unit::Assertions
46
+ def property_of(&block)
47
+ Rant::Property.new(block)
48
+ end
49
+ end
50
+
data/lib/rant/silly.rb ADDED
@@ -0,0 +1,116 @@
1
+ require 'rant'
2
+ module Rant::Silly
3
+ class << self
4
+ def love_letter(n)
5
+ Rant.new.extend(Rant::Silly::Love).value { letter(n) }
6
+ end
7
+ end
8
+ end
9
+
10
+ module Rant::Silly::Love
11
+
12
+ def letter(n=3)
13
+ body = sized(n) { array(:paragraph) }.join "\n\n"
14
+ <<-EOS
15
+ #{address}:
16
+
17
+ #{body}
18
+
19
+ #{sign}
20
+
21
+ #{post_script}
22
+ EOS
23
+ end
24
+
25
+ def address
26
+ "my #{extremifier} #{pedestal_label}"
27
+ end
28
+
29
+ def extremifier
30
+ choose "most","ultimate","unbelievable","incredible","burning"
31
+ end
32
+
33
+ def pedestal_label
34
+ choose "beloved","desire","dove","virgin goddess","existential solution","lighthouse","beacon","holy mother","queen","mistress"
35
+ end
36
+
37
+ def double_plus_good
38
+ choose "holy","shiny","glittering","joyous","delicious"
39
+ end
40
+
41
+ def how_i_feel
42
+ choose "my heart aches","my spine pines","my spirit wanders and wonders","my soul is awed","my loin burns"
43
+ end
44
+
45
+ def paragraph
46
+ sized(range(2,4)) { array(:sentence)}.join " "
47
+ end
48
+
49
+ def sentence
50
+ freq \
51
+ Proc.new { "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}." },
52
+ Proc.new { "because you are my #{pedestal_label}, and i am your #{whoami}, no, rather your #{whoami}, #{fragment}."},
53
+ Proc.new { "do not think that saying '#{how_i_feel}' suffices to show the depth of how #{how_i_feel}, because more than that, #{fantasy}"},
54
+ Proc.new { "as a #{whoami}, that #{how_i_feel} is never quite enough for you, my #{double_plus_good} #{pedestal_label}."}
55
+ end
56
+
57
+ def fragment
58
+ fun = fantasy
59
+ choose "i hope to god #{fun}", "i believe #{fun}", "i will that #{fun}"
60
+ end
61
+
62
+ def caused_by
63
+
64
+ end
65
+
66
+ def whoami
67
+ "#{extremifier} #{humbleizer} #{groveler}"
68
+ end
69
+
70
+ def sign
71
+ "your #{whoami}"
72
+ end
73
+
74
+ def humbleizer
75
+ choose "undeserving","insignificant","unremarkable","fearful","menial"
76
+ end
77
+
78
+ def groveler
79
+ choose "slave","servant","captive","lapdog"
80
+ end
81
+
82
+ def post_script
83
+ "ps: #{i_am_stalking_you}, and hope that #{fantasy}"
84
+ end
85
+
86
+ def i_am_stalking_you
87
+ "every #{time_duration} i #{stalk_action}"
88
+ end
89
+
90
+ def fantasy
91
+ freq \
92
+ Proc.new {
93
+ make = choose "raise","nurture","bring into the world"
94
+ babies = choose "brood of babies","#{double_plus_good} angels"
95
+ good = double_plus_good
96
+ effect = choose "the world becomes all the more #{good}",
97
+ "we may at the end of our lives rest in #{good} peace.",
98
+ "you, my #{pedestal_label}, would continue to live."
99
+ "we would #{make} #{babies}, so #{effect}."
100
+ },
101
+ Proc.new {
102
+ 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"
103
+ affect = choose "joy", "mindfulness", "calm", "sanctity"
104
+ "we would #{do_thing} with #{double_plus_good} #{affect}"
105
+ }
106
+ end
107
+
108
+ def stalk_action
109
+ choose "think of you","dream of us together","look at your picture and sigh"
110
+ end
111
+
112
+ def time_duration
113
+ choose "once in a while","night","day","hour","minute"
114
+ end
115
+ end
116
+
data/lib/rant/spec.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rant'
2
+ module Rant::Check
3
+ def check(n=100,&block)
4
+ Rant.gen.each(n,&block)
5
+ end
6
+
7
+ def sample(n=100,&block)
8
+ Rant.gen.map(n,&block)
9
+ end
10
+ end
data/rantly.gemspec ADDED
@@ -0,0 +1,56 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rantly}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Howard Yeh"]
12
+ s.date = %q{2009-11-30}
13
+ s.email = %q{hayeah@gmail.com}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.textile"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.textile",
23
+ "Rakefile",
24
+ "Rant.gemspec",
25
+ "VERSION.yml",
26
+ "lib/rant.rb",
27
+ "lib/rant/data.rb",
28
+ "lib/rant/generator.rb",
29
+ "lib/rant/property.rb",
30
+ "lib/rant/silly.rb",
31
+ "lib/rant/spec.rb",
32
+ "rantly.gemspec",
33
+ "test/rant_test.rb",
34
+ "test/test_helper.rb"
35
+ ]
36
+ s.homepage = %q{http://github.com/hayeah/rantly}
37
+ s.rdoc_options = ["--charset=UTF-8"]
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = %q{1.3.5}
40
+ s.summary = %q{Ruby Imperative Random Data Generator and Quickcheck}
41
+ s.test_files = [
42
+ "test/rant_test.rb",
43
+ "test/test_helper.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
+ else
52
+ end
53
+ else
54
+ end
55
+ end
56
+
data/test/rant_test.rb ADDED
@@ -0,0 +1,284 @@
1
+ require 'test_helper'
2
+ require 'rant/check'
3
+
4
+ module RantTest
5
+ end
6
+
7
+ # check we generate the right kind of data.
8
+ ## doesn't check for distribution
9
+ class RantTest::Generator < Test::Unit::TestCase
10
+ def setup
11
+ Rant.gen.reset
12
+ end
13
+
14
+ should "fail test generation" do
15
+ assert_raises(Rant::TooManyTries) {
16
+ property_of { guard range(0,1) < 0 }.check
17
+ }
18
+ end
19
+
20
+ should "generate literal value by returning itself" do
21
+ property_of {
22
+ i = integer
23
+ [i,literal(i)]
24
+ }.check { |(a,b)|
25
+ assert_equal a, b
26
+ }
27
+ end
28
+
29
+ should "generate integer in range" do
30
+ property_of {
31
+ i = integer
32
+ [i,range(i,i)]
33
+ }.check { |(a,b)|
34
+ assert_equal a, b
35
+ }
36
+ property_of {
37
+ lo, hi = [integer(100),integer(100)].sort
38
+ [lo,hi,range(lo,hi)]
39
+ }.check { |(lo,hi,int)|
40
+ assert((lo..hi).include?(int))
41
+ }
42
+ end
43
+
44
+ should "generate Fixnum only" do
45
+ property_of { integer }.check { |i| assert i.is_a?(Integer) }
46
+ end
47
+
48
+ should "generate integer less than abs(n)" do
49
+ property_of {
50
+ n = range(0,10)
51
+ [n,integer(n)]
52
+ }.check {|(n,i)|
53
+ assert n.abs >= i.abs
54
+ }
55
+ end
56
+
57
+ should "generate Float" do
58
+ property_of { float }.check { |f| assert f.is_a?(Float)}
59
+ end
60
+
61
+ should "generate Boolean" do
62
+ property_of { bool }.check { |t|
63
+ assert t == true || t == false
64
+ }
65
+ end
66
+
67
+ should "generate empty strings" do
68
+ property_of {
69
+ sized(0) { string }
70
+ }.check { |s|
71
+ assert s.empty?
72
+ }
73
+ end
74
+
75
+ should "generate strings with the right regexp char classes" do
76
+ char_classes = Rant::Chars::CLASSES.keys
77
+ property_of {
78
+ char_class = choose(*char_classes)
79
+ len = range(0,10)
80
+ sized(len) { [len,char_class,string(char_class)]}
81
+ }.check { |(len,char_class,str)|
82
+ t = true
83
+ chars = Rant::Chars::CLASSES[char_class]
84
+ str.each_byte { |c|
85
+ unless chars.include?(c)
86
+ t = false
87
+ break
88
+ end
89
+ }
90
+ assert_equal len, str.length
91
+ assert t
92
+ }
93
+ end
94
+
95
+ should "generate strings matching regexp" do
96
+ property_of {
97
+ sized(10) { string(/[abcd]/) }
98
+ }.check { |s|
99
+ assert s =~ /[abcd]+/
100
+ }
101
+ end
102
+
103
+ # call
104
+
105
+ should "call Symbol as method call (no arg)" do
106
+ property_of {call(:integer)}.check { |i| i.is_a?(Integer)}
107
+ end
108
+
109
+ should "call Symbol as method call (with arg)" do
110
+ property_of {
111
+ n = range(0,100)
112
+ [n,call(:integer,n)]
113
+ }.check { |(n,i)|
114
+ assert n.abs >= i.abs
115
+ }
116
+ end
117
+
118
+ should "call Array by calling first element as method, the rest as args" do
119
+ assert_raise(RuntimeError) {
120
+ Rant.gen.value {
121
+ call []
122
+ }
123
+ }
124
+ property_of {
125
+ i = integer
126
+ [i,call(choose([:literal,i],[:range,i,i]))]
127
+ }.check { |(a,b)|
128
+ assert_equal a, b
129
+ }
130
+ end
131
+
132
+ should "call Proc with generator.instance_eval" do
133
+ property_of {
134
+ call Proc.new { true }
135
+ }.check { |o|
136
+ assert_equal true, o
137
+ }
138
+ property_of {
139
+ i0 = range(0,100)
140
+ i1,s = call Proc.new {
141
+ range(i0+1,i0+100)
142
+ }
143
+ [i0,i1]
144
+ }.check { |(i0,i1)|
145
+ assert i0.is_a?(Fixnum) && i1.is_a?(Fixnum)
146
+ assert i0 != i1
147
+ assert i1 > i0
148
+ }
149
+ end
150
+
151
+ should "raise if calling on any other value" do
152
+ assert_raise(RuntimeError) {
153
+ Rant.gen.call 0
154
+ }
155
+ end
156
+
157
+ # branch
158
+
159
+ should "branch by Rant#calling one of the args" do
160
+ property_of {
161
+ branch :integer, :integer, :integer
162
+ }.check { |o|
163
+ assert o.is_a?(Fixnum)
164
+ }
165
+ property_of {
166
+ sized(10) { branch :integer, :string }
167
+ }.check { |o|
168
+ assert o.is_a?(Fixnum) || o.is_a?(String)
169
+ }
170
+ end
171
+
172
+
173
+ # choose
174
+
175
+ should "choose a value from args " do
176
+ property_of {
177
+ choose
178
+ }.check {|o|
179
+ assert_nil o
180
+ }
181
+ property_of {
182
+ choose 1
183
+ }.check { |o|
184
+ assert_equal 1, o
185
+ }
186
+ property_of {
187
+ choose 1,2
188
+ }.check { |o|
189
+ assert o == 1 || o == 2
190
+ }
191
+ property_of {
192
+ arr = sized(10) { array(:integer) }
193
+ choose(*arr)
194
+ }.check { |o|
195
+ assert o.is_a?(Fixnum)
196
+ }
197
+ property_of {
198
+ # array of array of ints
199
+ arr = sized(10) { array(Proc.new { array(:integer)})}
200
+ # choose an array from an array of arrays of ints
201
+ choose(*arr)
202
+ }.check { |arr|
203
+ assert arr.is_a?(Array)
204
+ assert arr.all? { |o| o.is_a?(Fixnum)}
205
+ }
206
+ end
207
+
208
+ # freq
209
+
210
+ should "not pick an element with 0 frequency" do
211
+ property_of {
212
+ sized(10) {
213
+ array Proc.new { freq([0,:string],[1,:integer]) }
214
+ }
215
+ }.check { |arr|
216
+ assert arr.all? { |o| o.is_a?(Integer)}
217
+ }
218
+ end
219
+
220
+ should "handle degenerate freq pairs" do
221
+ assert_raise(RuntimeError) {
222
+ Rant.gen.value {
223
+ freq
224
+ }
225
+ }
226
+ property_of {
227
+ i = integer
228
+ [i,freq([:literal,i])]
229
+ }.check { |(a,b)|
230
+ assert_equal a, b
231
+ }
232
+ end
233
+
234
+ # array
235
+
236
+ should "generate empty array" do
237
+ property_of {
238
+ sized(0) { array(:integer)}
239
+ }.check { |o|
240
+ assert o.empty?
241
+ }
242
+ end
243
+
244
+ should "generate the right sized nested arrays" do
245
+ property_of {
246
+ size1 = range(5,10)
247
+ size2 = range(0,size1-1)
248
+ array = sized(size1) { array(Proc.new { sized(size2) { array(:integer)}})}
249
+ [size1,array]
250
+ }.check { |(size1,outter_array)|
251
+ assert_equal size1, outter_array.size
252
+ assert outter_array.all? { |inner_array| inner_array.size < size1 }
253
+ }
254
+ end
255
+
256
+ should "generate array with right types" do
257
+ property_of {
258
+ sized(10) { array :integer,:string,:float }
259
+ }.check { |arr|
260
+ assert arr.all? { |o|
261
+ case o
262
+ when Fixnum, Float, String
263
+ true
264
+ else
265
+ false
266
+ end
267
+ }
268
+ }
269
+ end
270
+
271
+ should "raise if generating an array without size" do
272
+ assert_raise(RuntimeError) {
273
+ Rant.gen.value { array(:integer) }
274
+ }
275
+ end
276
+
277
+ end
278
+
279
+
280
+
281
+ # TODO: check that distributions of different methods look roughly correct.
282
+ class RantTest::Distribution
283
+
284
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'rant'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rantly
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Howard Yeh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-30 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: hayeah@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.textile
25
+ files:
26
+ - .document
27
+ - .gitignore
28
+ - LICENSE
29
+ - README.textile
30
+ - Rakefile
31
+ - Rant.gemspec
32
+ - VERSION.yml
33
+ - lib/rant.rb
34
+ - lib/rant/data.rb
35
+ - lib/rant/generator.rb
36
+ - lib/rant/property.rb
37
+ - lib/rant/silly.rb
38
+ - lib/rant/spec.rb
39
+ - rantly.gemspec
40
+ - test/rant_test.rb
41
+ - test/test_helper.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/hayeah/rantly
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --charset=UTF-8
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.5
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Ruby Imperative Random Data Generator and Quickcheck
70
+ test_files:
71
+ - test/rant_test.rb
72
+ - test/test_helper.rb