probatio 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,311 @@
1
+
2
+ #
3
+ # probatio/assertions.rb
4
+
5
+ class Probatio::Context
6
+
7
+ # Beware: ws.any? returns false when ws == [ false ]
8
+
9
+ def assert_nil(*as)
10
+
11
+ do_assert(as, 'nil') { |a| a == nil }
12
+ end
13
+
14
+ def assert_not_nil(*as)
15
+
16
+ do_assert(as, 'not nil') { |a| a != nil }
17
+ end
18
+
19
+ def assert_truthy(*as)
20
+
21
+ do_assert(as, 'truthy') { |a| !! a }
22
+ end
23
+ alias assert_trueish assert_truthy
24
+
25
+ def assert_falsey(*as)
26
+
27
+ do_assert(as, 'falsey') { |a| ! a }
28
+ end
29
+ alias assert_falsy assert_falsey
30
+ alias assert_falseish assert_falsey
31
+
32
+ def assert_true(*as)
33
+
34
+ do_assert(as, 'true') { |a| a == true }
35
+ end
36
+
37
+ def assert_false(*as)
38
+
39
+ do_assert(as, 'false') { |a| a == false }
40
+ end
41
+
42
+ def assert_any(*as)
43
+
44
+ do_assert(as, 'any') { |a| a.respond_to?(:size) && a.size > 0 }
45
+ end
46
+
47
+ def assert_empty(*as)
48
+
49
+ do_assert(as, 'empty') { |a| a.respond_to?(:size) && a.size == 0 }
50
+ end
51
+
52
+ def assert_equal(*as)
53
+
54
+ do_assert(as, 'equal') { |a| a == as[0] }
55
+ end
56
+
57
+ def assert_match(*as)
58
+
59
+ strings, others = as.partition { |a| a.is_a?(String) }
60
+ rex = others.find { |o| o.is_a?(Regexp) } || strings.pop
61
+
62
+ do_assert(strings, 'matched') { |s| s.match?(rex) }
63
+ end
64
+
65
+ def assert_start_with(*as)
66
+
67
+ fail ArgumentError.new(
68
+ "assert_start_with expects strings, not #{x.inspect}") \
69
+ if as.find { |a| ! a.is_a?(String) }
70
+
71
+ sta = as.inject { |s, a| s.length < a.length ? s : a }
72
+
73
+ do_assert(as.filter { |a| a != sta }, 'starting with') { |a|
74
+ a.start_with?(sta) }
75
+ end
76
+
77
+ def assert_end_with(*as)
78
+
79
+ fail ArgumentError.new(
80
+ "assert_end_with expects strings, not #{x.inspect}") \
81
+ if as.find { |a| ! a.is_a?(String) }
82
+
83
+ sta = as.inject { |s, a| s.length < a.length ? s : a }
84
+
85
+ do_assert(as.filter { |a| a != sta }, 'ending with') { |a|
86
+ a.end_with?(sta) }
87
+ end
88
+
89
+ def assert_include(*as)
90
+
91
+ ai =
92
+ as.index { |a| a.is_a?(Array) } ||
93
+ fail(ArgumentError.new("assert_include found no array"))
94
+
95
+ arr = as.delete_at(ai)
96
+
97
+ do_assert(as, 'included') { |e| arr.include?(e) }
98
+ end
99
+
100
+ def assert_size(*as)
101
+
102
+ ai =
103
+ as.index { |a| a.is_a?(Integer) && a >= 0 } ||
104
+ fail(ArgumentError.new("assert_size found no integer >= 0"))
105
+
106
+ sz = as.delete_at(ai)
107
+
108
+ as = as.collect { |a| a.respond_to?(:size) ? a.size : a.class }
109
+
110
+ do_assert(as, "size #{sz}") { |a| a == sz }
111
+ end
112
+ alias assert_count assert_size
113
+
114
+ def assert_hashy(*as)
115
+
116
+ do_assert(as[0].to_a, 'hashy equal') { |k, v| k == v }
117
+ end
118
+
119
+ def assert_instance_of(*as)
120
+
121
+ moc = as.find { |a| a.is_a?(Module) }
122
+ as.delete(moc)
123
+
124
+ do_assert(as, 'instance of') { |e| e.is_a?(moc) }
125
+ end
126
+ alias assert_is_a assert_instance_of
127
+
128
+ def assert_error(*as, &block)
129
+
130
+ block = block || as.find { |a| a.is_a?(Proc) }
131
+ as.delete(block)
132
+
133
+ fail ArgumentError.new("assert_error expects a block or a proc") \
134
+ unless block
135
+
136
+ err = nil;
137
+ begin; block.call; rescue => err; end
138
+
139
+ return "no error raised" unless err.is_a?(StandardError)
140
+
141
+ as.each do |a|
142
+
143
+ case a
144
+ when String
145
+ return "error message #{err.message} is not #{a.inspect}" \
146
+ unless err.message == a
147
+ when Regexp
148
+ return "error message #{err.message} did not match #{a.inspect}" \
149
+ unless err.message.match(a)
150
+ when Module
151
+ return "error is of class #{err.class} not #{a.name}" \
152
+ unless err.is_a?(a)
153
+ else
154
+ fail ArgumentError.new("assert_error cannot fathom #{a.inspect}")
155
+ end
156
+ end
157
+
158
+ true
159
+ end
160
+
161
+ # Checks whether its "_assert_something", if that's the case,
162
+ # just flags the assertion as :pending an moves on
163
+ #
164
+ def method_missing(name, *args, &block)
165
+
166
+ n = name.to_s
167
+
168
+ if n.start_with?('_assert') && self.respond_to?(n[1..-1])
169
+
170
+ Probatio.despatch(:assertion_pending, self, @__child)
171
+
172
+ :pending
173
+
174
+ else
175
+
176
+ super
177
+ end
178
+ end
179
+
180
+ # Jack of all trade assert
181
+ #
182
+ def assert(*as)
183
+
184
+ count = {
185
+ rexes: 0, hashes: 0, arrays: 0, strings: 0, scalars: 0, others: 0 }
186
+
187
+ as.each { |a|
188
+ k =
189
+ case a
190
+ when Regexp then :rexes
191
+ when Hash then :hashes
192
+ when Array then :arrays
193
+ when String then :strings
194
+ else :others; end
195
+ count[k] = count[k] + 1
196
+ count[:scalars] += 1 if %i[ rexes strings ].include?(k) }
197
+
198
+ if as.length == 1 && count[:hashes] == 1
199
+
200
+ assert_hashy(*as)
201
+
202
+ elsif as.length == 1
203
+
204
+ assert_truthy(*as)
205
+
206
+ elsif count[:rexes] == 1 && count[:strings] == as.length - 1
207
+
208
+ assert_match(*as)
209
+
210
+ #elsif count[:arrays] > 0
211
+ # assert_include(*as)
212
+
213
+ else
214
+
215
+ assert_equal(*as)
216
+ end
217
+ end
218
+
219
+ protected
220
+
221
+ def do_assert(as, msg, &block)
222
+
223
+ do_assert_(as) do
224
+
225
+ rs, ws = as.partition(&block)
226
+ wi = ws.empty? ? -1 : as.index(ws.first)
227
+
228
+ wi == -1 ? true :
229
+ "not #{msg}: arg[#{wi}]: #{val_to_s(ws.first)}"
230
+ end
231
+ end
232
+
233
+ def do_assert_(as, &block)
234
+
235
+ Probatio.despatch(:assertion_enter, self, @__child)
236
+
237
+ case r =
238
+ begin; block.call; rescue => err; err; end
239
+
240
+ when StandardError, Hash, String
241
+
242
+ aerr = make_assertion_error(as, r)
243
+
244
+ Probatio.despatch(:test_fail, self, @__child, aerr)
245
+
246
+ raise aerr
247
+
248
+ when Exception
249
+
250
+ Probatio.despatch(:test_exception, self, @__child, r)
251
+
252
+ raise r
253
+
254
+ when :pending
255
+
256
+ :pending
257
+
258
+ else
259
+
260
+ true
261
+ end
262
+
263
+ ensure
264
+
265
+ #Probatio.despatch(:test_succeed, self, @__child)
266
+ Probatio.despatch(:assertion_leave, self, @__child)
267
+ end
268
+
269
+ def make_assertion_error(arguments, result)
270
+
271
+ Probatio::AssertionError
272
+ .new(
273
+ extract_assert_method(caller),
274
+ arguments,
275
+ result,
276
+ @__child,
277
+ *extract_file_and_line(caller))
278
+ end
279
+
280
+ def extract_file_and_line(backtrace)
281
+
282
+ #l = backtrace.find { |l|
283
+ # ! l.index('lib/probatio/assertions.rb') &&
284
+ # ! l.index('_helper.rb') }
285
+ l = backtrace.find { |l| l.index('_test.rb') }
286
+
287
+ m = l && l.match(/([^:]+):(\d+)/)
288
+ m && [ m[1], m[2].to_i ]
289
+ end
290
+
291
+ def extract_assert_method(backtrace)
292
+
293
+ backtrace.inject(nil) { |r, l|
294
+ r ||
295
+ begin
296
+ m = l.match(/[^a-zA-Z_](assert_[a-z0-9_]+)/)
297
+ m && m[1]
298
+ end }
299
+ end
300
+
301
+ MAX_VS_LENGTH = 42
302
+
303
+ def val_to_s(v)
304
+
305
+ vs = v.inspect
306
+ vs = vs[0, MAX_VS_LENGTH - 1] + '…' if vs.length > MAX_VS_LENGTH
307
+
308
+ ">#{vs}<"
309
+ end
310
+ end
311
+
@@ -0,0 +1,35 @@
1
+
2
+ #
3
+ # probatio/debug.rb
4
+
5
+ module Probatio
6
+
7
+ module DebugMethods
8
+
9
+ def debug(*as, &block)
10
+
11
+ return unless $_PROBATIO_DEBUG.include?(as.shift)
12
+
13
+ $stderr.print $_PROBATIO_COLOURS.green
14
+ $stderr.puts(*as) if as.any?
15
+ $stderr.puts block.call if block
16
+ $stderr.print $_PROBATIO_COLOURS.reset
17
+ end
18
+
19
+ def dbg_s(*as, &block)
20
+
21
+ debug(:s, *as, &block)
22
+ end
23
+
24
+ def dbg_m(*as, &block)
25
+
26
+ debug(:m, *as, &block)
27
+ end
28
+ end
29
+
30
+ class << self
31
+
32
+ include DebugMethods
33
+ end
34
+ end
35
+
@@ -0,0 +1,14 @@
1
+
2
+ #
3
+ # examples of probatio plugins
4
+
5
+ class MyProbatioPlugin
6
+
7
+ def on_test_succeed(ev)
8
+
9
+ puts "GREAT SUCCESS! " + ev.to_s
10
+ end
11
+ end
12
+
13
+ Probatio.plug(MyProbatioPlugin.new)
14
+
@@ -0,0 +1,126 @@
1
+
2
+ group 'core' do
3
+
4
+ setup do
5
+ # occurs once before tests and sub-groups in group 'core'
6
+ end
7
+ teadowm do
8
+ # occurs once after tests and sub-groups in group 'core'
9
+ end
10
+
11
+ group 'alpha' do
12
+
13
+ before do
14
+ # is run in the separate test context before _each_ test
15
+ end
16
+ after do
17
+ # is run in the separate test context after _each_ test
18
+ end
19
+
20
+ test 'one' do
21
+
22
+ MyLib.do_this_or_that()
23
+
24
+ assert_nil nil
25
+ assert_not_nil [], 1
26
+
27
+ assert_true true
28
+ assert_false 1 > 2
29
+
30
+ assert_truthy "yes", "no"
31
+ assert_trueish "yes", "no"
32
+ assert_falsy nil, false
33
+ assert_falsey nil, false
34
+ assert_falseish nil, false
35
+
36
+ assert_any %w[ an array ], 'a string'
37
+ assert_empty [], ''
38
+
39
+ assert_size [], 0
40
+ assert_size 'foo', 3
41
+ assert_size { a: 1 }, 1
42
+ assert_count %w[ a b c ], 3
43
+
44
+ assert_equal 'one', 'o' + 'ne'
45
+ # checks that all its arguments are equal
46
+
47
+ assert_match 'one', /^one$/
48
+ # checks that it receives a regex and one or more strings
49
+ # and that all those strings match the regex
50
+
51
+ assert_start_with 'one', 'one two or three'
52
+ # checks that the shortest string is the start of the remaining string
53
+ # arguments
54
+ assert_end_with 'three', 'one two or three'
55
+ # checks that the shortest string is the end of the remaining string
56
+ # arguments
57
+
58
+ assert_include 1, [ 1, 'two' ]
59
+ # checks that the first array argument includes all other arguments
60
+
61
+ assert_error(ArgumentError) { do_this_or_that() }
62
+ # checks that the given block raises an ArgumentError
63
+ assert_error(ArgumentError, /bad/) { do_this_or_that() }
64
+ # checks that the given block raises an ArgumentError and
65
+ # the error message matches the /bad/ regexp
66
+ assert_error lambda { do_this_or_that() }, ArgumentError
67
+ # checks that the given Proc raises an ArgumentError
68
+ assert_error lambda { do_this_or_that() }, ArgumentError, 'bad'
69
+ # checks that the given Proc raises an ArgumentError and
70
+ # the error message == "bad"
71
+
72
+ assert_hashy(
73
+ this_thing => 1,
74
+ that_thing => 'two')
75
+ # combines two assert_equals in one
76
+
77
+ assert_instance_of 1, Integer
78
+ assert_is_a Integer, 123
79
+ # checks that value or set of values are of a given of class
80
+
81
+ assert 1, 1
82
+ # behaves like assert_equal
83
+ assert 'one', /one/i
84
+ # behaves like assert_match
85
+ assert 11 => '10'.to_i + 1
86
+ # assert equality between key and value
87
+ assert 'one' => 'on' + 'e', 'two' => :two.to_s
88
+ # assert equality between keys and values
89
+ end
90
+ end
91
+
92
+ group 'bravo' do
93
+ end
94
+ end
95
+
96
+ group 'core' do
97
+ #
98
+ # it's OK to re-open a group to add sub-groups, tests,
99
+ # and setups, teardowns, befores, or afters
100
+ #
101
+ # it's OK to re-open a group in another file, as long
102
+ # as it's the same name at the same point in the name hierarchy
103
+
104
+ _test 'not yet' do
105
+ #
106
+ # prefix a test, a group, or other element with _
107
+ # marks it as _pending
108
+ end
109
+ end
110
+
111
+ group 'core', 'sub-core', 'sub-sub-core' do
112
+ #
113
+ # it's OK to specifiy a path of group names
114
+
115
+ test 'this' do
116
+ end
117
+ end
118
+
119
+ group 'core < sub-core < sub-sub-core' do
120
+ #
121
+ # this is also ok...
122
+
123
+ test 'that' do
124
+ end
125
+ end
126
+
@@ -0,0 +1,42 @@
1
+
2
+ #
3
+ # probatio/mangle.rb
4
+
5
+ module Probatio
6
+
7
+ class << self
8
+
9
+ def mangle(dirs, files, switches)
10
+
11
+ dry = switches['-y'] || switches['--dry']
12
+
13
+ (
14
+ dirs.collect { |d| Dir[File.join(d, '**', '*_spec.rb')] }.flatten +
15
+ files.select { |f| f.match?(/_spec\.rb$/) }
16
+ )
17
+ .each do |path|
18
+
19
+ puts c.dark_gray(". considering #{c.green(path)}")
20
+ p1 = path[0..-8] + 'test.rb'
21
+ puts c.dark_gray(" .. writing to #{c.light_green(p1)}")
22
+
23
+ next if dry
24
+
25
+ File.open(p1, 'wb') do |f|
26
+ f.write(
27
+ File.read(path)
28
+ .gsub(/^(\s*)describe(\s+)/) { "#{$1}group#{$2}" }
29
+ .gsub(/^(\s*)context(\s+)/) { "#{$1}group#{$2}" }
30
+ .gsub(/^(\s*)it(\s+)/) { "#{$1}test#{$2}" }
31
+ .gsub(/^(\s*)expect(\(|\s)/) { "#{$1}assert#{$2}" }
32
+ .gsub(/\)\.to (eq|match)\(/) { ', ' }
33
+ .gsub(/\n\s*, */) { ",\n" }
34
+ )
35
+ end
36
+
37
+ puts c.dark_gray(" .. wrote to #{c.light_green(p1)}")
38
+ end
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,151 @@
1
+
2
+ #
3
+ # probatio/more.rb
4
+
5
+
6
+ module Probatio; class << self
7
+
8
+ def monow; @ts = Process.clock_gettime(Process::CLOCK_MONOTONIC); end
9
+ def monow_and_delta; ts0, ts1 = @ts, monow; [ ts1, ts1 - (ts0 || ts1) ]; end
10
+
11
+ def seconds_to_time_s(f)
12
+
13
+ i = f.to_i
14
+ d = i / (24 * 3600); i = i % (24 * 3600)
15
+ h = i / 3600; i = i % 3600
16
+ m = i / 60; i = i % 60
17
+ s = i
18
+
19
+ ms = ((
20
+ f < 1 ? '%0.6f' :
21
+ f < 60 ? '%0.3f' :
22
+ ''
23
+ ) % (f % 1.0))[2..-1] || ''
24
+ ms = ms.insert(3, '_') if ms.length > 3
25
+
26
+ [ d > 0 ? "#{d}d" : nil,
27
+ h > 0 ? "#{h}h" : nil,
28
+ m > 0 ? "#{m}m" : nil,
29
+ "#{s}s",
30
+ "#{ms}" ].compact.join('')
31
+ end
32
+ alias to_time_s seconds_to_time_s
33
+
34
+
35
+ def to_rexes_and_strs(a)
36
+
37
+ a && a.collect { |e| to_rex_or_str(e) }
38
+ end
39
+
40
+ def to_rex_or_str(s)
41
+
42
+ m = s.match(/^\/(.+)\/([imx]*)$/); return s unless m
43
+
44
+ pat = m[1]; opts = m[2]
45
+
46
+ ropts = opts.each_char.inject(0) { |r, c|
47
+ case c
48
+ when 'i' then r |= Regexp::IGNORECASE
49
+ #when 'm' then r |= Regexp::MULTILINE
50
+ when 'x' then r |= Regexp::EXTENDED
51
+ else r; end }
52
+
53
+ Regexp.new(pat, ropts)
54
+ end
55
+
56
+ def term_width
57
+
58
+ (IO.console.winsize[1] rescue nil) ||
59
+ (`tput cols`.strip.to_i rescue nil) ||
60
+ 80
61
+ end
62
+ end; end
63
+
64
+
65
+ module Cerata; class << self
66
+
67
+ # TODO deal with double width characters...
68
+
69
+ def horizontal_a_to_s(a, indent='')
70
+
71
+ return '[]' if a.empty?
72
+
73
+ o = StringIO.new
74
+
75
+ o << indent << '[ '
76
+ a1 = a.dup; while e = a1.shift
77
+ o << e.inspect
78
+ o << ', ' if a1.any?
79
+ end
80
+ o << ' ]'
81
+
82
+ o.string
83
+ end
84
+
85
+ def horizontal_h_to_s(h, indent='')
86
+
87
+ return '{}' if h.empty?
88
+
89
+ o = StringIO.new
90
+
91
+ o << indent << '{ '
92
+ kvs = h.to_a; while kv = kvs.shift
93
+ o << "#{kv.first}: " << kv[1].inspect
94
+ o << ', ' if kvs.any?
95
+ end
96
+ o << ' }'
97
+
98
+ o.string
99
+ end
100
+
101
+ def vertical_h_to_s(h, indent='')
102
+
103
+ return '{}' if h.empty?
104
+
105
+ o = StringIO.new
106
+
107
+ o << indent << "{\n"
108
+ h.each { |k, v| o << indent << "#{k}: " << v.inspect << ",\n" }
109
+ o << indent << '}'
110
+
111
+ o.string
112
+ end
113
+
114
+ # A "table" here is an array of hashes
115
+ #
116
+ def table_to_s(a, indent='')
117
+
118
+ return '[]' if a.empty?
119
+
120
+ all_keys =
121
+ a.collect { |h| h.keys }.flatten.uniq.map(&:to_s)
122
+ key_widths =
123
+ all_keys.inject({}) { |h, k| h[k] = k.length; h }
124
+ val_widths =
125
+ a.inject({}) { |w, h|
126
+ h.each { |k, v| k = k.to_s; w[k] = [ w[k] || 0, v.inspect.length ].max }
127
+ w }
128
+
129
+ o = StringIO.new
130
+
131
+ o << indent << "[\n"
132
+
133
+ a.each do |h|
134
+ o << indent << '{ '
135
+ kvs = h.to_a; while kv = kvs.shift
136
+ k, v = kv[0].to_s, kv[1].inspect
137
+ kl, vl = key_widths[k], val_widths[k]
138
+ kf = "%#{kl}s"
139
+ vf = v.start_with?('"') ? "%#{vl}s" : "%-#{vl}s"
140
+ o << ("#{kf}: #{vf}" % [ k, v ])
141
+ o << ', ' if kvs.any?
142
+ end
143
+ o << " },\n"
144
+ end
145
+
146
+ o << indent << ']'
147
+
148
+ o.string
149
+ end
150
+ end; end
151
+