quick-sampler 0.0.1
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/.gitignore +14 -0
- data/.rspec +3 -0
- data/.travis.yml +3 -0
- data/.yardopts +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +151 -0
- data/Rakefile +9 -0
- data/lib/quick/sampler/autoload.rb +2 -0
- data/lib/quick/sampler/base.rb +96 -0
- data/lib/quick/sampler/config.rb +25 -0
- data/lib/quick/sampler/dsl/character_class.rb +41 -0
- data/lib/quick/sampler/dsl/fluidiom.rb +49 -0
- data/lib/quick/sampler/dsl/simple_combinators.rb +135 -0
- data/lib/quick/sampler/dsl/simple_values.rb +97 -0
- data/lib/quick/sampler/dsl.rb +71 -0
- data/lib/quick/sampler/shrink/class_methods/while.rb +38 -0
- data/lib/quick/sampler/shrink/refinements.rb +42 -0
- data/lib/quick/sampler/shrink.rb +8 -0
- data/lib/quick/sampler/version.rb +5 -0
- data/lib/quick/sampler.rb +49 -0
- data/quick-sampler.gemspec +28 -0
- data/spec/quick/sampler/compile_spec.rb +41 -0
- data/spec/quick/sampler/dsl/list_like_spec.rb +62 -0
- data/spec/quick/sampler/dsl/send_to_spec.rb +58 -0
- data/spec/spec_helper.rb +4 -0
- metadata +159 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 58a28eb8cda1b35a7cfde8b6c7c118e3a6373b17
|
4
|
+
data.tar.gz: 655c8fb5860814fbf19f7fedf8573e2d310cbcc4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a31df65d1b884a650988a248af0c324a5aad89df9dd5295e85b44b9a3a841ed6effde069088cf5368e846eba23b764ab19d163d3788d8ec1f2879eec903069c8
|
7
|
+
data.tar.gz: 4e5ae84816f4e748c3c184775cb5de4e6c34e1cda267081f6d08d20201ec4a1c53811fff2d31ce1f7a55433ff1eb2a70a47452e28a7a7cad43541da51cd7ea0e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 de Praktijk Index B.V.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# Quick Sampler
|
2
|
+
|
3
|
+
Composable samplers of data: describe your randomness and watch it blend.
|
4
|
+
|
5
|
+
Quick Sampler is DSL for describing and sampling random data influenced by
|
6
|
+
Haskell/Erlang's [QuickCheck][1] generators by Koen Claessen and John Hughes,
|
7
|
+
[rantly][2] gem by Howard Yeh, [theft][3] gem by Shawn Anderson, Jessica Kerr's
|
8
|
+
[generatron][4] gem, her [Property-Based Testing for Better Code talk][5] and
|
9
|
+
the rest of the cause-and-effect chain [all the way][6] to [big bang][7].
|
10
|
+
|
11
|
+
[1]: http://www.cse.chalmers.se/~rjmh/QuickCheck/
|
12
|
+
[2]: https://github.com/hayeah/rantly
|
13
|
+
[3]: https://github.com/shawn42/theft
|
14
|
+
[4]: https://github.com/jessitron/generatron/
|
15
|
+
[5]: http://www.windycityrails.org/videos/2014/#14
|
16
|
+
[6]: http://en.wikipedia.org/wiki/Turtles_all_the_way_down
|
17
|
+
[7]: http://en.wikipedia.org/wiki/Unmoved_mover
|
18
|
+
|
19
|
+
## Sampler vs Generator
|
20
|
+
|
21
|
+
**Sampler** is the same as **generator** in other randomness frameworks, but
|
22
|
+
tries to suggest an expanded understanding of what it can be used for.
|
23
|
+
Ordinarily one would *sample* a source of randomness, but one could just as
|
24
|
+
well *sample* some "real" data at random and pass it on, verbatim or randomly
|
25
|
+
transmuted.
|
26
|
+
|
27
|
+
The term is supposed to suggest "the way of truth" view of
|
28
|
+
unchanging reality that Parmenides described in his *On Nature* in fifth
|
29
|
+
century BCE. To see what he meant back when Socrates was a young man - fix your
|
30
|
+
random seed and watch your "generators" repeat themselves. Smoke that,
|
31
|
+
Heraclitus.
|
32
|
+
|
33
|
+
<img src="https://cloud.githubusercontent.com/assets/64227/6993106/512cc778-daea-11e4-82dc-01cc8ef958fa.jpg" height="200px">
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
The main entry point is {Quick::Sampler.compile}. The method takes sampler
|
38
|
+
definition as block and returns a "compiled" sampler:
|
39
|
+
|
40
|
+
```
|
41
|
+
pry> sampler = Quick::Sampler.compile { integer }
|
42
|
+
=> #<Enumerator::Lazy: #<Enumerator: #<Enumerator::Generator:0x007f295f9b2af0>:each>>
|
43
|
+
pry> sampler.first(5)
|
44
|
+
=> [1763573971376409386, -1692192782152475313, -3665498119514965288, 0, 0]
|
45
|
+
```
|
46
|
+
|
47
|
+
So, the truth is out: a sampler is a lazy enumerator (and by extension - Enumerable).
|
48
|
+
But [will it blend?][8]
|
49
|
+
|
50
|
+
[8]: https://github.com/jessitron/gerald#gerald
|
51
|
+
|
52
|
+
```irb
|
53
|
+
pry> sampler2 = Quick::Sampler.compile { config(upper_bound: 5).string(:lower) }
|
54
|
+
=> #<Enumerator::Lazy: #<Enumerator: #<Enumerator::Generator:0x007f295fd88058>:each>>
|
55
|
+
pry> sampler2.zip(sampler).first(5).to_h
|
56
|
+
=> {"bjm"=>-4027257104748747508,
|
57
|
+
"bcrs"=>-540067903901761386,
|
58
|
+
"wqfn"=>2107130696606126069,
|
59
|
+
"disw"=>2495326937126240758,
|
60
|
+
"rglv"=>2235767748081203791}
|
61
|
+
```
|
62
|
+
|
63
|
+
Hell yeah, it blends.
|
64
|
+
|
65
|
+
### Compile
|
66
|
+
|
67
|
+
The aim of "compilation" is to deliver us from typing / reading the namespaces
|
68
|
+
in sampler definitions. Consider, for example:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
Quick::Sampler.compile do
|
72
|
+
one_of_weighted integer => 10,
|
73
|
+
boolean => 1,
|
74
|
+
vector_of(5, integer) => 5
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
(**Rosetta stone:** `one_of_weighted` is what in Haskell QuickCheck is known as `frequency`)
|
79
|
+
|
80
|
+
vs hypothetic alternative:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
Generators.one_of_weighted Generators.integer => 10,
|
84
|
+
Generators.boolean => 1,
|
85
|
+
Generators.vector_of(5, Generators.integer) => 5
|
86
|
+
```
|
87
|
+
|
88
|
+
### Sampler configuration
|
89
|
+
|
90
|
+
Some sampling parameters can be passed as arguments to a sampler function (like
|
91
|
+
character class `:lower` in the example above). Others - that affect multiple
|
92
|
+
sub-samplers in a definition - may be injected with a call to `config(...)` (like
|
93
|
+
`upper_bound` above).
|
94
|
+
|
95
|
+
(**Rosetta stone:** `upper_bound` is what in Haskell QuickCheck is known as `size`)
|
96
|
+
|
97
|
+
### Sampler composability
|
98
|
+
|
99
|
+
The composition using Enumerable API as demonstrated above is pretty flexible and familiar to a
|
100
|
+
ruby developer.
|
101
|
+
|
102
|
+
Some less "linear" shapes of data on the other hand are better
|
103
|
+
expressed by the Quick Sampler DSL within the sampler definition:
|
104
|
+
|
105
|
+
**TODO** replace with a real life example of a complex sampler.
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
cities_sampler = Quick::Sampler.compile do
|
109
|
+
send_to City, :build, name: string(:lower),
|
110
|
+
state: pick_from(State.all),
|
111
|
+
population: pick_from(1_000..10_000_000)
|
112
|
+
end
|
113
|
+
|
114
|
+
cities = cities_sampler.first(100)
|
115
|
+
cities.each &:save!
|
116
|
+
|
117
|
+
travellers_sampler = Quick::Sampler.compile do
|
118
|
+
send_to Person, :build,
|
119
|
+
name: string(:lower),
|
120
|
+
born_in: pick_from(cities),
|
121
|
+
lives_in: pick_from(cities),
|
122
|
+
visited: list_of(pick_from(cities))
|
123
|
+
end
|
124
|
+
|
125
|
+
travellers = travellers_sampler.first(1000)
|
126
|
+
travellers.each &:save!
|
127
|
+
```
|
128
|
+
|
129
|
+
## Installation
|
130
|
+
|
131
|
+
Add this line to your application's Gemfile:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
gem 'quick-sampler'
|
135
|
+
```
|
136
|
+
|
137
|
+
And then execute:
|
138
|
+
|
139
|
+
$ bundle
|
140
|
+
|
141
|
+
Or install it yourself as:
|
142
|
+
|
143
|
+
$ gem install quick-sampler
|
144
|
+
|
145
|
+
## Contributing
|
146
|
+
|
147
|
+
1. Fork it ( https://github.com/praktijkindex/quick-sampler/fork )
|
148
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
149
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
150
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
151
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Quick
|
4
|
+
module Sampler
|
5
|
+
# A sampler base class, delegating most work to the underlying lazy enumerator.
|
6
|
+
#
|
7
|
+
# ### Readable #inspect
|
8
|
+
#
|
9
|
+
# One superficial extra that {Sampler::Base} provides is a description attribute
|
10
|
+
# which is returned by `#inspect`. This can help keep test output readable.
|
11
|
+
#
|
12
|
+
# ### Shrinking
|
13
|
+
#
|
14
|
+
# Sampler::Base also provides an {#shrink api for shrinking} failed inputs.
|
15
|
+
#
|
16
|
+
# From {http://stackoverflow.com/a/16970029/538534 stackoverflow discussion}
|
17
|
+
# of shrinking (follow the link for an example) in the original QuickCheck:
|
18
|
+
#
|
19
|
+
# > When QuickCheck finds an input that violates a property, it will first
|
20
|
+
# try to find smaller inputs that also violate the property, in order to
|
21
|
+
# give the developer a better message about the nature of the failure.
|
22
|
+
#
|
23
|
+
# > What it means to be „small“ of course depends on the datatype in
|
24
|
+
# question; to QuickCheck it is anything that comes out from from the
|
25
|
+
# shrink function.
|
26
|
+
#
|
27
|
+
class Base < DelegateClass(Enumerator::Lazy)
|
28
|
+
attr_accessor :description
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
# Not supposed to be used directly, use {Quick::Sampler.compile} instead.
|
32
|
+
#
|
33
|
+
# @param [Enumerator, Enumerator::Lazy, #call] source source of data.
|
34
|
+
# Non-lazy `Enumerator` will be lazyfied, and anything `call`able will be
|
35
|
+
# wrapped into a lazy enumerator.
|
36
|
+
# @param [String] description sampler description to be returned by #inspect
|
37
|
+
def initialize source, description: "Quick Sampler"
|
38
|
+
@description = description
|
39
|
+
super(source_to_lazy(source))
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [String] sampler description, so test output is readable
|
43
|
+
def inspect
|
44
|
+
description
|
45
|
+
end
|
46
|
+
|
47
|
+
# A very preliminary API for QuickCheck-like input shrinking.
|
48
|
+
# A sampler is responsible for shrinking its failed samples.
|
49
|
+
#
|
50
|
+
# @example Detecting and then shrinking failing examples:
|
51
|
+
#
|
52
|
+
# # Generate random input data
|
53
|
+
# all_inputs = sampler.first(100)
|
54
|
+
#
|
55
|
+
# # Find failures (by rejecting inputs satisfying the property)
|
56
|
+
# failed_inputs = all_inputs.reject{ |input| property(input) }
|
57
|
+
#
|
58
|
+
# # Shrink failed inputs, continue shrinking as long as property does not hold
|
59
|
+
# shrunk_inputs = sampler.shrink(failed_inputs) {|input| !property(input)}
|
60
|
+
#
|
61
|
+
#
|
62
|
+
# @param [Enumerable] samples input samples that failed the preoperty
|
63
|
+
#
|
64
|
+
# @yieldparam [<Sample>] sample shrunk value to check the property again. If
|
65
|
+
# property holds, the value will be discarded, and the one it was shrunk
|
66
|
+
# from will be added to the set of "minimal" failing examples.
|
67
|
+
#
|
68
|
+
# @yieldreturn [Boolean] `true` to keep on shrinking, meaning *"property still fails for
|
69
|
+
# shrunken value, try to shrink more"*
|
70
|
+
#
|
71
|
+
# @return [Array] "minimal" samples that failed to satisfy the property under test
|
72
|
+
def shrink samples, &block
|
73
|
+
Quick::Sampler::Shrink.while(samples, &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def source_to_lazy source
|
79
|
+
case source
|
80
|
+
when Enumerator::Lazy
|
81
|
+
source
|
82
|
+
when Enumerator
|
83
|
+
source.lazy
|
84
|
+
when ->(i) { i.respond_to? :call }
|
85
|
+
Enumerator.new do |recipient|
|
86
|
+
loop do
|
87
|
+
recipient << source.call
|
88
|
+
end
|
89
|
+
end.lazy
|
90
|
+
else
|
91
|
+
raise "Don't know how to make a sampler from #{source.inspect}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "active_support/configurable"
|
2
|
+
require "active_support/concern"
|
3
|
+
|
4
|
+
module Quick
|
5
|
+
module Sampler
|
6
|
+
module Config
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include ActiveSupport::Configurable
|
9
|
+
|
10
|
+
included do
|
11
|
+
config_accessor(:max_iterations) { 1000 }
|
12
|
+
config_accessor(:upper_bound) { 25 }
|
13
|
+
end
|
14
|
+
|
15
|
+
def config options = :none_given
|
16
|
+
if options == :none_given
|
17
|
+
super()
|
18
|
+
else
|
19
|
+
config.merge!(options)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Quick
|
2
|
+
module Sampler
|
3
|
+
#@!visibility private
|
4
|
+
module DSL::CharacterClass
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def expand *class_names
|
8
|
+
class_names
|
9
|
+
.map { |name| definitions[name] }
|
10
|
+
.compact
|
11
|
+
.flat_map { |definition|
|
12
|
+
case definition
|
13
|
+
when String
|
14
|
+
definition.chars
|
15
|
+
when Range
|
16
|
+
definition.to_a
|
17
|
+
when Array
|
18
|
+
expand(*definition)
|
19
|
+
end
|
20
|
+
}.to_a
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def definitions
|
26
|
+
{
|
27
|
+
lower: "a".."z",
|
28
|
+
upper: "A".."Z",
|
29
|
+
alpha: [:lower, :upper],
|
30
|
+
digits: "0".."9",
|
31
|
+
alnum: [:alpha, :digits],
|
32
|
+
punctuation: %q[~`!@#$%^&*()_-+={}|\\:;"'<,>.?/],
|
33
|
+
whitespace: " \t\n",
|
34
|
+
printable: [:alnum, :punctuation, :whitespace]
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Quick
|
4
|
+
module Sampler
|
5
|
+
# A Quick::Sampler wrapper providing a fluid DSL that can be used in a sampler definition
|
6
|
+
# passed to {Quick::Sampler.compile}.
|
7
|
+
class DSL::Fluidiom < SimpleDelegator
|
8
|
+
# SimpleDelegator so that it can unwrap the "original" sampler with `#__getobj__`
|
9
|
+
include Quick::Sampler::Config
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
# wraps a `sampler` into a `Fluidiom` instance so it has extra methods while
|
13
|
+
# inside the block passed to {Quick::Sampler.compile}
|
14
|
+
def initialize sampler, _config = {}
|
15
|
+
sampler = Base.new(sampler) unless sampler.is_a? Base
|
16
|
+
super(sampler)
|
17
|
+
config.merge! _config
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Quick::Sampler]
|
21
|
+
# the unwrapped original sampler
|
22
|
+
def unwrap
|
23
|
+
__getobj__
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Quick::Sampler]
|
27
|
+
# wrapped sampler that starts out with the same config as this one
|
28
|
+
def spawn sampler
|
29
|
+
self.class.new(sampler, config)
|
30
|
+
end
|
31
|
+
|
32
|
+
# spawn a filtering sampler
|
33
|
+
#
|
34
|
+
# The produced sampler honors the config variable `max_iterations` and stops
|
35
|
+
# iterating when that many original values are tested.
|
36
|
+
#
|
37
|
+
# @return [Quick::Sampler]
|
38
|
+
# a sampler that filter through only samples that satisfy the
|
39
|
+
# predicate given as block
|
40
|
+
# @yieldparam [Anything] sample
|
41
|
+
# a sampled value to be tested
|
42
|
+
# @yieldreturn [Boolean]
|
43
|
+
# `true` to pass the value through
|
44
|
+
def such_that &predicate
|
45
|
+
spawn(unwrap.take(max_iterations).select(&predicate))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Quick
|
2
|
+
module Sampler
|
3
|
+
# DSL methods that allow to combine samplers in various ways.
|
4
|
+
#
|
5
|
+
# ### Recursive samplers
|
6
|
+
#
|
7
|
+
# Some of the combinators are recursive: given a nested structure of Arrays and Hashes
|
8
|
+
# containing samplers and non-sampler values they would produce an analogous structure
|
9
|
+
# where samplers are replaced by their `#next` value.
|
10
|
+
module DSL::SimpleCombinators
|
11
|
+
|
12
|
+
# @return [Quick::Sampler]
|
13
|
+
# a sampler producing values from the range or array
|
14
|
+
def pick_from source
|
15
|
+
case source
|
16
|
+
when Range
|
17
|
+
feed { rand(source) }
|
18
|
+
when Array
|
19
|
+
feed { source.sample }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Quick::Sampler]
|
24
|
+
# a sampler that recursively samples on of the arguments at random
|
25
|
+
def one_of *args
|
26
|
+
feed { recursive_sample(args.sample) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Quick::Sampler]
|
30
|
+
# a sampler that recursively samples on of the expressions at random, with
|
31
|
+
# likelyhood of picking one of the expressions depends on its weight.
|
32
|
+
def one_of_weighted expression_weights
|
33
|
+
total_weight, expressions = expression_weights
|
34
|
+
.reduce([0, {}]) { |(total_weight, expressions), (expression, weight)|
|
35
|
+
total_weight += weight
|
36
|
+
[total_weight, expressions.merge(total_weight => expression)]
|
37
|
+
}
|
38
|
+
|
39
|
+
feed {
|
40
|
+
dice = rand * total_weight
|
41
|
+
weight_class = expressions.keys.find{|w| dice < w}
|
42
|
+
recursive_sample(expressions[weight_class])
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
# Sampler of uniform arrays
|
47
|
+
#
|
48
|
+
# This sampler honors `upper_bound` config variable and samples Arrays of up to
|
49
|
+
# that many elements.
|
50
|
+
#
|
51
|
+
# **Rosetta stone** the single argument version corresponds to QuickCheck's `listOf`.
|
52
|
+
# Passing `non_empty: true` turns it into QuickCheck's `listOf1`.
|
53
|
+
#
|
54
|
+
# @return [Quick::Sampler]
|
55
|
+
# a sampler that produces arrays of values sampled from its argument
|
56
|
+
# @param [Quick::Sampler] sampler
|
57
|
+
# a sampler to sample array elements from
|
58
|
+
# @param [Boolean] non_empty
|
59
|
+
# pass true to never produce empty arrays
|
60
|
+
def list_of sampler, non_empty: false
|
61
|
+
lower_bound = non_empty ? 1 : 0
|
62
|
+
feed { sampler.first(rand(lower_bound..upper_bound)) }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Sampler of uniform fixed length arrays
|
66
|
+
#
|
67
|
+
# **Rosetta stone** this sampler corresponds to QuickCheck's `vectorOf`.
|
68
|
+
#
|
69
|
+
# @return [Quick::Sampler]
|
70
|
+
# a sampler that produces arrays of `length` of values sampled from `sampler`
|
71
|
+
# @param [Integer] length
|
72
|
+
# sample array length
|
73
|
+
# @param [Quick::Sampler] sampler
|
74
|
+
# a sampler to sample array elements from
|
75
|
+
def vector_of length, sampler
|
76
|
+
feed { sampler.take(length).force }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Sampler of arbitrary nested structures made up of `Array`s, `Hash`es, `Quick::Sampler`s and
|
80
|
+
# non-sampler values
|
81
|
+
#
|
82
|
+
# @param [Array] args
|
83
|
+
# a template structure
|
84
|
+
# @return [Quick::Sampler]
|
85
|
+
# a recursive sampler
|
86
|
+
def list_like *args
|
87
|
+
feed { args.map { |arg| recursive_sample(arg) } }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Sampler of arbitrary message send ("method call") results
|
91
|
+
#
|
92
|
+
# @overload send_to recipient, message, *args
|
93
|
+
# @param [Object, Quick::Sampler<Object>] recipient
|
94
|
+
# message recipient or a sampler producing recipients
|
95
|
+
# @param [Symbol, Quick::Sampler<Symbol>] message
|
96
|
+
# message to send or a sampler producing messages
|
97
|
+
# @param [Array] args
|
98
|
+
# argument list that may contain samplers producing arguments
|
99
|
+
#
|
100
|
+
# @overload send_to sampler
|
101
|
+
# @param [Quick::Sampler<[Object, Symbol, *Anything]>] sampler
|
102
|
+
# a sampler producing the message send details
|
103
|
+
#
|
104
|
+
# @return [Quick::Sampler]
|
105
|
+
# a sampler of dynamically generated message send results
|
106
|
+
def send_to *args
|
107
|
+
call_sampler = if args.count > 1
|
108
|
+
list_like *args
|
109
|
+
else
|
110
|
+
args.first
|
111
|
+
end
|
112
|
+
feed {
|
113
|
+
object, message, *args = call_sampler.next
|
114
|
+
object.send( message, *args )
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def recursive_sample value
|
121
|
+
case value
|
122
|
+
when Quick::Sampler
|
123
|
+
value.next
|
124
|
+
when Hash
|
125
|
+
value.map{ |key, value| [key, recursive_sample(value)] }.to_h
|
126
|
+
when Array
|
127
|
+
value.map{ |value| recursive_sample(value) }
|
128
|
+
else
|
129
|
+
value
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Quick
|
2
|
+
module Sampler
|
3
|
+
# Samplers of simple values to form the basis of the sampled data structure.
|
4
|
+
#
|
5
|
+
# ### Note from the future
|
6
|
+
#
|
7
|
+
# In the future simple values are sampled from other excellent Gems from behind a
|
8
|
+
# composable Quick Sampler API. In the mean time this is possible at the cost of
|
9
|
+
# readablity:
|
10
|
+
#
|
11
|
+
# @example Faker integration
|
12
|
+
#
|
13
|
+
# Quick::Sampler.compile description: "email address" do
|
14
|
+
# feed { Faker::Internet.email }
|
15
|
+
# end
|
16
|
+
module DSL::SimpleValues
|
17
|
+
|
18
|
+
# @!volatile
|
19
|
+
#
|
20
|
+
# Degenerate constant sampler. Will probably be superseeded by
|
21
|
+
# a cleaner smarter syntax as I get a better hang of it.
|
22
|
+
#
|
23
|
+
# @return [Quick::Sampler<Anything>] a sampler of constant value
|
24
|
+
# @param [Anything] const
|
25
|
+
# the value to keep on sampling
|
26
|
+
def const const
|
27
|
+
feed { const }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Degenerate sampler of zeros. Like {#const} this will probably
|
31
|
+
# go away soon.
|
32
|
+
#
|
33
|
+
# @return [Quick::Sampler<0>] a sampler of zeros
|
34
|
+
def zero
|
35
|
+
const 0
|
36
|
+
end
|
37
|
+
|
38
|
+
FixnumRange = -(2**(0.size * 8 -2))..(2**(0.size * 8 -2) -1)
|
39
|
+
private_constant :FixnumRange
|
40
|
+
|
41
|
+
# Samples random fixnums (smaller integers that can be handled quickly by
|
42
|
+
# the CPU itself)
|
43
|
+
#
|
44
|
+
# @return [Quick::Sampler<Fixnum>] a sampler of fixnums
|
45
|
+
def fixnum
|
46
|
+
pick_from(FixnumRange)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Quick::Sampler<Fixnum>] a sampler of negative fixnums
|
50
|
+
def negative_fixnum
|
51
|
+
pick_from(FixnumRange.min..-1)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Quick::Sampler<Fixnum>] a sampler of positive fixnums
|
55
|
+
def positive_fixnum
|
56
|
+
pick_from(1..FixnumRange.max)
|
57
|
+
end
|
58
|
+
|
59
|
+
# A sampler of integers prefering smaller ones
|
60
|
+
#
|
61
|
+
# It will however sample a large one (from the Fixnum range) occasionally.
|
62
|
+
#
|
63
|
+
# @return [Quick::Sampler<Fixnum>] a sampler of integers
|
64
|
+
def integer
|
65
|
+
one_of_weighted(fixnum => 5,
|
66
|
+
pick_from(-1_000_000_000..1_000_000_000) => 7,
|
67
|
+
pick_from(-1000..1000) => 9,
|
68
|
+
pick_from(-100..100) => 11,
|
69
|
+
pick_from(-20..20) => 17)
|
70
|
+
end
|
71
|
+
|
72
|
+
alias_method :negative_integer, :negative_fixnum
|
73
|
+
alias_method :positive_integer, :positive_fixnum
|
74
|
+
|
75
|
+
# @return [Quick::Sampler<Boolean>] a sampler of `true` and `false` values
|
76
|
+
def boolean
|
77
|
+
pick_from([true, false])
|
78
|
+
end
|
79
|
+
|
80
|
+
# This sampler honors `upper_bound` config variable.
|
81
|
+
#
|
82
|
+
# The sampler will produce strings of random (between 0 and `upper_bound`) length
|
83
|
+
# made up of characters belonging to supplied named classes.
|
84
|
+
#
|
85
|
+
# @returns [Quick::Sampler<String>] random `String` sampler
|
86
|
+
# @param [Array<Symbol>] classes
|
87
|
+
# Character classes to pick from.
|
88
|
+
# @todo document character classes
|
89
|
+
def string *classes
|
90
|
+
classes = [:printable] if classes.empty?
|
91
|
+
repertoire = DSL::CharacterClass.expand(*classes)
|
92
|
+
feed { repertoire.sample(rand(upper_bound)).join }
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Quick
|
2
|
+
module Sampler
|
3
|
+
# A domain specific method for sampler definitions.
|
4
|
+
#
|
5
|
+
# Instance methods of this class are available as barewords inside a
|
6
|
+
# sampler definition supplied as a block to {Quick::Sampler.compile}.
|
7
|
+
#
|
8
|
+
# Methods that produce a sampler actually wrap it in a {Fluidiom} instance
|
9
|
+
# that adds a fluid API to the sampler. This wrapping is stripped off from
|
10
|
+
# the sampler returned by {Quick::Sampler.compile} - although I'm still
|
11
|
+
# undesided if that's the right thing to do.
|
12
|
+
#
|
13
|
+
# (Incidentally, {Fluidiom} instances for deeper nested sub-samplers get
|
14
|
+
# leaked from compile at the moment)
|
15
|
+
class DSL
|
16
|
+
include Quick::Sampler::Config
|
17
|
+
include SimpleValues
|
18
|
+
include SimpleCombinators
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
# @param [Binding] binding a context to lookup unknown methods
|
22
|
+
def initialize binding = nil
|
23
|
+
setup_delegation(binding) if binding
|
24
|
+
end
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
# (see Quick::Sampler.compile)
|
28
|
+
def self.compile description: nil, &block
|
29
|
+
new(block.binding).instance_eval(&block).unwrap.tap do |sampler|
|
30
|
+
sampler.description = description
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# overloaded to display human readable text in tests output
|
35
|
+
def inspect
|
36
|
+
"Quick Sampler DSL"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Wraps a block into a lazy enumerator which will become sampler.
|
40
|
+
#
|
41
|
+
# I haven't decided yet if this is a private implementation detail
|
42
|
+
# or a powerful albeit confusing DSL verb
|
43
|
+
#
|
44
|
+
# @yieldreturn [<Sample>] a sampled value
|
45
|
+
#
|
46
|
+
def feed &block
|
47
|
+
Fluidiom.new(Base.new(block), config)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
|
53
|
+
def setup_delegation binding
|
54
|
+
@context = binding.eval("self")
|
55
|
+
|
56
|
+
# this poor man's delegation is because inheriting from SimpleDelegator breaks
|
57
|
+
# autoload for some reason
|
58
|
+
|
59
|
+
# @!visibility private
|
60
|
+
def self.method_missing *args, &block
|
61
|
+
@context.send(*args, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @!visibility private
|
65
|
+
def self.respond_to? *args
|
66
|
+
super || @context.respond_to?(*args)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "set"
|
2
|
+
using Quick::Sampler::Shrink::Refinements
|
3
|
+
|
4
|
+
module Quick
|
5
|
+
module Sampler
|
6
|
+
module Shrink
|
7
|
+
#@!visibility private
|
8
|
+
module ClassMethods
|
9
|
+
#@!visibility private
|
10
|
+
module While
|
11
|
+
def while values, &block
|
12
|
+
shrunk = Set.new
|
13
|
+
input_size = values.count
|
14
|
+
values = values.reduce(Set.new) { |shrinking, current|
|
15
|
+
report "shrunk: #{shrunk.count}, still shrinking: #{shrinking.count}"
|
16
|
+
shrunk_current = (current.shrink||[])
|
17
|
+
.reject{|candidate| candidate == current}
|
18
|
+
.select(&block)
|
19
|
+
shrunk << current if shrunk_current.empty?
|
20
|
+
shrinking + shrunk_current
|
21
|
+
} until values.empty?
|
22
|
+
report ""
|
23
|
+
shrunk.to_a
|
24
|
+
end
|
25
|
+
|
26
|
+
def report message
|
27
|
+
width = 80
|
28
|
+
message = "" if message == :clear
|
29
|
+
message = message[0...width]
|
30
|
+
padding = " " * [width - message.length, 0].max
|
31
|
+
print "#{message}#{padding}\r"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Quick
|
2
|
+
module Sampler
|
3
|
+
module Shrink
|
4
|
+
#@!visibility private
|
5
|
+
module Refinements
|
6
|
+
|
7
|
+
refine ::Object do
|
8
|
+
def shrink
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
refine ::Integer do
|
13
|
+
def shrink
|
14
|
+
# -1, 0 and 1 can't be shrunken
|
15
|
+
if self > 10
|
16
|
+
[self/2]
|
17
|
+
elsif self > 1
|
18
|
+
[self - 1]
|
19
|
+
elsif self < -1
|
20
|
+
[-self.abs.shrink.first]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
refine ::String do
|
26
|
+
def shrink
|
27
|
+
if length > 1
|
28
|
+
remove_indices = (0...length).to_a
|
29
|
+
remove_indices = remove_indices.sample(1) if length > 9
|
30
|
+
remove_indices.map{|i| remove_by_index(i)}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_by_index index
|
35
|
+
dup.tap{|copy| copy.slice!(index)}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative "sampler/autoload"
|
2
|
+
require "active_support/core_ext/module/delegation"
|
3
|
+
|
4
|
+
# A prefix module for future Quick Sampler's sister libraries.
|
5
|
+
# For now just look at {Quick::Sampler} itself.
|
6
|
+
module Quick
|
7
|
+
# A façade module with the main entry point: {.compile}
|
8
|
+
module Sampler
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Main entry point of Quick Sampler. Compiles a definition into a sampler. The
|
13
|
+
# "compilation" is only a metaphor here, since definition - which is given to `compile` as
|
14
|
+
# a block - is simply executed in the context of a fresh {Quick::Sampler::DSL}
|
15
|
+
# instance (where available syntax can be found).
|
16
|
+
#
|
17
|
+
# @example Compile a sampler
|
18
|
+
# chaos = Quick::Sampler.compile description: "a bit of everything" do
|
19
|
+
# one_of_weighted integer => 5,
|
20
|
+
# boolean => 1,
|
21
|
+
# pick_from(-10.0..10.0) => 10,
|
22
|
+
# string(:alnum) => 15,
|
23
|
+
# feed { Faker::Internet.email } => 3
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# @param [String] description sampler description which
|
27
|
+
# will be returned by `#inspect` as well as `#description`
|
28
|
+
#
|
29
|
+
# @param block sampler definition. Will be `instance_eval`ed in the context of an
|
30
|
+
# anonymous instance of {Quick::Sampler::DSL}.
|
31
|
+
#
|
32
|
+
# @return [Quick::Sampler] compiled sampler
|
33
|
+
#
|
34
|
+
# @!method compile(description: nil, &block)
|
35
|
+
delegate :compile, to: Quick::Sampler::DSL
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
#
|
39
|
+
# Tests if `obj` is an instance of a known Quick Sampler sub-type.
|
40
|
+
#
|
41
|
+
# @param [Object] obj the object to test
|
42
|
+
# @return [Boolean] true if the object is of a Quick Sampler sub-type
|
43
|
+
def === obj
|
44
|
+
Base === obj || DSL::Fluidiom === obj
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "quick/sampler/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "quick-sampler"
|
8
|
+
spec.version = Quick::Sampler::VERSION
|
9
|
+
spec.authors = ["Artem Baguinski"]
|
10
|
+
spec.email = ["abaguinski@depraktijkindex.nl"]
|
11
|
+
spec.summary = %q{Composable samplers of random data}
|
12
|
+
spec.description = %q{Describe randomness and watch it blend}
|
13
|
+
spec.homepage = "https://github.com/praktijkindex/quick-sampler"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.4"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
24
|
+
spec.add_development_dependency "yard"
|
25
|
+
spec.add_development_dependency "redcarpet"
|
26
|
+
|
27
|
+
spec.add_dependency "activesupport"
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
describe Quick::Sampler do
|
2
|
+
specify do
|
3
|
+
expect(Quick::Sampler).to have_method :compile
|
4
|
+
end
|
5
|
+
|
6
|
+
context "given a simple script" do
|
7
|
+
let(:sampler) { Quick::Sampler.compile { const(:it_lives!) } }
|
8
|
+
it "compiles a sampler from a DSL script" do
|
9
|
+
expect(sampler).to be_a Quick::Sampler::Base
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "given a description: option" do
|
14
|
+
let(:sampler) {
|
15
|
+
Quick::Sampler.compile description: "wine sampler" do
|
16
|
+
one_of(:chiraz, :pinot_noir, :riesling)
|
17
|
+
end
|
18
|
+
}
|
19
|
+
|
20
|
+
it "sets description as sampler's description" do
|
21
|
+
expect(sampler.description).to be == "wine sampler"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "makes the sampler to return description when inspected" do
|
25
|
+
expect(sampler.inspect).to be == "wine sampler"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "given an unbound reference in the script" do
|
30
|
+
let(:external_thing) { 42 }
|
31
|
+
let(:sampler) {
|
32
|
+
Quick::Sampler.compile do
|
33
|
+
const(external_thing)
|
34
|
+
end
|
35
|
+
}
|
36
|
+
|
37
|
+
it "resolves it in the context compile was called in" do
|
38
|
+
expect(sampler.first).to be 42
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
describe Quick::Sampler::DSL do
|
2
|
+
it { is_expected.to have_method :list_like }
|
3
|
+
let(:dsl) { subject }
|
4
|
+
|
5
|
+
describe "#list_like" do
|
6
|
+
let(:sampled_lists) {
|
7
|
+
dsl.list_like(*args).first(5)
|
8
|
+
}
|
9
|
+
let(:positive_integer) { dsl.positive_integer }
|
10
|
+
let(:negative_integer) { dsl.negative_integer }
|
11
|
+
|
12
|
+
context "given no arguments" do
|
13
|
+
let(:args) { [] }
|
14
|
+
it "repeats empty lists" do
|
15
|
+
expect(sampled_lists).to all match []
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "given only constants" do
|
20
|
+
let(:args) { [1, 2, 3] }
|
21
|
+
it "repeats args as is" do
|
22
|
+
expect(sampled_lists).to all eq args
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "given a list of samplers" do
|
27
|
+
let(:args) { [positive_integer, negative_integer] }
|
28
|
+
it "samples from each sampler" do
|
29
|
+
expect(sampled_lists).to all match [a_value > 0, a_value < 0]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "given a combination of samplers and constants" do
|
34
|
+
let(:args) { ["text", positive_integer, negative_integer, 3.14159] }
|
35
|
+
it "keeps constants as is, but samples the samplers" do
|
36
|
+
expect(sampled_lists).to all match ["text", a_value > 0, a_value < 0, 3.14159]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "given a hash with constant keys and values" do
|
41
|
+
let(:args) { [const: 42, another: 666] }
|
42
|
+
it "repeats the hash as is" do
|
43
|
+
expect(sampled_lists).to all match args
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "given a hash with sampler values" do
|
48
|
+
let(:args) { [positive_integer: positive_integer, negative_integer: negative_integer] }
|
49
|
+
it "samples the values" do
|
50
|
+
expect(sampled_lists).to all match [positive_integer: a_value > 0, negative_integer: a_value < 1]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "given a nested array with samplers" do
|
55
|
+
let(:args) { ["outie", ["innie", positive_integer, negative_integer, 3.14159, option: positive_integer]] }
|
56
|
+
it "recurses into the array" do
|
57
|
+
expect(sampled_lists).to all match ["outie", ["innie", a_value > 0, a_value < 0, 3.14159, option: a_value > 0]]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
describe Quick::Sampler::DSL do
|
2
|
+
it { is_expected.to have_method :send_to }
|
3
|
+
let(:dsl) { subject }
|
4
|
+
|
5
|
+
describe "#send_to" do
|
6
|
+
let(:sampled_results) {
|
7
|
+
dsl.send_to(*args).first(5)
|
8
|
+
}
|
9
|
+
let(:two) { double("two", value: 2, string: "two") }
|
10
|
+
let(:three) { double("three", value: 3, string: "three") }
|
11
|
+
before do
|
12
|
+
allow(two).to receive(:times) { |x| 2 * x }
|
13
|
+
allow(three).to receive(:times) { |x| 3 * x }
|
14
|
+
end
|
15
|
+
|
16
|
+
context "given object and method" do
|
17
|
+
let(:args) { [two, :value] }
|
18
|
+
it "samples a method call result" do
|
19
|
+
expect(sampled_results).to all be == 2
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "given object, method and argument" do
|
24
|
+
let(:args) { [two, :times, 3] }
|
25
|
+
it "samples a method call result" do
|
26
|
+
expect(sampled_results).to all be == 6
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "given a sampler as the only argument" do
|
31
|
+
let(:args) { [ dsl.one_of([two, dsl.one_of(:value, :string)], [three, :times, 4]) ] }
|
32
|
+
it "samples the message argument" do
|
33
|
+
expect(sampled_results).to all eq(2).or eq("two").or eq(12)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "given a sampler as the first argument" do
|
38
|
+
let(:args) { [two, dsl.one_of(:value, :string)] }
|
39
|
+
it "samples the object to send to" do
|
40
|
+
expect(sampled_results).to all eq(2).or eq("two")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "given a sampler as a second argument" do
|
45
|
+
let(:args) { [dsl.one_of(two, three), :value] }
|
46
|
+
it "samples a message to send" do
|
47
|
+
expect(sampled_results).to all eq(2).or eq(3)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "given a sampler as third argument" do
|
52
|
+
let(:args) { [two, :times, dsl.one_of(2,3,4)] }
|
53
|
+
it "samples the message argument" do
|
54
|
+
expect(sampled_results).to all eq(4).or eq(6).or eq(8)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: quick-sampler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Artem Baguinski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.4'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redcarpet
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activesupport
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Describe randomness and watch it blend
|
98
|
+
email:
|
99
|
+
- abaguinski@depraktijkindex.nl
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".travis.yml"
|
107
|
+
- ".yardopts"
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- lib/quick/sampler.rb
|
113
|
+
- lib/quick/sampler/autoload.rb
|
114
|
+
- lib/quick/sampler/base.rb
|
115
|
+
- lib/quick/sampler/config.rb
|
116
|
+
- lib/quick/sampler/dsl.rb
|
117
|
+
- lib/quick/sampler/dsl/character_class.rb
|
118
|
+
- lib/quick/sampler/dsl/fluidiom.rb
|
119
|
+
- lib/quick/sampler/dsl/simple_combinators.rb
|
120
|
+
- lib/quick/sampler/dsl/simple_values.rb
|
121
|
+
- lib/quick/sampler/shrink.rb
|
122
|
+
- lib/quick/sampler/shrink/class_methods/while.rb
|
123
|
+
- lib/quick/sampler/shrink/refinements.rb
|
124
|
+
- lib/quick/sampler/version.rb
|
125
|
+
- quick-sampler.gemspec
|
126
|
+
- spec/quick/sampler/compile_spec.rb
|
127
|
+
- spec/quick/sampler/dsl/list_like_spec.rb
|
128
|
+
- spec/quick/sampler/dsl/send_to_spec.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
homepage: https://github.com/praktijkindex/quick-sampler
|
131
|
+
licenses:
|
132
|
+
- MIT
|
133
|
+
metadata: {}
|
134
|
+
post_install_message:
|
135
|
+
rdoc_options: []
|
136
|
+
require_paths:
|
137
|
+
- lib
|
138
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
requirements: []
|
149
|
+
rubyforge_project:
|
150
|
+
rubygems_version: 2.2.2
|
151
|
+
signing_key:
|
152
|
+
specification_version: 4
|
153
|
+
summary: Composable samplers of random data
|
154
|
+
test_files:
|
155
|
+
- spec/quick/sampler/compile_spec.rb
|
156
|
+
- spec/quick/sampler/dsl/list_like_spec.rb
|
157
|
+
- spec/quick/sampler/dsl/send_to_spec.rb
|
158
|
+
- spec/spec_helper.rb
|
159
|
+
has_rdoc:
|