queencheck 0.1.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,115 +1,100 @@
1
- require 'queencheck/config'
1
+ require 'queencheck/exception'
2
+ require 'queencheck/arbitrary'
3
+ require 'queencheck/result'
2
4
 
3
5
  module QueenCheck
4
- def self.new(*args)
5
- QueenCheck::Core.new(*args)
6
- end
7
-
8
- class Core
9
- def initialize(instance, method, *types)
10
- @instance = instance
11
- @method = method
12
- @types = types.map do | type |
13
- if type.respond_to?(:arbitrary?) && type.arbitrary?
14
- next type
15
- elsif type.kind_of?(Symbol)
16
- arb = QueenCheck::Arbitrary::Instance.get_by_id(type)
17
- next arb if arb
18
- end
19
- raise QueenCheck::Arbitrary::NotQueenCheckArbitrary, "`#{type}` is not implemented arbitrary"
20
- end
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
- def run(config = QueenCheck::Config.new, &block)
24
- config = config.kind_of?(Hash) ? QueenCheck::Config.new(config) : config
35
+ DEFAULT_TEST_COUNT = 100
25
36
 
26
- stats = QueenCheck::Core::Result.new(config.count)
27
-
28
- config.count.times do | n |
29
- range = n.to_f / config.count
30
- arguments = []
31
- @types.each do | type |
32
- arguments.push(type.arbitrary(range))
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
- class Result
44
- def initialize(examples)
45
- @examples = examples
46
- @tasks = []
47
- end
48
- attr_reader :examples
49
-
50
- def push(obj)
51
- @tasks.push(obj)
52
- end
53
-
54
- def [](index)
55
- @tasks[index]
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
- class Task
72
- def initialize(instance, method, args)
73
- @is_pass = nil
74
- @instance = instance
75
- @method = method
76
- @arguments = args
77
- @result = nil
78
- @exception = nil
79
- end
80
- attr_reader :is_pass, :instance, :method, :arguments, :result, :exception
81
-
82
- def run!(&check_block)
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
- @is_pass = !!check_block.call(@result, @arguments, @exception) if check_block
93
-
94
- return self
95
- end
96
-
97
- def to_s(verbose = false)
98
- if verbose
99
- "<#{@instance.class.name}:#{@instance.object_id}>\##{@method.kind_of?(Method) ? @method.name : '::lambda::'}(" +
100
- @arguments.map { |arg| "#{arg.class.name}:#{arg}" }.join(', ') +
101
- ") => <#{@result.class.name}:#{@result}>" +
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,4 @@
1
+ module QueenCheck
2
+ class CanNotRetryMore < SystemStackError
3
+ end
4
+ 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