hirsute 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.
- checksums.yaml +7 -0
- data/MIT_LICENSE +7 -0
- data/README.md +105 -0
- data/bin/hirsute +14 -0
- data/lib/hirsute.rb +110 -0
- data/lib/hirsute_collection.rb +67 -0
- data/lib/hirsute_constraint.rb +17 -0
- data/lib/hirsute_fixed.rb +17 -0
- data/lib/hirsute_generator.rb +140 -0
- data/lib/hirsute_make_generators.rb +125 -0
- data/lib/hirsute_output.rb +142 -0
- data/lib/hirsute_template.rb +159 -0
- data/lib/hirsute_test.rb +25 -0
- data/lib/hirsute_utils.rb +108 -0
- data/lib/histoparse.rb +47 -0
- data/manual.md +184 -0
- data/samples/readme.hrs +44 -0
- data/samples/wine_cellar.hrs +145 -0
- data/tests/first_names.txt +8 -0
- data/tests/hirsute_test.rb +362 -0
- data/tests/histoparse_test.rb +32 -0
- metadata +72 -0
@@ -0,0 +1,362 @@
|
|
1
|
+
# unit tests for the hirsute language
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'lib/hirsute_utils.rb'
|
5
|
+
require 'lib/hirsute.rb'
|
6
|
+
require 'lib/hirsute_make_generators.rb'
|
7
|
+
require 'lib/hirsute_collection.rb'
|
8
|
+
|
9
|
+
class TestHirsute < Test::Unit::TestCase
|
10
|
+
include Hirsute::GeneratorMakers
|
11
|
+
include Hirsute::Support
|
12
|
+
|
13
|
+
# test functionality of the histogram distribution
|
14
|
+
def testIntegerFromHistogram1
|
15
|
+
|
16
|
+
# define a very skewed histogram
|
17
|
+
histogram_a = [0.9,0.05,0.05]
|
18
|
+
values_a = Array.new(histogram_a.length,0)
|
19
|
+
|
20
|
+
(1..1000).each do |i|
|
21
|
+
index = integer_from_histogram(histogram_a)
|
22
|
+
values_a[index] = values_a[index] + 1
|
23
|
+
end
|
24
|
+
|
25
|
+
# check for 5% tolerance
|
26
|
+
assert(values_a[0] > 855 && values_a[0] < 945)
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def testHistogramGreaterThanList
|
31
|
+
begin
|
32
|
+
random_item_with_histogram([1,2],[1,2,3,4])
|
33
|
+
fail
|
34
|
+
rescue Exception => e
|
35
|
+
assert(true)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def testHistogramDoesntEqualOne
|
40
|
+
histogram = [0.2,0.1]
|
41
|
+
values = Array.new
|
42
|
+
|
43
|
+
(1..1000).each do |i|
|
44
|
+
values << random_item_with_histogram([1,2],histogram)
|
45
|
+
end
|
46
|
+
|
47
|
+
one_values = values.select {|item| item == 1}
|
48
|
+
two_values = values.select {|item| item == 2}
|
49
|
+
|
50
|
+
assert(one_values.length > 667 * 0.95 && one_values.length < 667 * 1.05)
|
51
|
+
assert(two_values.length > 333 * 0.95 && two_values.length < 333 * 1.05)
|
52
|
+
end
|
53
|
+
|
54
|
+
def testOneOfWithHistogram
|
55
|
+
results = []
|
56
|
+
list = ["a","b","c"]
|
57
|
+
histogram = [0.9,0.05,0.05]
|
58
|
+
gen = one_of(list,histogram)
|
59
|
+
(1..1000).each do |i|
|
60
|
+
results << gen.generate(nil)
|
61
|
+
end
|
62
|
+
|
63
|
+
a_count = (results.select {|item| item == 'a'}).length
|
64
|
+
assert(a_count > 855 && a_count < 945)
|
65
|
+
end
|
66
|
+
|
67
|
+
def testGeneratorBlockRunWithinInstance
|
68
|
+
template = make_template("testGeneratorBlockRunWithinInstance") {
|
69
|
+
has :id => counter(3),
|
70
|
+
:triple => depending_on(:id,
|
71
|
+
Hirsute::DEFAULT => "") {|result| id * 3}
|
72
|
+
}
|
73
|
+
obj = template.make
|
74
|
+
assert(obj.triple == (obj.id * 3))
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def testOneOfGenerator
|
79
|
+
|
80
|
+
domains = ["gmail.com","yahoo.com","ea.com"]
|
81
|
+
domain = one_of(domains).generate(nil)
|
82
|
+
assert(domain == 'gmail.com' || domain == 'yahoo.com' || domain = 'ea.com')
|
83
|
+
end
|
84
|
+
|
85
|
+
def testFileRead
|
86
|
+
gen = read_from_file('tests/first_names.txt',:linear)
|
87
|
+
line = gen.generate(nil)
|
88
|
+
assert(line == 'Derrick')
|
89
|
+
|
90
|
+
# toss the rest
|
91
|
+
(1..7).each do |i|
|
92
|
+
line = gen.generate(nil)
|
93
|
+
end
|
94
|
+
|
95
|
+
line = gen.generate(nil) # should have wrapped around
|
96
|
+
assert(line == 'Derrick')
|
97
|
+
end
|
98
|
+
|
99
|
+
def testCollectionCreationWithObject
|
100
|
+
coll = Hirsute::Collection.new("String")
|
101
|
+
begin
|
102
|
+
coll << 3
|
103
|
+
flunk "Collection should not allow an inconsistent type"
|
104
|
+
rescue Exception => e
|
105
|
+
assert(coll.length == 0)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def testCollectionCreationWithoutObject
|
110
|
+
coll = Hirsute::Collection.new('fixnum')
|
111
|
+
coll << 3
|
112
|
+
begin
|
113
|
+
coll << "test"
|
114
|
+
flunk "Strings should not be allowed in a collection created as an integer"
|
115
|
+
rescue
|
116
|
+
assert(coll.length == 1)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def testCollectionChoice
|
121
|
+
coll = Hirsute::Collection.new("String")
|
122
|
+
coll << "a"
|
123
|
+
coll << "b"
|
124
|
+
coll << "c"
|
125
|
+
|
126
|
+
str = one_of(coll).generate(nil)
|
127
|
+
assert(str=='a' || str == 'b' || str == 'c')
|
128
|
+
end
|
129
|
+
|
130
|
+
def testPostGenerateBlockExecution
|
131
|
+
list = ['abc','apple','asparagus']
|
132
|
+
gen = one_of(list) {|value| value[0,1]}
|
133
|
+
result = gen.generate(nil)
|
134
|
+
assert(result == 'a')
|
135
|
+
end
|
136
|
+
|
137
|
+
# ensure that when you create a collection for an object, that it registers itself as a holder of that object type
|
138
|
+
def testCollectionsRegisterForObject
|
139
|
+
objName = 'testObj'
|
140
|
+
|
141
|
+
#setup, copied from hirsute.rb
|
142
|
+
objClass = Class.new(Hirsute::Fixed)
|
143
|
+
objClassName = Kernel.const_set(objName.capitalize.to_sym,objClass)
|
144
|
+
|
145
|
+
template = Hirsute::Template.new(objName)
|
146
|
+
|
147
|
+
coll1 = template * 2
|
148
|
+
coll2 = template * 3
|
149
|
+
all_colls = Hirsute::Collection.collections_holding_object(objName)
|
150
|
+
assert(all_colls.length == 2)
|
151
|
+
end
|
152
|
+
|
153
|
+
# tests that is_template works
|
154
|
+
def testIsTemplate
|
155
|
+
testObj2 = Hirsute::Template.new('testObj2')
|
156
|
+
assert(is_template(testObj2))
|
157
|
+
end
|
158
|
+
|
159
|
+
def testCollectionRejectsDifferentObject
|
160
|
+
template1 = make_template('testCollectionRejectsDifferentObject1')
|
161
|
+
template2 = make_template('testCollectionRejectsDifferentObject2')
|
162
|
+
|
163
|
+
coll1 = template1 * 2
|
164
|
+
|
165
|
+
begin
|
166
|
+
coll1 << template2
|
167
|
+
assert(false)
|
168
|
+
rescue
|
169
|
+
assert(true)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def testMakeAddsToSingleCollection
|
174
|
+
template = make_template('testMakeAddsToSingleCollection')
|
175
|
+
coll = collection_of template
|
176
|
+
template.make
|
177
|
+
assert(coll.length == 1)
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
# ensure that the << operator works properly when appending a template (i.e., it makes a new object rather than appending the template)
|
182
|
+
def testAppendWithTemplate
|
183
|
+
testObj3 = make_template('testObj3') {
|
184
|
+
has :id => counter(1)
|
185
|
+
}
|
186
|
+
|
187
|
+
coll = testObj3 * 1
|
188
|
+
coll << testObj3
|
189
|
+
|
190
|
+
# either line would have raised an exception if the collection thought it was an invalid type (see test above)
|
191
|
+
assert(true)
|
192
|
+
end
|
193
|
+
|
194
|
+
def testNestedGenerators
|
195
|
+
template = make_template('testNestedGenerators') {
|
196
|
+
has :id => one_of([one_of([1,2,3]),one_of([4,5,6])])
|
197
|
+
}
|
198
|
+
obj = template.make
|
199
|
+
assert(obj.id == 1 || obj.id == 2 || obj.id == 3 || obj.id == 4 || obj.id == 5 || obj.id == 6)
|
200
|
+
end
|
201
|
+
|
202
|
+
def testSubset
|
203
|
+
template = make_template('testSubset') {
|
204
|
+
has :item => subset(one_of([1,2,3]),
|
205
|
+
one_of(['a','b','c']),
|
206
|
+
one_of([4,5,6]))
|
207
|
+
}
|
208
|
+
obj = template.make
|
209
|
+
assert(obj.item.length <= 3 && obj.item.length > 0)
|
210
|
+
end
|
211
|
+
|
212
|
+
def testAppendCollectionToCollection
|
213
|
+
template = make_template('testAppendCollectionToCollection')
|
214
|
+
coll1 = template * 3
|
215
|
+
coll2 = template * 4
|
216
|
+
coll1 << coll2
|
217
|
+
assert(coll1.length == 7)
|
218
|
+
end
|
219
|
+
|
220
|
+
def testAnyObject
|
221
|
+
template = make_template('testAnyObject') {
|
222
|
+
has :objid => counter(1)
|
223
|
+
}
|
224
|
+
coll1 = template * 3
|
225
|
+
coll2 = template * 4
|
226
|
+
greaterThan5 = any(template) {|item| item.objid > 5}
|
227
|
+
assert(greaterThan5.objid > 5)
|
228
|
+
|
229
|
+
equals2 = any(template) do |item|
|
230
|
+
item.objid == 2
|
231
|
+
end
|
232
|
+
assert(equals2.objid == 2)
|
233
|
+
end
|
234
|
+
|
235
|
+
def testReadFromSequence
|
236
|
+
template = make_template('testReadFromSequence') {
|
237
|
+
has :looper => read_from_sequence([1,2,3,4])
|
238
|
+
}
|
239
|
+
|
240
|
+
# 5 objects should exercise the loop of 4 items
|
241
|
+
template1 = template.make
|
242
|
+
template2 = template.make
|
243
|
+
template3 = template.make
|
244
|
+
template4 = template.make
|
245
|
+
template5 = template.make
|
246
|
+
|
247
|
+
assert(template4.looper == 4)
|
248
|
+
assert(template5.looper == template1.looper)
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
def testDependentGeneratorCircularDependencyException
|
253
|
+
template = make_template("testDependentGeneratorCircularDependencyException") {
|
254
|
+
has :a => depending_on(:b, 3 => 3),
|
255
|
+
:b => depending_on(:a, 4 => 4)
|
256
|
+
}
|
257
|
+
begin
|
258
|
+
template.make
|
259
|
+
fail
|
260
|
+
rescue
|
261
|
+
assert(true)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def testDependencyBasics
|
266
|
+
template = make_template("testDependencyBasics") {
|
267
|
+
has :value => read_from_sequence([1,2,3]),
|
268
|
+
:name => depending_on(:value,
|
269
|
+
1 => 'a')
|
270
|
+
}
|
271
|
+
template1 = template.make
|
272
|
+
template2 = template.make
|
273
|
+
assert(template1.name == 'a')
|
274
|
+
assert(template2.name.nil?)
|
275
|
+
end
|
276
|
+
|
277
|
+
def testDependencyBasicsWithDefault
|
278
|
+
template = make_template("testDependencyBasicsWithDefault") {
|
279
|
+
has :value => read_from_sequence([1,2,3]),
|
280
|
+
:name => depending_on(:value,
|
281
|
+
1 => 'a',
|
282
|
+
Hirsute::DEFAULT => 'z')
|
283
|
+
}
|
284
|
+
|
285
|
+
template1 = template.make
|
286
|
+
template2 = template.make
|
287
|
+
template3 = template.make
|
288
|
+
|
289
|
+
assert(template1.value == 1 && template1.name == 'a')
|
290
|
+
assert(template2.value == 2 && template2.name == 'z')
|
291
|
+
assert(template3.value == 3 && template3.name == 'z')
|
292
|
+
end
|
293
|
+
|
294
|
+
def testRequiresField
|
295
|
+
template = make_template("testRequiresField") {
|
296
|
+
has :value => requires_field(:literal,counter(20)) {|value| self.literal = value},
|
297
|
+
:literal => 1
|
298
|
+
}
|
299
|
+
|
300
|
+
template1 = template.make
|
301
|
+
assert(template1.literal == template1.value)
|
302
|
+
end
|
303
|
+
|
304
|
+
def testRangeArrayCaching
|
305
|
+
range1 = 1..3
|
306
|
+
range2 = 1..3 # ensure that the same conceptual range maps to the same array
|
307
|
+
|
308
|
+
range3 = 1..4
|
309
|
+
|
310
|
+
ary = Hirsute::Support.get_range_array(range1)
|
311
|
+
ary2 = Hirsute::Support.get_range_array(range2)
|
312
|
+
ary3 = Hirsute::Support.get_range_array(range2)
|
313
|
+
ary4 = Hirsute::Support.get_range_array(range3)
|
314
|
+
assert(ary.equal?(ary2))
|
315
|
+
assert(ary.equal?(ary3))
|
316
|
+
assert(!ary.equal?(ary4))
|
317
|
+
end
|
318
|
+
|
319
|
+
def testRangeResultsBecomeElements
|
320
|
+
template = make_template("testRangeResultsBecomeElements") {
|
321
|
+
has :rating => one_of([1..10])
|
322
|
+
}
|
323
|
+
obj = template.make
|
324
|
+
assert(obj.rating >= 1 && obj.rating <= 10)
|
325
|
+
end
|
326
|
+
|
327
|
+
# just to measure that range caching is worth it
|
328
|
+
def testRangeCachingSpeed
|
329
|
+
iterations = 100000
|
330
|
+
range = 1..10
|
331
|
+
|
332
|
+
start = Time.new
|
333
|
+
(1..iterations).each {|i| range.to_a}
|
334
|
+
|
335
|
+
avg_to_a_time = (Time.new - start).to_f / iterations
|
336
|
+
|
337
|
+
start = Time.new
|
338
|
+
(1..iterations).each {|i| Hirsute::Support.get_range_array(range)}
|
339
|
+
avg_cached_time = (Time.new - start).to_f / iterations
|
340
|
+
|
341
|
+
assert(avg_cached_time < avg_to_a_time)
|
342
|
+
|
343
|
+
end
|
344
|
+
|
345
|
+
# for testing outputter behavior.
|
346
|
+
class TestOutputter < Hirsute::Outputter
|
347
|
+
def _outputItem(item)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def testFieldsInOutputter
|
352
|
+
# build up the collection
|
353
|
+
template = make_template("testFieldsInOutputter") {
|
354
|
+
has :id => counter(1)
|
355
|
+
}
|
356
|
+
collection = template * 4
|
357
|
+
|
358
|
+
outputter = TestOutputter.new(collection)
|
359
|
+
assert(outputter.fields[0] == :id)
|
360
|
+
end
|
361
|
+
|
362
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# unit tests for the histoparse subsystem of Hirsute
|
2
|
+
require 'test/unit'
|
3
|
+
require 'lib/histoparse.rb'
|
4
|
+
|
5
|
+
class TestHistoParse < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include Hirsute::HistoParse
|
8
|
+
|
9
|
+
def testBasicParsing
|
10
|
+
histogram = <<-HIST
|
11
|
+
**
|
12
|
+
|
13
|
+
****
|
14
|
+
|
15
|
+
| **
|
16
|
+
| *
|
17
|
+
| *
|
18
|
+
HIST
|
19
|
+
|
20
|
+
parsed_histogram = parse_histogram(histogram)
|
21
|
+
buckets = parsed_histogram.histogram_buckets
|
22
|
+
assert(!buckets.nil?)
|
23
|
+
|
24
|
+
assert(buckets[0] > 0.19 && buckets[0] < 0.21)
|
25
|
+
assert(buckets[1] > 0.39 && buckets[1] < 0.41)
|
26
|
+
assert(buckets[2] > 0.19 && buckets[2] < 0.21)
|
27
|
+
assert(buckets[3] > 0.09 && buckets[3] < 0.11)
|
28
|
+
assert(buckets[4] > 0.09 && buckets[4] < 0.11)
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hirsute
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Derrick Schneider
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2013-06-02 00:00:00 Z
|
13
|
+
dependencies: []
|
14
|
+
|
15
|
+
description: " Hirsute is a Ruby-based domain specific language for creating\n fake data based on a variety of techniques, including non-uniform\n probabilities to more closely emulate real-world data.\n (e.g., allow a person to have up to 100 friends in a social\n network but specify that most will have between ten and twenty.)\n"
|
16
|
+
email: derrick.schneider@gmail.com
|
17
|
+
executables:
|
18
|
+
- hirsute
|
19
|
+
extensions: []
|
20
|
+
|
21
|
+
extra_rdoc_files: []
|
22
|
+
|
23
|
+
files:
|
24
|
+
- ./lib/hirsute.rb
|
25
|
+
- ./lib/hirsute_collection.rb
|
26
|
+
- ./lib/hirsute_constraint.rb
|
27
|
+
- ./lib/hirsute_fixed.rb
|
28
|
+
- ./lib/hirsute_generator.rb
|
29
|
+
- ./lib/hirsute_make_generators.rb
|
30
|
+
- ./lib/hirsute_output.rb
|
31
|
+
- ./lib/hirsute_template.rb
|
32
|
+
- ./lib/hirsute_test.rb
|
33
|
+
- ./lib/hirsute_utils.rb
|
34
|
+
- ./lib/histoparse.rb
|
35
|
+
- ./tests/hirsute_test.rb
|
36
|
+
- ./tests/histoparse_test.rb
|
37
|
+
- ./manual.md
|
38
|
+
- ./README.md
|
39
|
+
- ./samples/readme.hrs
|
40
|
+
- ./samples/wine_cellar.hrs
|
41
|
+
- ./bin/hirsute
|
42
|
+
- ./tests/first_names.txt
|
43
|
+
- ./MIT_LICENSE
|
44
|
+
- bin/hirsute
|
45
|
+
homepage:
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
metadata: {}
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- &id001
|
58
|
+
- ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- *id001
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 2.0.3
|
68
|
+
signing_key:
|
69
|
+
specification_version: 4
|
70
|
+
summary: A DSL for creating fake but plausible data.
|
71
|
+
test_files: []
|
72
|
+
|