queencheck 0.1.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|