test-unit-mock 0.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (10) hide show
  1. data/COPYING +58 -0
  2. data/ChangeLog +262 -0
  3. data/README +203 -0
  4. data/install.rb +85 -0
  5. data/misc/readmecode.rb +125 -0
  6. data/mock.rb +467 -0
  7. data/test-unit-mock.gemspec +20 -0
  8. data/test.rb +327 -0
  9. data/utils.rb +345 -0
  10. metadata +59 -0
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # $Date: 2003/03/04 23:33:31 $
4
+ # Copyright (c) 2000 Masatoshi SEKI
5
+ #
6
+ # install.rb is copyrighted free software by Masatoshi SEKI.
7
+ # You can redistribute it and/or modify it under the same term as Ruby.
8
+
9
+ require 'rbconfig'
10
+ require 'find'
11
+ require 'ftools'
12
+
13
+ include Config
14
+
15
+ class Installer
16
+ protected
17
+ def install(from, to, mode = nil, verbose = false)
18
+ str = "install '#{from}' to '#{to}'"
19
+ str += ", mode=#{mode}" if mode
20
+ puts str if verbose
21
+ end
22
+
23
+ protected
24
+ def makedirs(*dirs)
25
+ for d in dirs
26
+ puts "mkdir #{d}"
27
+ end
28
+ end
29
+
30
+ def initialize(test=false)
31
+ @version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
32
+ @libdir = File.join(CONFIG["libdir"], "ruby", @version)
33
+ @sitelib = find_site_libdir
34
+ @ftools = (test) ? self : File
35
+ end
36
+ public
37
+ attr_reader(:libdir, :sitelib)
38
+
39
+ private
40
+ def find_site_libdir
41
+ site_libdir = $:.find {|x| x =~ /site_ruby$/}
42
+ if !site_libdir
43
+ site_libdir = File.join(@libdir, "site_ruby")
44
+ elsif site_libdir !~ Regexp.quote(@version)
45
+ site_libdir = File.join(site_libdir, @version)
46
+ end
47
+ site_libdir
48
+ end
49
+
50
+ public
51
+ def files_in_dir(dir)
52
+ list = []
53
+ Find.find(dir) do |f|
54
+ list.push(f)
55
+ end
56
+ list
57
+ end
58
+
59
+ public
60
+ def install_files(srcdir, files, destdir=@sitelib)
61
+ path = []
62
+ dir = []
63
+
64
+ for f in files
65
+ next if (f = f[srcdir.length+1..-1]) == nil
66
+ path.push f if File.ftype(File.join(srcdir, f)) == 'file'
67
+ dir |= [ File.dirname(File.join(destdir, f)) ]
68
+ end
69
+ @ftools.makedirs(*dir)
70
+ for f in path
71
+ @ftools.install(File.join(srcdir, f), File.join(destdir, f), nil, true)
72
+ end
73
+ end
74
+
75
+ public
76
+ def install_rb
77
+ intall_files('lib', files_in_dir('lib'))
78
+ end
79
+ end
80
+
81
+ if __FILE__ == $0
82
+ inst = Installer.new(ARGV.shift == '-n')
83
+ inst.install_files( '.', ['./mock.rb'], File::join(inst.sitelib, 'test/unit') )
84
+ end
85
+
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ $LOAD_PATH.unshift ".", ".."
4
+
5
+ require File::join( File::dirname(File::dirname( __FILE__ )), 'utils.rb' )
6
+
7
+ begin
8
+ require 'mock'
9
+ rescue LoadError
10
+ require 'test/unit/mock'
11
+ end
12
+ require 'socket'
13
+ require 'test/unit'
14
+
15
+ # This is just a test to make sure the code in the README works as advertised.
16
+
17
+ class MockTestExperiment < Test::Unit::TestCase
18
+
19
+ def setup
20
+ # Create the mock object
21
+ @mockSocket = Test::Unit::MockObject( TCPSocket ).new
22
+
23
+ # Make the #addr method return three cycling values (which will be repeated
24
+ # when it reaches the end
25
+ @mockSocket.setReturnValues( :addr => [
26
+ ["AF_INET", 23, "localhost", "127.0.0.1"],
27
+ ["AF_INET", 80, "slashdot.org", "66.35.250.150"],
28
+ ["AF_INET", 2401, "helium.ruby-lang.org", "210.251.121.214"],
29
+ ] )
30
+
31
+ # Make the #write and #read methods call a Proc and a Method, respectively
32
+ @mockSocket.setReturnValues( :write => Proc::new {|str| str.length},
33
+ :read => method(:fakeRead) )
34
+
35
+ # Set up the #getsockopt method to return a value based on the arguments
36
+ # given:
37
+ @mockSocket.setReturnValues( :getsockopt => {
38
+ [Socket::SOL_TCP, Socket::TCP_NODELAY] => [0].pack("i_"),
39
+ [Socket::SOL_SOCKET, Socket::SO_REUSEADDR] => [1].pack("i_"),
40
+ } )
41
+
42
+ @mockSocket.setCallOrder( :addr, :getsockopt, :write, :read, :write, :read )
43
+ @mockSocket.strictCallOrder = true
44
+ end
45
+ alias :set_up :setup
46
+
47
+ def teardown
48
+ @mockSocket = nil
49
+ end
50
+ alias :tear_down :teardown
51
+
52
+
53
+ # A method to fake reading from the socket.
54
+ def fakeRead( len )
55
+ return "x" * len
56
+ end
57
+
58
+ # This should pass
59
+ def test_correctorder
60
+ @mockSocket.activate
61
+
62
+ # Call the methods in the correct order
63
+ assert_nothing_raised {
64
+ @mockSocket.addr
65
+ @mockSocket.getsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY )
66
+ @mockSocket.write( "foo" )
67
+ @mockSocket.read( 1024 )
68
+ @mockSocket.write( "bar" )
69
+ @mockSocket.read( 4096 )
70
+ }
71
+
72
+ if $DEBUG
73
+ puts "Call trace:\n " + @mockSocket.callTrace.join("\n ")
74
+ end
75
+
76
+ # Check method call order on the mocked socket
77
+ @mockSocket.verify
78
+ end
79
+
80
+ # This should fail with an incorrect order message
81
+ def test_incorrectorder
82
+ @mockSocket.activate
83
+
84
+ # Call the methods in the correct order
85
+ assert_nothing_raised {
86
+ @mockSocket.addr
87
+ @mockSocket.getsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY )
88
+ @mockSocket.read( 1024 )
89
+ @mockSocket.write( "foo" )
90
+ @mockSocket.write( "bar" )
91
+ @mockSocket.read( 4096 )
92
+ }
93
+
94
+ if $DEBUG
95
+ puts "Call trace:\n " + @mockSocket.callTrace.join("\n ")
96
+ end
97
+
98
+ # Check method call order on the mocked socket
99
+ @mockSocket.verify
100
+ end
101
+
102
+ # This should fail with a 'missing' message
103
+ def test_missingcall
104
+ @mockSocket.activate
105
+
106
+ # Call the methods in the correct order
107
+ assert_nothing_raised {
108
+ @mockSocket.addr
109
+ @mockSocket.getsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY )
110
+ @mockSocket.write( "foo" )
111
+ @mockSocket.read( 1024 )
112
+ @mockSocket.write( "bar" )
113
+ }
114
+
115
+ if $DEBUG
116
+ puts "Call trace:\n " + @mockSocket.callTrace.join("\n ")
117
+ end
118
+
119
+ # Check method call order on the mocked socket
120
+ @mockSocket.verify
121
+ end
122
+
123
+ end
124
+
125
+
data/mock.rb ADDED
@@ -0,0 +1,467 @@
1
+ # Ruby/Mock version 1.0
2
+ #
3
+ # A class for conveniently building mock objects in Test::Unit test cases. It is
4
+ # based on ideas in Ruby/Mock by Nat Pryce <nat.pryce@b13media.com>, which is a
5
+ # class for doing much the same thing for RUnit test cases.
6
+ #
7
+ # == Examples
8
+ #
9
+ # For the examples below, it is assumed that a hypothetical '<tt>Adapter</tt>'
10
+ # class is needed to test whatever class is being tested. It has two instance
11
+ # methods in addition to its initializer: <tt>read</tt>, which takes no
12
+ # arguments and returns data read from the adapted source, and <tt>write</tt>,
13
+ # which takes a String and writes as much as it can to the adapted destination,
14
+ # returning any that is left over.
15
+ #
16
+ # # With the in-place mock-object constructor, you can make an instance of a
17
+ # # one-off anonymous test class:
18
+ # mockAdapter = Test::Unit::MockObject( Adapter ).new
19
+ #
20
+ # # Now set up some return values for the next test:
21
+ # mockAdapter.setReturnValues( :read => "",
22
+ # :write => Proc::new {|data| data[-20,20]} )
23
+ #
24
+ # # Mandate a certain order to the calls
25
+ # mockAdapter.setCallOrder( :read, :read, :read, :write, :read )
26
+ #
27
+ # # Now start the mock object recording interactions with it
28
+ # mockAdapter.activate
29
+ #
30
+ # # Send the adapter to the tested object and run the tests
31
+ # testedObject.setAdapter( mockAdapter )
32
+ # ...
33
+ #
34
+ # # Now check the order of method calls on the mock object against the expected
35
+ # # order.
36
+ # mockAdapter.verify
37
+ #
38
+ # If you require more advanced functionality in your mock class, you can also
39
+ # use the anonymous class returned by the Test::Unit::MockObject factory method
40
+ # as the superclass for your own mockup like this:
41
+ #
42
+ # # Create a mocked adapter class
43
+ # class MockAdapter < Test::Unit::MockObject( Adapter )
44
+ #
45
+ # def initialize
46
+ # super
47
+ # setCallOrder( :read, :read, :read, :write, :read )
48
+ # end
49
+ #
50
+ # def read( *args )
51
+ # @readargs = args
52
+ # super # Call the mocked method to record the call
53
+ # end
54
+ #
55
+ # def write( *args )
56
+ # @writeargs = args
57
+ # super # Call the mocked method to record the call
58
+ # end
59
+ # end
60
+ #
61
+ # == Note
62
+ #
63
+ # All the testing and setup methods in the Test::Unit::Mockup class (the
64
+ # abstract class that new MockObjects inherit from) have two aliases for your
65
+ # convenience:
66
+ #
67
+ # * Each one has a double-underscore alias so that methods which collide with
68
+ # them from the mocked class don't obscure them. Eg.,
69
+ #
70
+ # mockAdapter.__setCallOrder( :read, :read, :read, :write, :read )
71
+ #
72
+ # can be used instead of the call from the example above if the Adapter for
73
+ # some reason already has a 'setCallOrder' instance method.
74
+ #
75
+ # * Non-camelCase versions of the methods are also provided. Eg.,
76
+ #
77
+ # mockAdapter.set_call_order( :read, :read, :read, :write, :read )
78
+ #
79
+ # will work, too. Double-underscored *and* non camelCased aliases are not
80
+ # defined; if anyone complains, I'll add them.
81
+ #
82
+ # == Rcsid
83
+ #
84
+ # $Id: mock.rb,v 1.10 2003/10/01 15:11:40 deveiant Exp $
85
+ #
86
+ # == Authors
87
+ #
88
+ # * Michael Granger <ged@FaerieMUD.org>
89
+ #
90
+ #
91
+ #
92
+
93
+ require 'algorithm/diff'
94
+
95
+ require 'test/unit'
96
+ require 'test/unit/assertions'
97
+
98
+ module Test
99
+ module Unit
100
+
101
+ # The Regexp that matches methods which will not be mocked.
102
+ UnmockedMethods = %r{^(
103
+ __ # __id__, __call__, etc.
104
+ |inspect # Useful as-is for debugging, and hard to fake
105
+ |kind_of\?|is_a\?|instance_of\? # / Can't fake simply -- delegated to the
106
+ |type|class # \ actual underlying class
107
+ |method|send|respond_to\? # These will work fine as-is
108
+ |hash # There's no good way to fake this
109
+ )}x
110
+
111
+ ### An abstract base class that provides the setup and testing methods
112
+ ### to concrete mock objects.
113
+ class Mockup
114
+
115
+ include Test::Unit::Assertions
116
+
117
+ ### Instantiate and return a new mock object after recording the
118
+ ### specified args.
119
+ def initialize( *args )
120
+ @args = args
121
+ @calls = []
122
+ @activated = nil
123
+ @returnValues = Hash::new( true )
124
+ @callOrder = []
125
+ @strictCallOrder = false
126
+ end
127
+
128
+
129
+ #########################################################
130
+ ### F A K E T Y P E - C H E C K I N G M E T H O D S
131
+ #########################################################
132
+
133
+ # Handle #type and #class methods
134
+ alias :__class :class
135
+ def class # :nodoc:
136
+ return __class.mockedClass
137
+ end
138
+ undef_method :type
139
+ alias_method :type, :class
140
+
141
+ # Fake instance_of?, kind_of?, and is_a? with the mocked class
142
+ def instance_of?( klass ) # :nodoc:
143
+ self.class == klass
144
+ end
145
+ def kind_of?( klass ) # :nodoc:
146
+ self.class <= klass
147
+ end
148
+ alias_method :is_a?, :kind_of?
149
+
150
+
151
+ #########################################################
152
+ ### S E T U P M E T H O D S
153
+ #########################################################
154
+
155
+ ### Set the return value for one or more methods. The <tt>hash</tt>
156
+ ### should contain one or more key/value pairs, the key of which is
157
+ ### the symbol which corresponds to the method being configured, and
158
+ ### the value which is a specification of the value to be
159
+ ### returned. More complex returns can be configured with one the
160
+ ### following types of values:
161
+ ###
162
+ ### [<tt>Method</tt> or <tt>Proc</tt>]
163
+ ### A <tt>Method</tt> or <tt>Proc</tt> object will be called with
164
+ ### the arguments given to the method, and whatever it returns
165
+ ### will be used as the return value.
166
+ ### [<tt>Array</tt>]
167
+ ### The first value in the <tt>Array</tt> will be rotated to the
168
+ ### end of the Array and returned.
169
+ ### [<tt>Hash</tt>]
170
+ ### The Array of method arguments will be used as a key, and
171
+ ### whatever the corresponding value in the given Hash is will be
172
+ ### returned.
173
+ ###
174
+ ### Any other value will be returned as-is. To return one of the
175
+ ### above types of objects literally, just wrap it in an Array like
176
+ ### so:
177
+ ###
178
+ ### # Return a literal Proc without calling it:
179
+ ### mockObj.setReturnValues( :meth => [myProc] )
180
+ ###
181
+ ### # Return a literal Array:
182
+ ### mockObj.setReturnValues( :meth => [["an", "array", "of", "stuff"]] )
183
+ def setReturnValues( hash )
184
+ @returnValues.update hash
185
+ end
186
+ alias_method :__setReturnValues, :setReturnValues
187
+ alias_method :set_return_values, :setReturnValues
188
+
189
+
190
+ ### Set up an expected method call order and argument specification
191
+ ### to be checked when #verify is called to the methods specified by
192
+ ### the given <tt>symbols</tt>.
193
+ def setCallOrder( *symbols )
194
+ @callOrder = symbols
195
+ end
196
+ alias_method :__setCallOrder, :setCallOrder
197
+ alias_method :set_call_order, :setCallOrder
198
+
199
+
200
+ ### Set the strict call order flag. When #verify is called, the
201
+ ### methods specified in calls to #setCallOrder will be checked
202
+ ### against the actual methods that were called on the object. If
203
+ ### this flag is set to <tt>true</tt>, any deviation (missing,
204
+ ### misordered, or extra calls) results in a failed assertion. If it
205
+ ### is not set, other method calls may be interspersed between the
206
+ ### calls specified without effect, but a missing or misordered
207
+ ### method still fails.
208
+ def strictCallOrder=( flag )
209
+ @strictCallOrder = true if flag
210
+ end
211
+ alias_method :__strictCallOrder=, :strictCallOrder=
212
+ alias_method :strict_call_order=, :strictCallOrder=
213
+
214
+
215
+ ### Returns true if strict call order checking is enabled.
216
+ def strictCallOrder?
217
+ @strictCallOrder
218
+ end
219
+ alias_method :__strictCallOrder?, :strictCallOrder?
220
+ alias_method :strict_call_order?, :strictCallOrder?
221
+
222
+
223
+ #########################################################
224
+ ### T E S T I N G M E T H O D S
225
+ #########################################################
226
+
227
+ ### Returns an array of Strings describing, in cronological order,
228
+ ### what method calls were registered with the object.
229
+ def callTrace
230
+ return [] unless @activated
231
+ @calls.collect {|call|
232
+ "%s( %s ) at %0.5f seconds from %s" % [
233
+ call[:method].to_s,
234
+ call[:args].collect {|arg| arg.inspect}.join(","),
235
+ call[:time] - @activated,
236
+ call[:caller][0]
237
+ ]
238
+ }
239
+ end
240
+ alias_method :__callTrace, :callTrace
241
+ alias_method :call_trace, :callTrace
242
+
243
+
244
+ ### Returns an array of Strings describing, in cronological order,
245
+ ### what method calls were registered with the object along with a
246
+ ### full stacktrace for each call.
247
+ def fullCallTrace
248
+ return [] unless @activated
249
+ @calls.collect {|call|
250
+ "%s( %s ) at %0.5f seconds. Called from %s\n\t%s" % [
251
+ call[:method].to_s,
252
+ call[:args].collect {|arg| arg.inspect}.join(","),
253
+ call[:time] - @activated,
254
+ call[:caller][0],
255
+ call[:caller][1..-1].join("\n\t"),
256
+ ]
257
+ }
258
+ end
259
+ alias_method :__fullCallTrace, :fullCallTrace
260
+ alias_method :full_call_trace, :fullCallTrace
261
+
262
+
263
+ ### Turn on call registration -- begin testing.
264
+ def activate
265
+ raise "Already activated!" if @activated
266
+ self.__clear
267
+ @activated = Time::now
268
+ end
269
+ alias_method :__activate, :activate
270
+
271
+
272
+ ### Verify the registered required methods were called with the
273
+ ### specified args
274
+ def verify
275
+ raise "Cannot verify a mock object that has never been "\
276
+ "activated." unless @activated
277
+ return true if @callOrder.empty?
278
+
279
+ actualCallOrder = @calls.collect {|call| call[:method]}
280
+ diff = Diff::diff( @callOrder, actualCallOrder )
281
+
282
+ # In strict mode, any differences are failures
283
+ if @strictCallOrder
284
+ msg = "{Message}"
285
+ assert_block( msg ) {
286
+ unless diff.empty?
287
+ msg.replace __makeCallOrderFailMsg(*diff[0])
288
+ end
289
+ diff.empty?
290
+ }
291
+
292
+ # In non-strict mode, only methods missing (:-) from the call
293
+ # order are failures.
294
+ else
295
+ msg = "{Message}"
296
+ assert_block( msg ) {
297
+ missingDiff = diff.find {|d| d[0] == :-}
298
+ unless missingDiff.nil?
299
+ msg.replace __makeCallOrderFailMsg( *missingDiff )
300
+ end
301
+ missingDiff.nil?
302
+ }
303
+ end
304
+ end
305
+ alias_method :__verify, :verify
306
+
307
+
308
+ ### Deactivate the object without doing call order checks and clear
309
+ ### the call list, but keep its configuration.
310
+ def clear
311
+ @calls.clear
312
+ @activated = nil
313
+ end
314
+ alias_method :__clear, :clear
315
+
316
+
317
+ ### Clear the call list and call order, unset any return values, and
318
+ ### deactivate the object without checking for conformance to the
319
+ ### call order.
320
+ def reset
321
+ self.__clear
322
+ @callOrder.clear
323
+ @returnValues.clear
324
+ end
325
+ alias_method :__reset, :reset
326
+
327
+
328
+ #########
329
+ protected
330
+ #########
331
+
332
+ ### Register a call to the faked method designated by <tt>sym</tt>
333
+ ### if the object is activated, and return a value as configured by
334
+ ### #setReturnValues given the specified <tt>args</tt>.
335
+ def __mockRegisterCall( sym, *args, &block )
336
+ if @activated
337
+ @calls.push({
338
+ :method => sym,
339
+ :args => args,
340
+ :time => Time::now,
341
+ :caller => caller(2),
342
+ })
343
+ end
344
+
345
+ rval = @returnValues[ sym ]
346
+ case rval
347
+ when Method, Proc
348
+ return rval.call( *args, &block )
349
+
350
+ when Array
351
+ return rval.push( rval.shift )[-1]
352
+
353
+ when Hash
354
+ return rval[ args ]
355
+
356
+ else
357
+ return rval
358
+ end
359
+ end
360
+ alias_method :__mock_register_call, :__mockRegisterCall
361
+
362
+
363
+ ### Build and return an error message for a call-order verification
364
+ ### failure. The expected arguments are those returned in an element
365
+ ### of the Array that is returned from Diff::diff.
366
+ def __makeCallOrderFailMsg( action, position, elements )
367
+ case action
368
+
369
+ # "Extra" method/s
370
+ when :+
371
+ extraCall = @calls[ position ]
372
+ return "Call order assertion failed: Unexpected method %s "\
373
+ "called from %s at %0.5f" %
374
+ [ extraCall[:method].inspect,
375
+ extraCall[:caller][0],
376
+ extraCall[:time] - @activated ]
377
+
378
+ when :-
379
+ # If there is a call in the position specified, it was a
380
+ # misordered or extra call. If not, there was a missing
381
+ # call.
382
+ missingCall = @callOrder[ position ]
383
+ if extraCall = @calls[ position ]
384
+ return "Call order assertion failed: Expected call to %s, "\
385
+ "but got call to %s from %s at %0.5f instead" %
386
+ [ missingCall.inspect,
387
+ extraCall[:method].inspect,
388
+ extraCall[:caller][0],
389
+ extraCall[:time] - @activated ]
390
+ else
391
+ return "Call order assertion failed: Missing call to %s." %
392
+ missingCall.inspect
393
+ end
394
+
395
+ else
396
+ return "Unknown diff action '#{action.inspect}'"
397
+ end
398
+ end
399
+ alias_method :__make_call_order_fail_msg, :__makeCallOrderFailMsg
400
+
401
+
402
+ end
403
+
404
+
405
+ ### Factory method for creating semi-functional mock objects given the
406
+ ### class which is to be mocked. It looks like a constant for purposes
407
+ ### of syntactic sugar.
408
+ def self::MockObject( klass )
409
+ mockup = Class::new( Mockup )
410
+ mockup.instance_eval do @mockedClass = klass end
411
+
412
+ ### Provide an accessor to class instance var that holds the class
413
+ ### object we're faking
414
+ class << mockup
415
+
416
+ # The actual class being mocked
417
+ attr_reader :mockedClass
418
+
419
+ ### Propagate the mocked class ivar to derivatives so it can be
420
+ ### called like:
421
+ ### class MockFoo < Test::Unit::MockObject( RealClass )
422
+ def inherited( subclass )
423
+ mc = self.mockedClass
424
+ subclass.instance_eval do @mockedClass = mc end
425
+ end
426
+ end
427
+
428
+ # Build method definitions for all the mocked class's instance
429
+ # methods, as well as those given to it by its superclasses, since
430
+ # we're not really inheriting from it.
431
+ imethods = klass.instance_methods(true).collect {|name|
432
+ next if name =~ UnmockedMethods
433
+
434
+ # Figure out the argument list
435
+ argCount = klass.instance_method( name ).arity
436
+ optionalArgs = false
437
+
438
+ if argCount < 0
439
+ optionalArgs = true
440
+ argCount = (argCount+1).abs
441
+ end
442
+
443
+ args = []
444
+ argCount.times do |n| args << "arg#{n+1}" end
445
+ args << "*optionalArgs" if optionalArgs
446
+
447
+ # Build a method definition. Some methods need special
448
+ # declarations.
449
+ case name.intern
450
+ when :initialize
451
+ "def initialize( %s ) ; super ; end" % args.join(',')
452
+
453
+ else
454
+ "def %s( %s ) ; self.__mockRegisterCall(%s) ; end" %
455
+ [ name, args.join(','), [":#{name}", *args].join(',') ]
456
+ end
457
+ }
458
+
459
+ # Now add the instance methods to the mockup class
460
+ mockup.class_eval imethods.join( "\n" )
461
+ return mockup
462
+ end
463
+
464
+ end # module Unit
465
+ end # module Test
466
+
467
+