probatio 0.9.0 → 1.1.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.
@@ -0,0 +1,324 @@
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
+ def assert_not_error(*as, &block)
162
+
163
+ block = block || as.find { |a| a.is_a?(Proc) }
164
+
165
+ err = nil;
166
+ begin; block.call; rescue => err; end
167
+
168
+ return "no error expected but returned #{err.class} #{err.name}" if err
169
+
170
+ true
171
+ end
172
+ alias assert_no_error assert_not_error
173
+
174
+ # Checks whether its "_assert_something", if that's the case,
175
+ # just flags the assertion as :pending an moves on
176
+ #
177
+ def method_missing(name, *args, &block)
178
+
179
+ n = name.to_s
180
+
181
+ if n.start_with?('_assert') && self.respond_to?(n[1..-1])
182
+
183
+ Probatio.despatch(:assertion_pending, self, @__child)
184
+
185
+ :pending
186
+
187
+ else
188
+
189
+ super
190
+ end
191
+ end
192
+
193
+ # Jack of all trade assert
194
+ #
195
+ def assert(*as)
196
+
197
+ count = {
198
+ rexes: 0, hashes: 0, arrays: 0, strings: 0, scalars: 0, others: 0 }
199
+
200
+ as.each { |a|
201
+ k =
202
+ case a
203
+ when Regexp then :rexes
204
+ when Hash then :hashes
205
+ when Array then :arrays
206
+ when String then :strings
207
+ else :others; end
208
+ count[k] = count[k] + 1
209
+ count[:scalars] += 1 if %i[ rexes strings ].include?(k) }
210
+
211
+ if as.length == 1 && count[:hashes] == 1
212
+
213
+ assert_hashy(*as)
214
+
215
+ elsif as.length == 1
216
+
217
+ assert_truthy(*as)
218
+
219
+ elsif count[:rexes] == 1 && count[:strings] == as.length - 1
220
+
221
+ assert_match(*as)
222
+
223
+ #elsif count[:arrays] > 0
224
+ # assert_include(*as)
225
+
226
+ else
227
+
228
+ assert_equal(*as)
229
+ end
230
+ end
231
+
232
+ protected
233
+
234
+ def do_assert(as, msg, &block)
235
+
236
+ do_assert_(as) do
237
+
238
+ rs, ws = as.partition(&block)
239
+ wi = ws.empty? ? -1 : as.index(ws.first)
240
+
241
+ wi == -1 ? true :
242
+ "not #{msg}: arg[#{wi}]: #{val_to_s(ws.first)}"
243
+ end
244
+ end
245
+
246
+ def do_assert_(as, &block)
247
+
248
+ Probatio.despatch(:assertion_enter, self, @__child)
249
+
250
+ case r =
251
+ begin; block.call; rescue => err; err; end
252
+
253
+ when StandardError, Hash, String
254
+
255
+ aerr = make_assertion_error(as, r)
256
+
257
+ Probatio.despatch(:test_fail, self, @__child, aerr)
258
+
259
+ raise aerr
260
+
261
+ when Exception
262
+
263
+ Probatio.despatch(:test_exception, self, @__child, r)
264
+
265
+ raise r
266
+
267
+ when :pending
268
+
269
+ :pending
270
+
271
+ else
272
+
273
+ true
274
+ end
275
+
276
+ ensure
277
+
278
+ #Probatio.despatch(:test_succeed, self, @__child)
279
+ Probatio.despatch(:assertion_leave, self, @__child)
280
+ end
281
+
282
+ def make_assertion_error(arguments, result)
283
+
284
+ Probatio::AssertionError
285
+ .new(
286
+ extract_assert_method(caller),
287
+ arguments,
288
+ result,
289
+ @__child,
290
+ *extract_file_and_line(caller))
291
+ end
292
+
293
+ def extract_file_and_line(backtrace)
294
+
295
+ #l = backtrace.find { |l|
296
+ # ! l.index('lib/probatio/assertions.rb') &&
297
+ # ! l.index('_helper.rb') }
298
+ l = backtrace.find { |l| l.index('_test.rb') }
299
+
300
+ m = l && l.match(/([^:]+):(\d+)/)
301
+ m && [ m[1], m[2].to_i ]
302
+ end
303
+
304
+ def extract_assert_method(backtrace)
305
+
306
+ backtrace.inject(nil) { |r, l|
307
+ r ||
308
+ begin
309
+ m = l.match(/[^a-zA-Z_](assert_[a-z0-9_]+)/)
310
+ m && m[1]
311
+ end }
312
+ end
313
+
314
+ MAX_VS_LENGTH = 42
315
+
316
+ def val_to_s(v)
317
+
318
+ vs = v.inspect
319
+ vs = vs[0, MAX_VS_LENGTH - 1] + '…' if vs.length > MAX_VS_LENGTH
320
+
321
+ ">#{vs}<"
322
+ end
323
+ end
324
+
@@ -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
+