queencheck 0.1.2 → 1.0.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/Gemfile +4 -0
- data/Guardfile +2 -1
- data/README.md +119 -0
- data/Rakefile +11 -0
- data/VERSION +1 -1
- data/lib/queencheck.rb +16 -13
- data/lib/queencheck/arbitraries/all.rb +5 -0
- data/lib/queencheck/arbitraries/boolean.rb +5 -0
- data/lib/queencheck/arbitraries/integer.rb +17 -0
- data/lib/queencheck/arbitraries/string.rb +39 -0
- data/lib/queencheck/arbitrary.rb +84 -40
- data/lib/queencheck/condition.rb +112 -0
- data/lib/queencheck/core.rb +82 -97
- data/lib/queencheck/exception.rb +4 -0
- data/lib/queencheck/gen.rb +254 -0
- data/lib/queencheck/result.rb +107 -0
- data/lib/queencheck/rspec.rb +50 -9
- data/queencheck.gemspec +26 -18
- data/spec/queencheck/arbitrary_spec.rb +8 -34
- data/spec/queencheck/core_spec.rb +12 -40
- data/spec/queencheck/gen_spec.rb +68 -0
- data/spec/queencheck/rspec_spec.rb +14 -14
- metadata +76 -42
- data/README.markdown +0 -24
- data/lib/queencheck/array.rb +0 -33
- data/lib/queencheck/boolean.rb +0 -9
- data/lib/queencheck/config.rb +0 -16
- data/lib/queencheck/elements_of.rb +0 -44
- data/lib/queencheck/float.rb +0 -23
- data/lib/queencheck/integer.rb +0 -16
- data/lib/queencheck/rspec/dsl.rb +0 -22
- data/lib/queencheck/string.rb +0 -47
- data/spec/queencheck/elements_of_spec.rb +0 -22
- data/spec/queencheck/integer_spec.rb +0 -17
- data/spec/queencheck/string_spec.rb +0 -13
data/lib/queencheck/core.rb
CHANGED
@@ -1,115 +1,100 @@
|
|
1
|
-
require 'queencheck/
|
1
|
+
require 'queencheck/exception'
|
2
|
+
require 'queencheck/arbitrary'
|
3
|
+
require 'queencheck/result'
|
2
4
|
|
3
5
|
module QueenCheck
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
6
|
+
# QueenCheck Testable Object
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# QueenCheck::Testable.new(Integer, Integer) do | x, y |
|
10
|
+
# x + y == y + x
|
11
|
+
# end
|
12
|
+
class Testable
|
13
|
+
# @param [Class(implmented arbitrary) or QueenCheck::Arbitrary or QueenCheck::Gen or Arbitrary Name] arbitraries
|
14
|
+
# @param [Proc] assertion assert proc
|
15
|
+
# @return [QueenCheck::Testable] new Testable instance
|
16
|
+
# @example
|
17
|
+
# QueenCheck::Testable(Integer, Integer.arbitrary, Integer.arbitrary.gen, :Integer)
|
18
|
+
def initialize(*arbitraries, &assertion)
|
19
|
+
@arbitraries = arbitraries.map { | arbitrary |
|
20
|
+
arb = (
|
21
|
+
if arbitrary.instance_of?(QueenCheck::Arbitrary)
|
22
|
+
arbitrary.gen
|
23
|
+
elsif arbitrary.instance_of?(QueenCheck::Gen)
|
24
|
+
arbitrary
|
25
|
+
else
|
26
|
+
QueenCheck::Arbitrary(arbitrary)
|
27
|
+
end
|
28
|
+
)
|
29
|
+
raise TypeError, "Not Implemented Arbitrary or Gen: #{arbitrary}" if arb.nil?
|
30
|
+
arb.instance_of?(QueenCheck::Arbitrary) ? arb.gen : arb
|
31
|
+
}
|
32
|
+
@assertion = assertion
|
21
33
|
end
|
22
34
|
|
23
|
-
|
24
|
-
config = config.kind_of?(Hash) ? QueenCheck::Config.new(config) : config
|
35
|
+
DEFAULT_TEST_COUNT = 100
|
25
36
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
37
|
+
# check assert
|
38
|
+
# @param [Integer] count number of tests
|
39
|
+
# @return [QueenCheck::ResultReport] ResultReport instance
|
40
|
+
def check(count = DEFAULT_TEST_COUNT)
|
41
|
+
results = QueenCheck::ResultReport.new
|
42
|
+
count.times do | n |
|
43
|
+
begin
|
44
|
+
results << self.assert(n.to_f / count)
|
45
|
+
rescue QueenCheck::CanNotRetryMore
|
46
|
+
next
|
33
47
|
end
|
34
|
-
|
35
|
-
stats.push(QueenCheck::Core::Task.new(@instance, @method, arguments).run!(&block))
|
36
|
-
|
37
|
-
puts taks.to_s(true) if config.verbose?
|
38
48
|
end
|
39
|
-
|
40
|
-
return stats
|
49
|
+
return results
|
41
50
|
end
|
42
51
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
def passes
|
59
|
-
@tasks.reject {|task| !task.is_pass }.length
|
60
|
-
end
|
61
|
-
|
62
|
-
def failures
|
63
|
-
@tasks.reject {|task| task.is_pass }.length
|
64
|
-
end
|
65
|
-
|
66
|
-
def tasks(filter = :pass)
|
67
|
-
@tasks.reject {|task| task.is_pass == (filter == :pass ? false : true) }
|
52
|
+
# check assert with label for report
|
53
|
+
# @param [Hash] labels key is String. value is Proc.
|
54
|
+
# @param [Integer] count number of tests
|
55
|
+
# @return [QueenCheck::ResultReport] ResultReport instance
|
56
|
+
# @example
|
57
|
+
# prop_int = QueenCheck::Testable(Integer, Integer){|x,y| true }
|
58
|
+
# prop_int.check_with_label(
|
59
|
+
# 'x > y' => proc {|x,y| x > y}
|
60
|
+
# )
|
61
|
+
def check_with_label(labels, count = DEFAULT_TEST_COUNT)
|
62
|
+
sets = check(count)
|
63
|
+
labels.each_pair do | label, proc |
|
64
|
+
sets.labeling(label, &proc)
|
68
65
|
end
|
66
|
+
return sets
|
69
67
|
end
|
70
68
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
return unless @is_pass.nil?
|
84
|
-
func = @method.respond_to?(:call) ? @method : @instance.method(@method)
|
85
|
-
|
86
|
-
begin
|
87
|
-
@result = func.call(*@arguments)
|
88
|
-
rescue Exception => e
|
89
|
-
@exception = e
|
69
|
+
DEFAULT_RETRY_COUNT = 100
|
70
|
+
|
71
|
+
# @param [Float] progress 0 .. 1
|
72
|
+
# @param [Integer] retry_count
|
73
|
+
#
|
74
|
+
# @return [Array<Object, Object ...>] generated values
|
75
|
+
def properties(progress, retry_count = DEFAULT_RETRY_COUNT)
|
76
|
+
@arbitraries.map { | gen |
|
77
|
+
c = 0
|
78
|
+
until (v = gen.value(progress))[1]
|
79
|
+
c += 1
|
80
|
+
raise QueenCheck::CanNotRetryMore, "can not retry generate: #{gen.inspect}" if c >= retry_count
|
90
81
|
end
|
82
|
+
v[0]
|
83
|
+
}
|
84
|
+
end
|
91
85
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
(!@is_pass ? " !! #{@exception.class.name}: #{@exception.message}" : '')
|
103
|
-
else
|
104
|
-
"#{@instance.class.name}\##{@method.respond_to?(:call) ? '::lambda::' : @method}(" +
|
105
|
-
@arguments.map {|arg| "#{arg}" } .join(', ') +
|
106
|
-
") => #{@result}" + (!@is_pass ? "! #{@exception.class.name}: #{@exception.message}": '')
|
107
|
-
end
|
86
|
+
# run assert
|
87
|
+
# @param [Float] progress 0 .. 1
|
88
|
+
# @return [QueenCheck::Result] assert result
|
89
|
+
def assert(progress)
|
90
|
+
begin
|
91
|
+
props = properties(progress)
|
92
|
+
is_success = @assertion.call(*props)
|
93
|
+
rescue => ex
|
94
|
+
is_success = false
|
95
|
+
exception = ex
|
108
96
|
end
|
97
|
+
QueenCheck::Result.new(props, is_success, exception)
|
109
98
|
end
|
110
99
|
end
|
111
100
|
end
|
112
|
-
|
113
|
-
def QueenCheck(*args)
|
114
|
-
QueenCheck.new(*args)
|
115
|
-
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
require 'queencheck/condition'
|
2
|
+
|
3
|
+
module QueenCheck
|
4
|
+
class Gen
|
5
|
+
DEFAULT_BOUND = 1000
|
6
|
+
|
7
|
+
# @param [Hash] options generater options
|
8
|
+
# @option options [Integer] :min bound min
|
9
|
+
# @option options [Integer] :max bound max
|
10
|
+
# @option options [Array<QueenCheck::Condition>] :conditions array of conditions
|
11
|
+
# @yield [p, r] progress and random int
|
12
|
+
def initialize(options = {}, &block)
|
13
|
+
@proc = block
|
14
|
+
|
15
|
+
@bound_min = options['min'] || options[:min] || 0
|
16
|
+
@bound_max = options['max'] || options[:max] || DEFAULT_BOUND
|
17
|
+
@conditions = options['conditions'] || options[:conditions] || []
|
18
|
+
end
|
19
|
+
|
20
|
+
# get value form generater
|
21
|
+
#
|
22
|
+
# @param [Float] progress 0 .. 1
|
23
|
+
# @return [Array<Any, Boolean>] Any Value & Condition Matched
|
24
|
+
def value(progress)
|
25
|
+
v = @proc.call(progress, random)
|
26
|
+
|
27
|
+
cond = @conditions.inject(true) { | bool, cond |
|
28
|
+
bool && cond.match?(v)
|
29
|
+
}
|
30
|
+
|
31
|
+
return [v, cond]
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Integer] random int in @bound_min .. @bound_max
|
35
|
+
def random
|
36
|
+
(@bound_min + rand * (@bound_max - @bound_min)).to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Hash] option
|
40
|
+
def option
|
41
|
+
{
|
42
|
+
:min => @bound_min,
|
43
|
+
:max => @bound_max,
|
44
|
+
:conditions => @conditions
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# @private
|
49
|
+
def inspect
|
50
|
+
"<QueenCheck::Gen: {#{@proc.to_s}}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
# resize bound of generater
|
54
|
+
#
|
55
|
+
# @param [Integer] lo lower bound
|
56
|
+
# @param [Integer] hi higher bound
|
57
|
+
# @example
|
58
|
+
# QueenCheck::Gen.rand.resize(-100, 100) == QueenCheck::Gen.choose(-100, 100)
|
59
|
+
# @return [QueenCheck::Gen]
|
60
|
+
def resize(lo, hi)
|
61
|
+
self.class.new(option.merge({
|
62
|
+
:min => lo,
|
63
|
+
:max => hi
|
64
|
+
}), &@proc)
|
65
|
+
end
|
66
|
+
|
67
|
+
# bind function
|
68
|
+
# @yield [x] x is generated value
|
69
|
+
# @yieldreturn [QueenCheck::Gen]
|
70
|
+
# @return [QueenCheck::Gen]
|
71
|
+
def bind
|
72
|
+
self.class.new(option) { | p, r |
|
73
|
+
yield(value(p)[0]).value(p)[0]
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
# @yield [x] x is generated value
|
78
|
+
# @yieldreturn [Object]
|
79
|
+
# @return [QueenCheck::Gen]
|
80
|
+
def fmap
|
81
|
+
bind { | x |
|
82
|
+
self.class.unit(yield(x))
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
# set conditions
|
87
|
+
#
|
88
|
+
# @overload where(conditions)
|
89
|
+
# @param [Hash] conditions { conditon_name => condition_param }
|
90
|
+
# @option conditions [QueenCheck::Condition] any_primitive_condition see QueenCheck::Condition
|
91
|
+
# @overload where(){|value| ... }
|
92
|
+
# @yield [value] value is generated value
|
93
|
+
# @yieldreturn [Boolean]
|
94
|
+
#
|
95
|
+
# @see QueenCheck::Condition
|
96
|
+
#
|
97
|
+
# @return [QueenCheck::Gen] new generater
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
# QueenCheck::Gen.elements_of(['a', 'b', 'c']).where(
|
101
|
+
# :include? => ['1', 'a', 'A'],
|
102
|
+
# :equal? => 'a'
|
103
|
+
# )
|
104
|
+
#
|
105
|
+
# QueenCheck::Gen.choose(0, 100).where { |v|
|
106
|
+
# v.odd?
|
107
|
+
# }
|
108
|
+
def where(conditions = {}, &block)
|
109
|
+
self.class.new(option.merge({
|
110
|
+
:conditions => @conditions + conditions.to_a.map { | el |
|
111
|
+
cond = el[0].to_s.sub(/([^\?])$/){$1 + '?'}
|
112
|
+
QueenCheck::Condition.method(cond).call(el[1])
|
113
|
+
} + (
|
114
|
+
!block.nil? ? [QueenCheck::Condition.new(&block)] : []
|
115
|
+
)
|
116
|
+
}), &@proc)
|
117
|
+
end
|
118
|
+
|
119
|
+
## class methods:
|
120
|
+
|
121
|
+
# choose value where >= lo and <= hi
|
122
|
+
# @param [Integer] lo bound min
|
123
|
+
# @param [Integer] hi bound max
|
124
|
+
# @example
|
125
|
+
# QueenCheck::Gen.choose(0, 100)
|
126
|
+
# @return [QueenCheck::Gen]
|
127
|
+
def self.choose(lo, hi)
|
128
|
+
elements_of((lo .. hi).to_a)
|
129
|
+
end
|
130
|
+
|
131
|
+
# choose value from ary
|
132
|
+
# @param [Array] ary
|
133
|
+
# @example
|
134
|
+
# QueenCheck::Gen.elements_of([0, 1, 2])
|
135
|
+
# @return [QueenCheck::Gen]
|
136
|
+
def self.elements_of(ary)
|
137
|
+
new({
|
138
|
+
:min => 0,
|
139
|
+
:max => ary.size
|
140
|
+
}) { | p, r |
|
141
|
+
ary[r]
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
# one of generater
|
146
|
+
# @param [Array<QueenCheck::Gen>] ary list of Generaters
|
147
|
+
# @example
|
148
|
+
# QueenCheck::Gen.one_of([
|
149
|
+
# QueenCheck::Gen.elements_of([0, 1, 3]),
|
150
|
+
# QueenCheck::Gen.elements_of(["A", "B", "C"])
|
151
|
+
# ])
|
152
|
+
# @return [QueenCheck::Gen]
|
153
|
+
def self.one_of(ary)
|
154
|
+
elements_of(ary).bind { | gen |
|
155
|
+
gen.instance_of?(QueenCheck::Gen) ? gen : (
|
156
|
+
gen.instance_of?(QueenCheck::Arbitrary) ?
|
157
|
+
gen.gen : QueenCheck::Arbitrary(gen).gen
|
158
|
+
)
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
# frequency generater
|
163
|
+
# @param [Array<[weight[Integer], QueenCheck::Gen]>] ary list of pairs weight and generater
|
164
|
+
# @example
|
165
|
+
# QueenCheck::Gen.frequency([
|
166
|
+
# [1, QueenCheck::Gen.elements_of(['a', 'b', 'c'])],
|
167
|
+
# [2, QueenCheck::Gen.elements_of(['1', '2', '3'])]
|
168
|
+
# ])
|
169
|
+
# # propability of 1/3 => 'a', 'b' or 'c'
|
170
|
+
# # propability of 2/3 => '1', '2' or '3'
|
171
|
+
# @return [QueenCheck::Gen]
|
172
|
+
def self.frequency(ary)
|
173
|
+
generaters = []
|
174
|
+
ary.each do | pair |
|
175
|
+
pair[0].times do
|
176
|
+
generaters << pair[1]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
raise ArgumentsError, "frequency: illigal weight total N > 0" if generaters.empty?
|
180
|
+
|
181
|
+
one_of(generaters)
|
182
|
+
end
|
183
|
+
|
184
|
+
# random generater
|
185
|
+
# @example
|
186
|
+
# QueenCheck::Gen.rand.resize(-100, 100)
|
187
|
+
# @return [QueenCheck::Gen]
|
188
|
+
def self.rand
|
189
|
+
new { | p, r |
|
190
|
+
r
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
# unit generater
|
195
|
+
# @param [Object] n any object
|
196
|
+
# @example
|
197
|
+
# QueenCheck::Gen.unit(1)
|
198
|
+
# # always generate 1
|
199
|
+
# @return [QueenCheck::Gen]
|
200
|
+
def self.unit(n)
|
201
|
+
new {
|
202
|
+
n
|
203
|
+
}
|
204
|
+
end
|
205
|
+
|
206
|
+
# progress generater
|
207
|
+
# @return [QueenCheck::Gen]
|
208
|
+
def self.progress
|
209
|
+
new { | p, r |
|
210
|
+
p
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
# step up generater
|
215
|
+
# @param [Array<[weight, Gen]>] ary
|
216
|
+
# @example
|
217
|
+
# QueenCheck::Gen.step_up([
|
218
|
+
# [1, QueenCheck::Gen.elements_of(['a', 'b', 'c'])],
|
219
|
+
# [2, QueenCheck::Gen.elements_of(['A', 'B', 'C'])]
|
220
|
+
# ])
|
221
|
+
# @return [QueenCheck::Gen]
|
222
|
+
def self.step_up(ary)
|
223
|
+
total = ary.inject(0) { | total, pair | total + pair.first }
|
224
|
+
|
225
|
+
self.progress.bind { |p, r|
|
226
|
+
index = total * p
|
227
|
+
|
228
|
+
gen = nil
|
229
|
+
ary.each do | pair |
|
230
|
+
gen = pair.last
|
231
|
+
break if index <= pair.first
|
232
|
+
index -= pair.first
|
233
|
+
end
|
234
|
+
|
235
|
+
gen.instance_of?(QueenCheck::Gen) ? gen : (
|
236
|
+
gen.instance_of?(QueenCheck::Arbitrary) ?
|
237
|
+
gen.gen : QueenCheck::Arbitrary(gen).gen
|
238
|
+
)
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
# quadratic function (general form) generater
|
243
|
+
# f(x) = ax^2 + bx + c
|
244
|
+
# @param [Integer] a factor a
|
245
|
+
# @param [Integer] b facror b
|
246
|
+
# @param [Integer] c factor c
|
247
|
+
# @return [QueenCheck::Gen]
|
248
|
+
def self.quadratic(a, b = 1, c = 0)
|
249
|
+
new { |p, r|
|
250
|
+
(a * (p ** 2)) + (b * p) + c
|
251
|
+
}
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|