radagen 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +120 -0
- data/.rspec +3 -0
- data/.travis.yml +11 -0
- data/.yardopts +3 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +254 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/radagen/generator.rb +76 -0
- data/lib/radagen/version.rb +3 -0
- data/lib/radagen.rb +768 -0
- data/radagen.gemspec +26 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cb56ff6848be1ab3ca322e0c45067323eba296f6
|
4
|
+
data.tar.gz: 83a447038d9980169a5d206f17e083721ef40890
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c0f94c3d19faf7cc4fe6e2fb1fd4b154289fb3ce21ac4bb12056297f54ce2a9c5cf1b9e147bb13bad35ee607a8b143b85b9bccd2554e02e2f58caac0fe837fc2
|
7
|
+
data.tar.gz: 3f29be3edea5dd05fe6586247f591b358d6a61b15f68e654459a4a8cfefcce687ed41971ea77f9806aec4a16107a0825383026596562d764a12fefcce8d7876b
|
data/.gitignore
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# Created by https://www.gitignore.io/api/ruby,rubymine
|
2
|
+
|
3
|
+
### Ruby ###
|
4
|
+
*.gem
|
5
|
+
*.rbc
|
6
|
+
/.config
|
7
|
+
/coverage/
|
8
|
+
/InstalledFiles
|
9
|
+
/pkg/
|
10
|
+
/spec/reports/
|
11
|
+
/spec/examples.txt
|
12
|
+
/test/tmp/
|
13
|
+
/test/version_tmp/
|
14
|
+
/tmp/
|
15
|
+
|
16
|
+
# Used by dotenv library to load environment variables.
|
17
|
+
# .env
|
18
|
+
|
19
|
+
## Specific to RubyMotion:
|
20
|
+
.dat*
|
21
|
+
.repl_history
|
22
|
+
build/
|
23
|
+
*.bridgesupport
|
24
|
+
build-iPhoneOS/
|
25
|
+
build-iPhoneSimulator/
|
26
|
+
|
27
|
+
## Specific to RubyMotion (use of CocoaPods):
|
28
|
+
#
|
29
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
30
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
31
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
32
|
+
#
|
33
|
+
# vendor/Pods/
|
34
|
+
|
35
|
+
## Documentation cache and generated files:
|
36
|
+
/.yardoc/
|
37
|
+
/_yardoc/
|
38
|
+
/doc/
|
39
|
+
/rdoc/
|
40
|
+
|
41
|
+
## Environment normalization:
|
42
|
+
/.bundle/
|
43
|
+
/vendor/bundle
|
44
|
+
/lib/bundler/man/
|
45
|
+
|
46
|
+
# for a library or gem, you might want to ignore these files since the code is
|
47
|
+
# intended to run in multiple environments; otherwise, check them in:
|
48
|
+
Gemfile.lock
|
49
|
+
.ruby-version
|
50
|
+
.ruby-gemset
|
51
|
+
|
52
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
53
|
+
.rvmrc
|
54
|
+
|
55
|
+
### RubyMine ###
|
56
|
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
57
|
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
58
|
+
|
59
|
+
# User-specific stuff:
|
60
|
+
.idea
|
61
|
+
.idea/**/workspace.xml
|
62
|
+
.idea/**/tasks.xml
|
63
|
+
.idea/dictionaries
|
64
|
+
|
65
|
+
# Sensitive or high-churn files:
|
66
|
+
.idea/**/dataSources/
|
67
|
+
.idea/**/dataSources.ids
|
68
|
+
.idea/**/dataSources.xml
|
69
|
+
.idea/**/dataSources.local.xml
|
70
|
+
.idea/**/sqlDataSources.xml
|
71
|
+
.idea/**/dynamic.xml
|
72
|
+
.idea/**/uiDesigner.xml
|
73
|
+
|
74
|
+
# Gradle:
|
75
|
+
.idea/**/gradle.xml
|
76
|
+
.idea/**/libraries
|
77
|
+
|
78
|
+
# CMake
|
79
|
+
cmake-build-debug/
|
80
|
+
|
81
|
+
# Mongo Explorer plugin:
|
82
|
+
.idea/**/mongoSettings.xml
|
83
|
+
|
84
|
+
## File-based project format:
|
85
|
+
*.iws
|
86
|
+
|
87
|
+
## Plugin-specific files:
|
88
|
+
|
89
|
+
# IntelliJ
|
90
|
+
/out/
|
91
|
+
|
92
|
+
# mpeltonen/sbt-idea plugin
|
93
|
+
.idea_modules/
|
94
|
+
|
95
|
+
# JIRA plugin
|
96
|
+
atlassian-ide-plugin.xml
|
97
|
+
|
98
|
+
# Cursive Clojure plugin
|
99
|
+
.idea/replstate.xml
|
100
|
+
|
101
|
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
102
|
+
com_crashlytics_export_strings.xml
|
103
|
+
crashlytics.properties
|
104
|
+
crashlytics-build.properties
|
105
|
+
fabric.properties
|
106
|
+
|
107
|
+
### RubyMine Patch ###
|
108
|
+
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
109
|
+
|
110
|
+
# *.iml
|
111
|
+
# modules.xml
|
112
|
+
# .idea/misc.xml
|
113
|
+
# *.ipr
|
114
|
+
|
115
|
+
# Sonarlint plugin
|
116
|
+
.idea/sonarlint
|
117
|
+
|
118
|
+
# End of https://www.gitignore.io/api/ruby,rubymine
|
119
|
+
|
120
|
+
.DS_Store
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Nathan Smith
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,254 @@
|
|
1
|
+
# Radagen
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/smidas/radagen.svg?branch=master)](https://travis-ci.org/smidas/radagen)
|
4
|
+
|
5
|
+
Radagen is a psuedo random data generator library for the Ruby language built with two primary design goals: *composition* and *sizing*. These two properties allow this library to be used in a range of different applications from simple test data generation, model checking, fuzz testing, database seeding to the foundation of a generative/property based testing framework.
|
6
|
+
|
7
|
+
## Requirements
|
8
|
+
- Ruby 2.0+
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'radagen'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install radagen
|
25
|
+
|
26
|
+
## API Documentation
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
The main use case for Radagen is to create data generators that produce arbitrarily complex values. These generators can then be used in many different contexts. Lets start with a few of the `scalar` generators provided by Radagen.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'radagen'
|
34
|
+
gen = Radagen
|
35
|
+
|
36
|
+
my_fixnum = gen.fixnum
|
37
|
+
my_string = gen.string_alphanumeric
|
38
|
+
```
|
39
|
+
|
40
|
+
So far we required the Radagen gem and "namespaced" the Radagen module to `gen`. Throughout the rest of this documentation it will be assume `Radagen` is namespaced to `gen`. Let's take look at what values these generators can produce.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
my_fixnum.sample => [0, 0, -1, 1, -3, -4, -5, -1, -2, -7]
|
44
|
+
my_string.sample => ["", "", "jV", "", "7zS", "2", "U9O84Q", "4S", "6Ccw66", "0Sip741V"]
|
45
|
+
```
|
46
|
+
|
47
|
+
`sample` above is a utility method on the `Radagen::Generator` object that allows you to interact with your generator seeing what type of values it will produce. As shown above `sample` returns a sampling of 10 values by default. You can change this number sampled by providing a count.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
my_string.sample(30) => ["",
|
51
|
+
"",
|
52
|
+
"Qo",
|
53
|
+
"T",
|
54
|
+
"X",
|
55
|
+
"qy10O",
|
56
|
+
"Vh",
|
57
|
+
"0omZ",
|
58
|
+
"l30fB",
|
59
|
+
"r08yruW",
|
60
|
+
"27q7zGR",
|
61
|
+
"6jSEk1r",
|
62
|
+
"k667",
|
63
|
+
"v9VUZYnn",
|
64
|
+
"M2K8Hd",
|
65
|
+
"",
|
66
|
+
"4Lu82vRMviY",
|
67
|
+
"LB",
|
68
|
+
"lB",
|
69
|
+
"H0aBry87ykl",
|
70
|
+
"",
|
71
|
+
"8JMjNC",
|
72
|
+
"Gr6lxA",
|
73
|
+
"",
|
74
|
+
"1VfvdCjA9t2PL72Xa",
|
75
|
+
"vI",
|
76
|
+
"lUabvF4Rg06RWl71V27fi53",
|
77
|
+
"qw2Uo71ADT",
|
78
|
+
"550EbA0UX9f9Sc4I",
|
79
|
+
"9QbchgbZtY7C57Eq"]
|
80
|
+
```
|
81
|
+
|
82
|
+
There is something to be noticed about the values produced by `my_string` generator. The values grow in *size* and *complexity* because as `sample` calls the generator it passes a larger and larger *size* value. This is a very important aspect of all Radagen generators and will be detailed further in `sizing`.
|
83
|
+
|
84
|
+
### Composition
|
85
|
+
|
86
|
+
`scalar` generators are interesting but can only take you so far. We now will explore the ideas around composition. Lets build on the previous generators.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
my_hash = gen.hash(:fixnum => my_fixnum, :string => my_strings)
|
90
|
+
my_hash.sample => [{:fixnum=>0, :string=>""},
|
91
|
+
{:fixnum=>1, :string=>"9"},
|
92
|
+
{:fixnum=>-1, :string=>"1"},
|
93
|
+
{:fixnum=>2, :string=>"2F2"},
|
94
|
+
{:fixnum=>1, :string=>""},
|
95
|
+
{:fixnum=>3, :string=>""},
|
96
|
+
{:fixnum=>5, :string=>""},
|
97
|
+
{:fixnum=>1, :string=>"4V9yx"},
|
98
|
+
{:fixnum=>8, :string=>""},
|
99
|
+
{:fixnum=>-1, :string=>"Dy443"}]
|
100
|
+
```
|
101
|
+
|
102
|
+
Above we have built a `hash` generator which uses values taken from previous generators. There is of course little difference between the above and the following:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
my_hash = gen.hash(:fixnum => gen.fixnum, :string => gen.string_alphanumeric)
|
106
|
+
my_hash.sample => [{:fixnum=>0, :string=>""},
|
107
|
+
{:fixnum=>0, :string=>""},
|
108
|
+
{:fixnum=>0, :string=>""},
|
109
|
+
{:fixnum=>2, :string=>"W9"},
|
110
|
+
{:fixnum=>-1, :string=>"79BG"},
|
111
|
+
{:fixnum=>2, :string=>"YEF0"},
|
112
|
+
{:fixnum=>0, :string=>"IQDe"},
|
113
|
+
{:fixnum=>-2, :string=>"mRo"},
|
114
|
+
{:fixnum=>-7, :string=>"F958K0"},
|
115
|
+
{:fixnum=>7, :string=>"18T"}]
|
116
|
+
```
|
117
|
+
|
118
|
+
However the following becomes more interesting.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
meta = gen.hash(:fixnum => gen.fixnum, :string => gen.string_alphanumeric)
|
122
|
+
individual_account = gen.hash(:account_id => gen.uuid, :type => gen.return('individual'), :meta => meta)
|
123
|
+
individual_account.sample => [{:account_id=>"d4f6a194-d2d8-4c4f-9a89-df3f477f3dfc", :type=>"individual", :meta=>{:fixnum=>0, :string=>""}},
|
124
|
+
{:account_id=>"e6da491f-0af3-4ab4-9529-00a5025cbbde", :type=>"individual", :meta=>{:fixnum=>-1, :string=>""}},
|
125
|
+
{:account_id=>"4c3a3ebb-0aa3-4fc7-9c66-022ef2c1b77a", :type=>"individual", :meta=>{:fixnum=>-1, :string=>""}},
|
126
|
+
{:account_id=>"2a21493e-5ea7-4ae4-b813-bfe7052c5ba0", :type=>"individual", :meta=>{:fixnum=>-2, :string=>""}},
|
127
|
+
{:account_id=>"54a06f43-35bf-4b86-af36-499164fdec0e", :type=>"individual", :meta=>{:fixnum=>2, :string=>"lYnP"}},
|
128
|
+
{:account_id=>"17d42c28-c9a2-484d-a54f-3063fba893a2",
|
129
|
+
:type=>"individual",
|
130
|
+
:meta=>{:fixnum=>-4, :string=>"92e8k"}},
|
131
|
+
{:account_id=>"c77a7dc6-7c13-4d68-b5ff-dd4d4d22366f", :type=>"individual", :meta=>{:fixnum=>4, :string=>"gEDq"}},
|
132
|
+
{:account_id=>"f0e2b910-70c7-4b65-9dd1-9e89ef03ec00",
|
133
|
+
:type=>"individual",
|
134
|
+
:meta=>{:fixnum=>3, :string=>"e2EYLzk"}},
|
135
|
+
{:account_id=>"7bce02de-6d77-4e4e-91c9-8f518cc73223",
|
136
|
+
:type=>"individual",
|
137
|
+
:meta=>{:fixnum=>4, :string=>"Qnh5Z"}},
|
138
|
+
{:account_id=>"8781bf21-85f0-40f4-b407-85805da60ba6", :type=>"individual", :meta=>{:fixnum=>-1, :string=>"Z"}}]
|
139
|
+
```
|
140
|
+
|
141
|
+
The `hash` generator combinator conveniently will nest any generator. But what if you would like to make your own combinators? There are two primitives to work with; `bind` and `fmap`.
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
domain = gen.elements(['gmail.com', 'mailinator.com'])
|
145
|
+
name = gen.string_ascii
|
146
|
+
|
147
|
+
email_account = gen.fmap(gen.tuple(name, domain)) do |name, domain|
|
148
|
+
"#{name}@#{domain}"
|
149
|
+
end
|
150
|
+
|
151
|
+
email_account.sample => ["@gmail.com", "r@gmail.com", "U@gmail.com", "mj@gmail.com", "z^@mailinator.com", "B_@mailinator.com", "(t-f;@gmail.com", "@gmail.com", ")3-@mailinator.com", "n@mailinator.com"]
|
152
|
+
```
|
153
|
+
|
154
|
+
Lets walk thru the above example. `elements` will randomly select an element from the array you pass it (ex. 'gmail.com' or 'mailinator.com'). `string_ascii` will produce strings containing the *ascii* band of characters. `fmap` will take values from a generator, which in this case was a two `tuple` with the first value taken from the *name* generator and the second taken from the *domain* generator, and passes those values to a `block`. Within the block we do some destructuring to the tuple and with string interpolation we return an email account string. Note the block passed to `fmap` requires you return a *value* NOT another generator.
|
155
|
+
|
156
|
+
The first example you noticed has an *empty* name which isn't a valid email address. We see our first example of how a 'stocastic' like tool can challenge our assumptions, or at least forces you to consider the domain you are working a little deeper.
|
157
|
+
|
158
|
+
If you don't want the generator to produce *empty* names you could do the following:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
name = gen.not_empty(gen.string_ascii)
|
162
|
+
name.sample => ["1", "G", "y", "K", "<xv", "|5`", "u4GgC", "^", "n(]yZ2", "V7-"]
|
163
|
+
```
|
164
|
+
|
165
|
+
Using `not_empty` here takes the `string_ascii` generator, returning another generator that won't produce empty strings.
|
166
|
+
|
167
|
+
`bind` is similar to `fmap` but the block instead of returning a *value* needs to return a `Radagen::Generator`.
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
account = gen.hash({:id => gen.uuid, :name => gen.string_alpha, :comment_count => gen.fixnum_pos})
|
171
|
+
accounts = gen.not_empty(gen.array(account)) #an array of non-empty accounts
|
172
|
+
|
173
|
+
accounts_and_selection = gen.bind(accounts) do |accounts|
|
174
|
+
gen.tuple(gen.identity(accounts), gen.elements(accounts))
|
175
|
+
end
|
176
|
+
|
177
|
+
accounts_and_selection.sample => [[[{:id=>"8f9308be-976f-447b-b207-3e4f3391d8da", :name=>"f", :comment_count=>1}], {:id=>"8f9308be-976f-447b-b207-3e4f3391d8da", :name=>"f", :comment_count=>1}],
|
178
|
+
[[{:id=>"f28cde53-4f8f-4548-9192-57c3b44af73c", :name=>"Ry", :comment_count=>2}, {:id=>"5b45b99a-1a8a-4674-9ad5-f5c706d27315", :name=>"Hl", :comment_count=>2}], {:id=>"5b45b99a-1a8a-4674-9ad5-f5c706d27315", :name=>"Hl", :comment_count=>2}],
|
179
|
+
[[{:id=>"3e97741d-107d-4cab-ae11-323b1ef42668", :name=>"Y", :comment_count=>2}], {:id=>"3e97741d-107d-4cab-ae11-323b1ef42668", :name=>"Y", :comment_count=>2}],
|
180
|
+
[[{:id=>"63e812b2-e1b2-4528-acd2-f36d0f323dbd", :name=>"", :comment_count=>1}], {:id=>"63e812b2-e1b2-4528-acd2-f36d0f323dbd", :name=>"", :comment_count=>1}]]
|
181
|
+
```
|
182
|
+
|
183
|
+
`accounts_and_selection` will create an array of accounts and then randomly select one of those accounts returning a two-tuple of that representation. This type of pattern is very helpful when you want to setup state in a system and then interact with one or more of the objects. Why create a generator that does the selection for you and not just select an element after the values from the generator that have been produced? Reproducibility. Being able to rerun the same generator with the same *seed*, producing the same array of accounts and selection is important when using this library in a testing context or in any context really.
|
184
|
+
|
185
|
+
### Seeding
|
186
|
+
|
187
|
+
*Seeding* the generator is simply providing a starting state to the [pseudo random number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) so that you can repeatably produce the same values from your generator.
|
188
|
+
|
189
|
+
#### gen
|
190
|
+
```ruby
|
191
|
+
seed = 5647326586234654723645
|
192
|
+
|
193
|
+
accounts_and_selection.gen(5, seed) => [[{:id=>"0cc609b8-7ada-4192-be41-1f2b29369b14", :name=>"Kiq", :comment_count=>5}, {:id=>"cc8e373b-1a06-4d2c-adad-bb18f9d9b10f", :name=>"vU", :comment_count=>3}], {:id=>"0cc609b8-7ada-4192-be41-1f2b29369b14", :name=>"Kiq", :comment_count=>5}]
|
194
|
+
|
195
|
+
accounts_and_selection.gen(5, seed) => [[{:id=>"0cc609b8-7ada-4192-be41-1f2b29369b14", :name=>"Kiq", :comment_count=>5}, {:id=>"cc8e373b-1a06-4d2c-adad-bb18f9d9b10f", :name=>"vU", :comment_count=>3}], {:id=>"0cc609b8-7ada-4192-be41-1f2b29369b14", :name=>"Kiq", :comment_count=>5}]
|
196
|
+
```
|
197
|
+
|
198
|
+
Taking our `account_and_selection` generator we created above, providing a *seed* value and a size we were able to reproduce the same generated value. In this example using the `gen` method, we can pass in the *size* and *seed* to the generator. Note the *size* value should also be seen as a state value as it will influence the value produced. The `sample` method does NOT provide this level of interactively and is intended for exploratory interaction in a console.
|
199
|
+
|
200
|
+
#### to_enum
|
201
|
+
```ruby
|
202
|
+
accounts_and_selection.to_enum(seed: 5647326586234654723645).take(6).to_a => [[[{:id=>"609b87ad-a192-4be4-91f2-b29369b14eee", :name=>"Kiq", :comment_count=>4},
|
203
|
+
{:id=>"cc8e373b-1a06-4d2c-adad-bb18f9d9b10f", :name=>"vU", :comment_count=>4},
|
204
|
+
{:id=>"004839da-0ea1-4545-bf74-211b1515e3c1", :name=>"OI", :comment_count=>2},
|
205
|
+
{:id=>"d59b453c-da7e-45d6-8d44-5a39123c1ce6", :name=>"", :comment_count=>1}],
|
206
|
+
{:id=>"d59b453c-da7e-45d6-8d44-5a39123c1ce6", :name=>"", :comment_count=>1}],
|
207
|
+
[[{:id=>"1f11bc82-c45a-4ab6-8528-eaf0a0b565f3", :name=>"sC", :comment_count=>4}, {:id=>"b2fa4c7b-0bb6-4ff0-9f69-ac709f703b02", :name=>"x", :comment_count=>3}], {:id=>"1f11bc82-c45a-4ab6-8528-eaf0a0b565f3", :name=>"sC", :comment_count=>4}],
|
208
|
+
[[{:id=>"f68e86b3-6b64-4e0d-a184-be9a04dd1101", :name=>"q", :comment_count=>1}, {:id=>"371fe60e-7cd8-40e0-bf8d-d06762978271", :name=>"", :comment_count=>1}], {:id=>"371fe60e-7cd8-40e0-bf8d-d06762978271", :name=>"", :comment_count=>1}],
|
209
|
+
[[{:id=>"30fa9545-b504-443d-8bb5-0a0a87acd2a3", :name=>"TZ", :comment_count=>4}, {:id=>"f8778a9b-2030-45ee-a82f-a7eddbad06c9", :name=>"b", :comment_count=>2}], {:id=>"f8778a9b-2030-45ee-a82f-a7eddbad06c9", :name=>"b", :comment_count=>2}],
|
210
|
+
[[{:id=>"d29f472d-55ea-4464-8b93-91d741b69db5", :name=>"oQc", :comment_count=>2}, {:id=>"448ae1c8-c6c6-40d5-a8ed-cd96dcf05c83", :name=>"", :comment_count=>1}], {:id=>"448ae1c8-c6c6-40d5-a8ed-cd96dcf05c83", :name=>"", :comment_count=>1}]]
|
211
|
+
```
|
212
|
+
|
213
|
+
You can also leverage the `to_enum` method which will return an enumerable representing a lazy infinite sequence of values taken from the generator. Seen above with a provided *seed*, `to_enum` also has the ability to control the sequence of sizes passed to the generator when called. See the API docs for more details.
|
214
|
+
|
215
|
+
### Sizing
|
216
|
+
|
217
|
+
Sizing provides the ability for a generator to produce values of varying degrees of well, *size*. Size has different meaning depending on the context of the generator being used. In some cases generators don't honor the size parameter at all. Examples being `uuid` and `identity`. When a generator is called to produce a value the *size* that is passed in represents the *upper bound* of all possible sizes starting from zero that could be generated. Why? This is so that methods like `to_enum`, `sample` and `gen` don't return a predictable linear growth of values as they are passed ever increasing size values. It is also why this library and libraries like it seem to have the ability to walk thru progressively more complex values "randomly", perhaps challenging boundary assumptions of a model.
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
5.times { p gen.fixnum.gen(size: 2) } => 2 0 -1 1 -2
|
221
|
+
|
222
|
+
5.times { p gen.string.gen(size: 300) } => "oysO930W8B4QU02J05qEWPn6R6H7xTZKFGbG6Hpo28b"
|
223
|
+
"R021N1tw0927BXZP7GbzgRE4rA5t46785g27jsztMRlaz571XzHoi6yBv22ec97194yByN3KIYM3EX1XiRrA08Y32S5i2AvkevMRLClA8Xsb0N7R"
|
224
|
+
"3zFiC25U5495KY52FcB1B12txoA6xlFc83TJ97j9ytKSfi0rs1EwSILytRyMy2S5F70TrT6H457teJkVk5fr"
|
225
|
+
"VaLYheY512yh2qsj2MV31dl7oV56kWmmLlPSDIJwd74mOcyYfQft8N9756VfM5ExtBHql9TnRWl15j3beLww4p176G48s5q8bEWS0Nwcx7RX0WBz2nO412k7fWGi3nmxn8i156C45AHS27ttTB34sT0MiY63HWG0rbXLXnt41d3m5HiWmbnyh9yL36KH3TL4NMwK4vV0A9gZ6DpLrvPUWYaNYgEqQ0MGll1d2009l388upimkl71xaglmK97r7EDA491"
|
226
|
+
"82Q78FWpl1992nXtPUP2cF0rnM7jUPo0M6F8VgvbrQjHY4Var31H0aY94OF0Np6mlxM648S38LBvTrjZObsGn2eB9RPqzqWOlzMD0j71UKRZU90L7B95B5MdZMviaj5vml3JkkIODi6QZQWUobQt4Gr6b0mqR69UQ77897BuK2VjmFdNPLx4z8we7Bk0vU5o6DcJQgxOu7ZP"
|
227
|
+
|
228
|
+
```
|
229
|
+
|
230
|
+
There a few methods that can be used to have finer grained control over the size being passed to a generator. See `resize`, `scale`, and for some generators `not_empty` in the API docs.
|
231
|
+
|
232
|
+
## Development
|
233
|
+
|
234
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt with pry that will allow you to experiment.
|
235
|
+
|
236
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
237
|
+
|
238
|
+
## Contributing
|
239
|
+
|
240
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/smidas/radagen.
|
241
|
+
|
242
|
+
## Attribution
|
243
|
+
|
244
|
+
Radagen was greatly influenced by the generator API found in [test.check](https://github.com/clojure/test.check) and shares many of the same naming conventions. I have a great deal of gratitude to the contributors and maintainers of that library.
|
245
|
+
|
246
|
+
## TODO
|
247
|
+
- More example documentation
|
248
|
+
- Make the `set` generator honor *min* elements
|
249
|
+
- Implement a Bignum generator with a good enough sampling distribution.
|
250
|
+
- Explore and implement the need for a splittable PRNG.
|
251
|
+
|
252
|
+
## License
|
253
|
+
|
254
|
+
Radagen is released under the [MIT License](https://github.com/smidas/radagen/blob/master/LICENSE).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "radagen"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require "pry"
|
11
|
+
Pry.start
|
12
|
+
|
13
|
+
# require "irb"
|
14
|
+
# IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module Radagen
|
2
|
+
|
3
|
+
class Generator
|
4
|
+
|
5
|
+
def initialize(&gen_proc)
|
6
|
+
@gen_proc = gen_proc
|
7
|
+
end
|
8
|
+
|
9
|
+
# Realize a value from the generator.
|
10
|
+
#
|
11
|
+
# @param prng [Object] psuedo random number generator
|
12
|
+
# @param size [Fixnum] size used in the generation of the value
|
13
|
+
# @return [Object]
|
14
|
+
#
|
15
|
+
def call(prng, size)
|
16
|
+
@gen_proc.call(prng, size)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generate n samples from the generator. Sizing is linear
|
20
|
+
# and starts at 0. Max size is defined by #to_enum. This is
|
21
|
+
# to *see* what type of values your generator will make.
|
22
|
+
#
|
23
|
+
# @note max size on samples is set by #to_enum
|
24
|
+
# @param n [Fixnum] sample generated values from generator
|
25
|
+
# @return [Array<Object>]
|
26
|
+
# @see #to_enum
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# fixnum.sample(3) #=> [0, 1, 1]
|
30
|
+
#
|
31
|
+
def sample(n=10)
|
32
|
+
self.to_enum.take(n).to_a
|
33
|
+
end
|
34
|
+
|
35
|
+
# Generate a single value from the generator.
|
36
|
+
#
|
37
|
+
# @param size [Fixnum] *size* passed to the generator
|
38
|
+
# @param seed [Fixnum] seed used as the initial state of the generator
|
39
|
+
# @return [Object]
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# string_alpha.gen => "Qwad"
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# string_alpha.gen(200, 45362642634632684368) => "IaxBvRLxDIvLBhKezMdMmVZBCGzSJZvVjHkcLHsEchCpZWOmLAUQ"
|
46
|
+
#
|
47
|
+
def gen(size=30, seed=Random.new_seed)
|
48
|
+
prng = Random.new(seed)
|
49
|
+
@gen_proc.call(prng, size)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Create a lazy enumerable of generator values. Size cycles from *size_min* to *size_max*
|
53
|
+
#
|
54
|
+
# @note the *size* value is the upper bound of the sizes that could be generated
|
55
|
+
# @param opts [Hash] options hash to control behavior of enumerator
|
56
|
+
# @option opts [Fixnum] :size_min (0) minimum *size* passed to generator in size cycles
|
57
|
+
# @option opts [Fixnum] :size_max (300) maximum *size* passed to generator in size cycles
|
58
|
+
# @option opts [Fixnum] :seed initial state passed to the pseudo random number generator
|
59
|
+
# @return [Enumerator::Lazy]
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# string_ascii.to_enum.take(10).to_a #=> ["", "", ")", "{?", "wE&", "h*hq", "9gm>dG", "9Ljn,(Z", "", "1q7:\\q{"]
|
63
|
+
#
|
64
|
+
def to_enum(opts={})
|
65
|
+
default_opts = {size_min: 0, size_max: 300, seed: Random.new_seed}
|
66
|
+
size_min, size_max, seed = default_opts.merge(opts).values_at(:size_min, :size_max, :seed)
|
67
|
+
prng = Random.new(seed)
|
68
|
+
|
69
|
+
(size_min...size_max).cycle.lazy.map do |size|
|
70
|
+
@gen_proc.call(prng, size)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|