must_be 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ pkg/
2
+ coverage/
data/README.md ADDED
@@ -0,0 +1,348 @@
1
+ # must_be Runtime Assertions
2
+
3
+ Ruby doesn't have a static type system. Tests and specs are well and good. But at the end of the day, Cthulhu reigns and sanity needs checking. must_be provides runtime assertions in all kinds of delicious Ruby flavors.
4
+
5
+ You can configure must_be to notify you of trouble in any way imaginable. Just set `MustBe.notifier` to any proc or other call worthy object. By default must_be raises an error, but logging and debugging notifiers come included. Furthermore, you can disable must_be at any time if its performance penalty proves unsatisfactory.
6
+
7
+ # Examples
8
+
9
+ After installing:
10
+
11
+ > sudo gem install must_be
12
+
13
+ Begin with customary oblations:
14
+
15
+ require 'rubygems'
16
+ require 'must_be'
17
+
18
+ Now `Object` has a number of `must`* and `must_not`* methods. Here are several examples of each. When an example notifies, the message corresponding to its `MustBe::Note` error is listed on the following comment line (`#=>` or `#~>` for a regexp match).
19
+
20
+ ## Basic
21
+
22
+ For your everyday, average sanity:
23
+
24
+ 87.must_be_a(Float, Integer)
25
+ 87.must_be_a(Symbol, String)
26
+ #=> 87.must_be_a(Symbol, String), but is a Fixnum
27
+
28
+ 87.must_not_be_a(Symbol, String, "message")
29
+ 87.must_not_be_a(Float, Integer, "message")
30
+ #=> 87.must_not_be_a(Float, Integer, "message"), but is a Fixnum
31
+
32
+ `must_be_in` uses `include?` to check. Multiple arguments are grouped together as an array:
33
+
34
+ 2.must_be_in(1, 2, 3)
35
+ 2.must_be_in([1, 2, 3])
36
+ 2.must_be_in(1 => 3, 2 => 4)
37
+ 2.must_be_in(1..3)
38
+ 2.must_be_in
39
+ #=> 2.must_be_in
40
+
41
+ 2.must_not_be_in
42
+ 2.must_not_be_in(4, 5)
43
+ 2.must_not_be_in(1 => 3, 2 => 4)
44
+ #=> 2.must_not_be_in({1=>3, 2=>4})
45
+
46
+ `must_be_boolean` ensures the value is `true` or `false`:
47
+
48
+ true.must_be_boolean
49
+ false.must_be_boolean
50
+ nil.must_be_boolean
51
+ #=> nil.must_be_boolean
52
+
53
+ Sometimes close is good enough:
54
+
55
+ 2.must_be_close(2.0)
56
+ 2.must_be_close(2.01)
57
+ 2.must_be_close(2.1)
58
+ #=> 2.must_be_close(2.1, 0.1), difference is 0.1
59
+
60
+ 2.must_be_close(2.1, 6)
61
+ 2.must_be_close(9.0, 6)
62
+ #=> 2.must_be_close(9.0, 6), difference is 7.0
63
+
64
+ `must_be` uses case (`===`) equality:
65
+
66
+ 34.must_be(Integer, :all)
67
+ :all.must_be(Integer, :all)
68
+ :none.must_be(Integer, :all)
69
+ #=> :none.must_be(Integer, :all), but matches Symbol
70
+
71
+ 5.must_be(1..5)
72
+ 5.must_be(1...5)
73
+ #=> 5.must_be(1...5), but matches Fixnum
74
+
75
+ 5.must_not_be(1...5)
76
+ 3.must_not_be(1...5)
77
+ #=> 3.must_not_be(1...5), but matches Fixnum
78
+
79
+ true.must_be
80
+ nil.must_be
81
+ #=> nil.must_be, but matches NilClass
82
+ false.must_be
83
+ #=> false.must_be, but matches FalseClass
84
+
85
+ nil.must_not_be
86
+ :hello.must_not_be
87
+ #=> :hello.must_not_be, but matches Symbol
88
+
89
+ :yep.must_be(lambda {|v| v == :yep})
90
+ :nope.must_be(lambda {|v| v == :yep})
91
+ #~> :nope.must_be\(#<Proc:0x[^.]+?>\), but matches Symbol
92
+
93
+ :yep.must_not_be(lambda {|v| v == :nope})
94
+ :nope.must_not_be(lambda {|v| v == :nope})
95
+ #~> :nope.must_not_be\(#<Proc:0x[^.]+?>\), but matches Symbol
96
+
97
+ ## Proxy
98
+
99
+ `must` either takes a block, or it returns a proxy for checking predicates:
100
+
101
+ 7.must {|x| x > 3}
102
+ 7.must {|x| x < 3}
103
+ #=> 7.must {}
104
+
105
+ 7.must_not {|x| x < 3}
106
+ 7.must_not {|x| x > 3}
107
+ #=> 7.must_not {}
108
+
109
+ 7.must == 7
110
+ 7.must > 3
111
+ 7.must.even?
112
+ #=> 7.must.even?
113
+
114
+ 7.must_not == 8
115
+ 7.must_not < 3
116
+ 7.must_not.odd?
117
+ #=> 7.must_not.odd?
118
+
119
+ ## Module#attr_typed
120
+
121
+ `attr_typed` is like `attr_accessor` but with type checking:
122
+
123
+ class Typed
124
+ attr_typed :v, Symbol
125
+ attr_typed :n, Fixnum, Bignum
126
+ attr_typed :o, &:odd?
127
+ end
128
+
129
+ t = Typed.new
130
+ t.v = :hello
131
+ t.v = "world"
132
+ #=> attribute `v' must be a Symbol, but value "world" is a String
133
+
134
+ t.n = 411
135
+ t.n = 4.1
136
+ #=> attribute `n' must be a Fixnum or Bignum, but value 4.1 is a Float
137
+
138
+ t.o = 7
139
+ t.o = 8
140
+ #=> attribute `o' cannot be 8
141
+
142
+ ## Containers
143
+
144
+ It's good to be sure that an array or hash contains the right stuff:
145
+
146
+ ["okay", :ready, "go"].must_only_contain(Symbol, String)
147
+ ["okay", :ready, 4].must_only_contain(Symbol, String)
148
+ #=> must_only_contain: 4.must_be(Symbol, String), but matches Fixnum in container ["okay", :ready, 4]
149
+
150
+ ["okay", :ready, "go"].must_not_contain(Numeric)
151
+ ["okay", :ready, 4].must_not_contain(Numeric)
152
+ #=> must_not_contain: 4.must_not_be(Numeric), but matches Fixnum in container ["okay", :ready, 4]
153
+
154
+ [].must_only_contain(:yes, :no)
155
+ [:yep].must_only_contain(:yes, :no)
156
+ #=> must_only_contain: :yep.must_be(:yes, :no), but matches Symbol in container [:yep]
157
+
158
+ [].must_not_contain(:yes, :no)
159
+ [:yes, :no].must_not_contain(:yes, :no)
160
+ #=> must_not_contain: :yes.must_not_be(:yes, :no), but matches Symbol in container [:yes, :no]
161
+
162
+ [0, [], ""].must_only_contain
163
+ [nil].must_only_contain
164
+ #=> must_only_contain: nil.must_be, but matches NilClass in container [nil]
165
+
166
+ [nil, false].must_not_contain
167
+ [0].must_not_contain
168
+ #=> must_not_contain: 0.must_not_be, but matches Fixnum in container [0]
169
+
170
+ {:welcome => :home}.must_only_contain(Symbol => Symbol)
171
+ {:symbol => :s, :fixnum => 5}.must_only_contain(Symbol => [Symbol, Fixnum])
172
+ {5 => :s, 6 => 5, :t => 5, :s => :s}.must_only_contain([Symbol, Fixnum] => [Symbol, Fixnum])
173
+ {6 => 5}.must_only_contain(Symbol => Fixnum, Fixnum => Symbol)
174
+ #=> must_only_contain: pair {6=>5} does not match [{Symbol=>Fixnum, Fixnum=>Symbol}] in container {6=>5}
175
+
176
+ {:welcome => nil}.must_not_contain(nil => Object)
177
+ {nil => :welcome}.must_not_contain(nil => Object)
178
+ #=> must_not_contain: pair {nil=>:welcome} matches [{nil=>Object}] in container {nil=>:welcome}
179
+
180
+ {:welcome => :home}.must_only_contain
181
+ {:welcome => nil}.must_only_contain
182
+ #=> must_only_contain: pair {:welcome=>nil} does not match [] in container {:welcome=>nil}
183
+
184
+ {nil => false, false => nil}.must_not_contain
185
+ {nil => 0}.must_not_contain
186
+ #=> must_not_contain: pair {nil=>0} matches [] in container {nil=>0}
187
+
188
+ It's better to be sure that a array or hash always contains the right stuff:
189
+
190
+ numbers = [].must_only_ever_contain(Numeric)
191
+ numbers << 3
192
+ numbers << :float
193
+ #=> must_only_ever_contain: Array#<<(:float): :float.must_be(Numeric), but matches Symbol in container [3, :float]
194
+
195
+ financials = [1, 4, 9].must_never_ever_contain(Float)
196
+ financials.map!{|x| Math.sqrt(x)}
197
+ #=> must_never_ever_contain: Array#map! {}: 3.0.must_not_be(Float), but matches Float in container [1.0, 2.0, 3.0]
198
+
199
+ ## MustBe::MustOnlyEverContain.register
200
+
201
+ It's best to be sure that your custom container contains the right stuff:
202
+
203
+ class Box
204
+ attr_accessor :contents
205
+
206
+ def self.[](contents = nil)
207
+ new(contents)
208
+ end
209
+
210
+ def initialize(contents = nil)
211
+ @contents = nil
212
+ end
213
+
214
+ def each
215
+ yield(contents) unless contents.nil?
216
+ end
217
+
218
+ def empty!
219
+ self.contents = nil
220
+ end
221
+
222
+ def inspect
223
+ "Box[#{contents.inspect}]"
224
+ end
225
+ end
226
+
227
+ MustOnlyEverContain.register(Box) do
228
+ def contents=(contents)
229
+ must_check_member(contents)
230
+ super
231
+ end
232
+
233
+ must_check_contents_after :empty!
234
+ end
235
+
236
+ box = Box[:hello].must_only_ever_contain(Symbol)
237
+ box.contents = :world
238
+ box.contents = 987
239
+ #=> must_only_ever_contain: Box#contents=(987): 987.must_be(Symbol), but matches Fixnum in container Box[987]
240
+
241
+ box = Box[2].must_never_ever_contain(nil)
242
+ box.contents = 64
243
+ box.empty!
244
+ #=> must_never_ever_contain: Box#empty!: nil.must_not_be(nil), but matches NilClass in container Box[nil]
245
+
246
+ ## Core
247
+
248
+ Define new must-assertions by using `must_notify`:
249
+
250
+ must_notify("message")
251
+ #=> message
252
+
253
+ must_notify(:receiver, :method, [:args], nil, ", additional message")
254
+ #=> :receiver.method(:args), additional message
255
+
256
+ note = MustBe::Note.new("message")
257
+ must_notify(note)
258
+ #=> message
259
+
260
+ Use `must_check` to temporarily override `MustBe.notify` usually as part of a bigger, better must-assertion.
261
+
262
+ note = must_check {}
263
+ note.must == nil
264
+
265
+ note = must_check { :it.must_not_be }
266
+ must_notify(note)
267
+ #=> :it.must_not_be, but matches Symbol
268
+
269
+ def add_more_if_notifies
270
+ must_check(lambda do
271
+ yield
272
+ end) do |note|
273
+ MustBe::Note.new(note.message+" more")
274
+ end
275
+ end
276
+
277
+ result = add_more_if_notifies { 5 }
278
+ result.must == 5
279
+
280
+ add_more_if_notifies { must_notify("learn") }
281
+ #=> learn more
282
+
283
+ ## Nonstandard Control Flow
284
+
285
+ Last, but not least, we have must-assertions for raising and throwing.
286
+
287
+ def rescuing
288
+ yield
289
+ rescue
290
+ raise if $!.is_a? MustBe::Note
291
+ end
292
+
293
+ rescuing { must_raise(/match/) { raise ArgumentError, "should match" } }
294
+ must_raise { "But it doesn't!" }
295
+ #=> main.must_raise {}, but nothing was raised
296
+
297
+ must_not_raise { "I'm fine."}
298
+ rescuing { must_not_raise { raise } }
299
+ #=> main.must_not_raise {}, but raised RuntimeError
300
+
301
+ catch(:ball) { must_throw { throw :ball } }
302
+ catch(:ball) { must_throw(:party) { throw :ball } }
303
+ #=> main.must_throw(:party) {}, but threw :ball
304
+
305
+ catch(:ball) { must_not_throw(:ball, :fiercely) { throw :ball, :gently } }
306
+ catch(:ball) { must_not_throw { throw :ball } }
307
+ #=> main.must_not_throw {}, but threw :ball
308
+
309
+ # Configuration
310
+
311
+ Set `MustBe.notifier` as needed. It takes a `MustBe::Note` as an argument and raises an exception unless its return value is false or nil:
312
+
313
+ MustBe.notifier = lambda do |note|
314
+ puts note
315
+ false
316
+ end
317
+
318
+ You can freely `MustBe.disable` and `MustBe.enable` as needed:
319
+
320
+ MustBe.disable
321
+
322
+ 3.must == 4
323
+ # no message
324
+
325
+ MustBe.enable
326
+
327
+ 3.must == 4
328
+ #=> 3.must.==(4)
329
+
330
+ Before requiring must_be, you can use `ENV['MUST_BE__NOTIFIER']` to set the notifier:
331
+
332
+ # Default: just raises the note.
333
+ ENV['MUST_BE__NOTIFIER'] = :raise
334
+
335
+ # Puts the note together with its backtrace.
336
+ ENV['MUST_BE__NOTIFIER'] = :log
337
+
338
+ # Invokes ruby-debug.
339
+ ENV['MUST_BE__NOTIFIER'] = :debug
340
+
341
+ # Calls MustBe.disable.
342
+ ENV['MUST_BE__NOTIFIER'] = :disable
343
+
344
+ By default `MustBe` is mixed into `Object`. If you want to mix `MustBe` selectively, set:
345
+
346
+ ENV['MUST_BE__DO_NOT_AUTOMATICALLY_INCLUDE_IN_OBJECT'] = "anything"
347
+
348
+ # Enjoy must_be.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'spec/rake/spectask'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "must_be"
7
+ gemspec.summary = "must_be Runtime Assertions"
8
+ gemspec.description = "must_be provides runtime assertions which can easily be disabled in production environments. Likewise, the notifier can be customized to raise errors, log failure, enter the debugger, or anything else."
9
+ gemspec.email = "wtaysom@gmail.com"
10
+ gemspec.homepage = "http://github.com/wtaysom/must_be"
11
+ gemspec.authors = ["William Taysom"]
12
+ end
13
+ Jeweler::GemcutterTasks.new
14
+ rescue LoadError
15
+ puts "Jeweler not available. Install it with: gem install jeweler"
16
+ end
17
+
18
+ desc "Run the spec suite against rcov"
19
+ Spec::Rake::SpecTask.new(:rcov_helper) do |t|
20
+ t.rcov = true
21
+ t.rcov_opts = ['--exclude', '/Library/Ruby/Gems/']
22
+ end
23
+
24
+ desc "Run the spec suite against rcov and open coverage results"
25
+ task :rcov => :rcov_helper do
26
+ `open coverage/index.html`
27
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,262 @@
1
+ ## Basic
2
+
3
+ 87.must_be_a(Float, Integer)
4
+ 87.must_be_a(Symbol, String)
5
+ #=> 87.must_be_a(Symbol, String), but is a Fixnum
6
+
7
+ 87.must_not_be_a(Symbol, String, "message")
8
+ 87.must_not_be_a(Float, Integer, "message")
9
+ #=> 87.must_not_be_a(Float, Integer, "message"), but is a Fixnum
10
+
11
+ 2.must_be_in(1, 2, 3)
12
+ 2.must_be_in([1, 2, 3])
13
+ 2.must_be_in(1 => 3, 2 => 4)
14
+ 2.must_be_in(1..3)
15
+ 2.must_be_in
16
+ #=> 2.must_be_in
17
+
18
+ 2.must_not_be_in
19
+ 2.must_not_be_in(4, 5)
20
+ 2.must_not_be_in(1 => 3, 2 => 4)
21
+ #=> 2.must_not_be_in({1=>3, 2=>4})
22
+
23
+ true.must_be_boolean
24
+ false.must_be_boolean
25
+ nil.must_be_boolean
26
+ #=> nil.must_be_boolean
27
+
28
+ 2.must_be_close(2.0)
29
+ 2.must_be_close(2.01)
30
+ 2.must_be_close(2.1)
31
+ #=> 2.must_be_close(2.1, 0.1), difference is 0.1
32
+
33
+ 2.must_be_close(2.1, 6)
34
+ 2.must_be_close(9.0, 6)
35
+ #=> 2.must_be_close(9.0, 6), difference is 7.0
36
+
37
+ 34.must_be(Integer, :all)
38
+ :all.must_be(Integer, :all)
39
+ :none.must_be(Integer, :all)
40
+ #=> :none.must_be(Integer, :all), but matches Symbol
41
+
42
+ 5.must_be(1..5)
43
+ 5.must_be(1...5)
44
+ #=> 5.must_be(1...5), but matches Fixnum
45
+
46
+ 5.must_not_be(1...5)
47
+ 3.must_not_be(1...5)
48
+ #=> 3.must_not_be(1...5), but matches Fixnum
49
+
50
+ true.must_be
51
+ nil.must_be
52
+ #=> nil.must_be, but matches NilClass
53
+ false.must_be
54
+ #=> false.must_be, but matches FalseClass
55
+
56
+ nil.must_not_be
57
+ :hello.must_not_be
58
+ #=> :hello.must_not_be, but matches Symbol
59
+
60
+ :yep.must_be(lambda {|v| v == :yep})
61
+ :nope.must_be(lambda {|v| v == :yep})
62
+ #~> :nope.must_be\(#<Proc:0x[^.]+?>\), but matches Symbol
63
+
64
+ :yep.must_not_be(lambda {|v| v == :nope})
65
+ :nope.must_not_be(lambda {|v| v == :nope})
66
+ #~> :nope.must_not_be\(#<Proc:0x[^.]+?>\), but matches Symbol
67
+
68
+ ## Proxy
69
+
70
+ 7.must {|x| x > 3}
71
+ 7.must {|x| x < 3}
72
+ #=> 7.must {}
73
+
74
+ 7.must_not {|x| x < 3}
75
+ 7.must_not {|x| x > 3}
76
+ #=> 7.must_not {}
77
+
78
+ 7.must == 7
79
+ 7.must > 3
80
+ 7.must.even?
81
+ #=> 7.must.even?
82
+
83
+ 7.must_not == 8
84
+ 7.must_not < 3
85
+ 7.must_not.odd?
86
+ #=> 7.must_not.odd?
87
+
88
+ ## `Module#attr_typed`
89
+
90
+ class Typed
91
+ attr_typed :v, Symbol
92
+ attr_typed :n, Fixnum, Bignum
93
+ attr_typed :o, &:odd?
94
+ end
95
+
96
+ t = Typed.new
97
+ t.v = :hello
98
+ t.v = "world"
99
+ #=> attribute `v' must be a Symbol, but value "world" is a String
100
+
101
+ t.n = 411
102
+ t.n = 4.1
103
+ #=> attribute `n' must be a Fixnum or Bignum, but value 4.1 is a Float
104
+
105
+ t.o = 7
106
+ t.o = 8
107
+ #=> attribute `o' cannot be 8
108
+
109
+ ## Containers
110
+
111
+ ["okay", :ready, "go"].must_only_contain(Symbol, String)
112
+ ["okay", :ready, 4].must_only_contain(Symbol, String)
113
+ #=> must_only_contain: 4.must_be(Symbol, String), but matches Fixnum in container ["okay", :ready, 4]
114
+
115
+ ["okay", :ready, "go"].must_not_contain(Numeric)
116
+ ["okay", :ready, 4].must_not_contain(Numeric)
117
+ #=> must_not_contain: 4.must_not_be(Numeric), but matches Fixnum in container ["okay", :ready, 4]
118
+
119
+ [].must_only_contain(:yes, :no)
120
+ [:yep].must_only_contain(:yes, :no)
121
+ #=> must_only_contain: :yep.must_be(:yes, :no), but matches Symbol in container [:yep]
122
+
123
+ [].must_not_contain(:yes, :no)
124
+ [:yes, :no].must_not_contain(:yes, :no)
125
+ #=> must_not_contain: :yes.must_not_be(:yes, :no), but matches Symbol in container [:yes, :no]
126
+
127
+ [0, [], ""].must_only_contain
128
+ [nil].must_only_contain
129
+ #=> must_only_contain: nil.must_be, but matches NilClass in container [nil]
130
+
131
+ [nil, false].must_not_contain
132
+ [0].must_not_contain
133
+ #=> must_not_contain: 0.must_not_be, but matches Fixnum in container [0]
134
+
135
+ {:welcome => :home}.must_only_contain(Symbol => Symbol)
136
+ {:symbol => :s, :fixnum => 5}.must_only_contain(Symbol => [Symbol, Fixnum])
137
+ {5 => :s, 6 => 5, :t => 5, :s => :s}.must_only_contain([Symbol, Fixnum] => [Symbol, Fixnum])
138
+ {6 => 5}.must_only_contain(Symbol => Fixnum, Fixnum => Symbol)
139
+ #=> must_only_contain: pair {6=>5} does not match [{Symbol=>Fixnum, Fixnum=>Symbol}] in container {6=>5}
140
+
141
+ {:welcome => nil}.must_not_contain(nil => Object)
142
+ {nil => :welcome}.must_not_contain(nil => Object)
143
+ #=> must_not_contain: pair {nil=>:welcome} matches [{nil=>Object}] in container {nil=>:welcome}
144
+
145
+ {:welcome => :home}.must_only_contain
146
+ {:welcome => nil}.must_only_contain
147
+ #=> must_only_contain: pair {:welcome=>nil} does not match [] in container {:welcome=>nil}
148
+
149
+ {nil => false, false => nil}.must_not_contain
150
+ {nil => 0}.must_not_contain
151
+ #=> must_not_contain: pair {nil=>0} matches [] in container {nil=>0}
152
+
153
+ numbers = [].must_only_ever_contain(Numeric)
154
+ numbers << 3
155
+ numbers << :float
156
+ #=> must_only_ever_contain: Array#<<(:float): :float.must_be(Numeric), but matches Symbol in container [3, :float]
157
+
158
+ financials = [1, 4, 9].must_never_ever_contain(Float)
159
+ financials.map!{|x| Math.sqrt(x)}
160
+ #=> must_never_ever_contain: Array#map! {}: 3.0.must_not_be(Float), but matches Float in container [1.0, 2.0, 3.0]
161
+
162
+ ## MustBe::MustOnlyEverContain.register
163
+
164
+ class Box
165
+ attr_accessor :contents
166
+
167
+ def self.[](contents = nil)
168
+ new(contents)
169
+ end
170
+
171
+ def initialize(contents = nil)
172
+ @contents = nil
173
+ end
174
+
175
+ def each
176
+ yield(contents) unless contents.nil?
177
+ end
178
+
179
+ def empty!
180
+ self.contents = nil
181
+ end
182
+
183
+ def inspect
184
+ "Box[#{contents.inspect}]"
185
+ end
186
+ end
187
+
188
+ MustOnlyEverContain.register(Box) do
189
+ def contents=(contents)
190
+ must_check_member(contents)
191
+ super
192
+ end
193
+
194
+ must_check_contents_after :empty!
195
+ end
196
+
197
+ box = Box[:hello].must_only_ever_contain(Symbol)
198
+ box.contents = :world
199
+ box.contents = 987
200
+ #=> must_only_ever_contain: Box#contents=(987): 987.must_be(Symbol), but matches Fixnum in container Box[987]
201
+
202
+ box = Box[2].must_never_ever_contain(nil)
203
+ box.contents = 64
204
+ box.empty!
205
+ #=> must_never_ever_contain: Box#empty!: nil.must_not_be(nil), but matches NilClass in container Box[nil]
206
+
207
+ ## Core
208
+
209
+ must_notify("message")
210
+ #=> message
211
+
212
+ must_notify(:receiver, :method, [:args], nil, ", additional message")
213
+ #=> :receiver.method(:args), additional message
214
+
215
+ note = MustBe::Note.new("message")
216
+ must_notify(note)
217
+ #=> message
218
+
219
+ note = must_check {}
220
+ note.must == nil
221
+
222
+ note = must_check { :it.must_not_be }
223
+ must_notify(note)
224
+ #=> :it.must_not_be, but matches Symbol
225
+
226
+ def add_more_if_notifies
227
+ must_check(lambda do
228
+ yield
229
+ end) do |note|
230
+ MustBe::Note.new(note.message+" more")
231
+ end
232
+ end
233
+
234
+ result = add_more_if_notifies { 5 }
235
+ result.must == 5
236
+
237
+ add_more_if_notifies { must_notify("learn") }
238
+ #=> learn more
239
+
240
+ ## Nonstandard Control Flow
241
+
242
+ def rescuing
243
+ yield
244
+ rescue
245
+ raise if $!.is_a? MustBe::Note
246
+ end
247
+
248
+ rescuing { must_raise(/match/) { raise ArgumentError, "should match" } }
249
+ must_raise { "But it doesn't!" }
250
+ #=> main.must_raise {}, but nothing was raised
251
+
252
+ must_not_raise { "I'm fine."}
253
+ rescuing { must_not_raise { raise } }
254
+ #=> main.must_not_raise {}, but raised RuntimeError
255
+
256
+ catch(:ball) { must_throw { throw :ball } }
257
+ catch(:ball) { must_throw(:party) { throw :ball } }
258
+ #=> main.must_throw(:party) {}, but threw :ball
259
+
260
+ catch(:ball) { must_not_throw(:ball, :fiercely) { throw :ball, :gently } }
261
+ catch(:ball) { must_not_throw { throw :ball } }
262
+ #=> main.must_not_throw {}, but threw :ball
@@ -0,0 +1,47 @@
1
+ require 'lib/must_be'
2
+
3
+ $expecting_note = false
4
+
5
+ class UnexpectedNoteError < StandardError; end
6
+ class MismatchedNoteError < StandardError; end
7
+
8
+ MustBe.notifier = lambda do |note|
9
+ unless $expecting_note
10
+ $note = note
11
+ raise UnexpectedNoteError
12
+ end
13
+ true
14
+ end
15
+
16
+ def log(message, details)
17
+ puts "expected: #{message.is_a?(Regexp) ? message.inspect : message}"
18
+ puts details
19
+ end
20
+
21
+ def check_note(message)
22
+ $expecting_note = true
23
+ yield
24
+ log(message, "but did not notify")
25
+ rescue Note => note
26
+ unless message === note.message
27
+ log(message, " found: #{note.message}")
28
+ raise MismatchedNoteError
29
+ end
30
+ ensure
31
+ $expecting_note = false
32
+ end
33
+
34
+ example_text = IO.read(File.dirname(__FILE__)+"/examples.rb")
35
+ example_text.gsub!(/^(.*)\n#=> (.*)$/, "check_note(%{\\2}) {\n\\1}")
36
+ example_text.gsub!(/^(.*)\n#~> (.*)$/, "check_note(%r{\\2}) {\n\\1}")
37
+
38
+ begin
39
+ eval(example_text)
40
+ puts "Examples are okay."
41
+ rescue UnexpectedNoteError => ex
42
+ eval_frame = ex.backtrace.last
43
+ eval_frame =~ /:(\d+)$/
44
+ eval_line = $1.to_i
45
+ puts "example.rb:#{eval_line}: unexpected note: #=> #{$note}"
46
+ rescue MismatchedNoteError
47
+ end