rantly 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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