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.
@@ -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