flexmock 0.5.1 → 0.6.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.
- data/CHANGELOG +10 -1
- data/README +390 -209
- data/Rakefile +31 -10
- data/doc/GoogleExample.rdoc +275 -0
- data/doc/releases/flexmock-0.6.0.rdoc +136 -0
- data/lib/flexmock.rb +3 -1160
- data/lib/flexmock/argument_matchers.rb +57 -0
- data/lib/flexmock/argument_types.rb +42 -0
- data/lib/flexmock/base.rb +22 -0
- data/lib/flexmock/composite.rb +10 -0
- data/lib/flexmock/core.rb +206 -0
- data/lib/flexmock/core_class_methods.rb +92 -0
- data/lib/flexmock/default_framework_adapter.rb +31 -0
- data/lib/flexmock/expectation.rb +334 -0
- data/lib/flexmock/expectation_director.rb +59 -0
- data/lib/flexmock/mock_container.rb +159 -0
- data/lib/flexmock/noop.rb +13 -0
- data/lib/flexmock/partial_mock.rb +226 -0
- data/lib/flexmock/recorder.rb +71 -0
- data/lib/flexmock/rspec.rb +34 -0
- data/lib/flexmock/test_unit.rb +32 -0
- data/lib/flexmock/test_unit_integration.rb +53 -0
- data/lib/flexmock/validators.rb +77 -0
- data/test/rspec_integration/integration_spec.rb +36 -0
- data/test/test_container_methods.rb +119 -0
- data/test/test_default_framework_adapter.rb +39 -0
- data/test/test_example.rb +1 -1
- data/test/test_extended_should_receive.rb +63 -0
- data/test/test_mock.rb +1 -1
- data/test/test_naming.rb +1 -1
- data/test/{test_any_instance.rb → test_new_instances.rb} +15 -8
- data/test/{test_stubbing.rb → test_partial_mock.rb} +44 -44
- data/test/test_record_mode.rb +1 -1
- data/test/test_samples.rb +6 -8
- data/test/test_should_receive.rb +7 -3
- data/test/test_tu_integration.rb +1 -1
- data/test/test_unit_integration/test_auto_test_unit.rb +34 -0
- metadata +30 -5
- data/test/test_class_interception.rb +0 -140
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#---
|
4
|
+
# Copyright 2003, 2004, 2005, 2006, 2007 by Jim Weirich (jim@weirichhouse.org).
|
5
|
+
# All rights reserved.
|
6
|
+
|
7
|
+
# Permission is granted for use, copying, modification, distribution,
|
8
|
+
# and distribution of modified versions of this work as long as the
|
9
|
+
# above copyright notice is included.
|
10
|
+
#+++
|
11
|
+
|
12
|
+
# No-op include file. Used as a kludge so that only the comments in the
|
13
|
+
# core.rb file are applied to the FlexMock class.
|
@@ -0,0 +1,226 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#---
|
4
|
+
# Copyright 2003, 2004, 2005, 2006, 2007 by Jim Weirich (jim@weirichhouse.org).
|
5
|
+
# All rights reserved.
|
6
|
+
|
7
|
+
# Permission is granted for use, copying, modification, distribution,
|
8
|
+
# and distribution of modified versions of this work as long as the
|
9
|
+
# above copyright notice is included.
|
10
|
+
#+++
|
11
|
+
|
12
|
+
require 'flexmock/noop'
|
13
|
+
|
14
|
+
class FlexMock
|
15
|
+
|
16
|
+
####################################################################
|
17
|
+
# PartialMock is used to mate the mock framework to an existing
|
18
|
+
# object. The object is "enhanced" with a reference to a mock
|
19
|
+
# object (stored in <tt>@flexmock_mock</tt>). When the
|
20
|
+
# +should_receive+ method is sent to the proxy, it overrides the
|
21
|
+
# existing object's method by creating singleton method that
|
22
|
+
# forwards to the mock. When testing is complete, PartialMock
|
23
|
+
# will erase the mocking infrastructure from the object being
|
24
|
+
# mocked (e.g. remove instance variables and mock singleton
|
25
|
+
# methods).
|
26
|
+
#
|
27
|
+
class PartialMock
|
28
|
+
attr_reader :mock
|
29
|
+
|
30
|
+
# Initialize a PartialMock object.
|
31
|
+
def initialize(obj, mock)
|
32
|
+
@obj = obj
|
33
|
+
@mock = mock
|
34
|
+
@method_definitions = {}
|
35
|
+
@methods_proxied = []
|
36
|
+
end
|
37
|
+
|
38
|
+
# :call-seq:
|
39
|
+
# should_receive(:method_name)
|
40
|
+
# should_receive(:method1, method2, ...)
|
41
|
+
# should_receive(:meth1 => result1, :meth2 => result2, ...)
|
42
|
+
#
|
43
|
+
# Declare that the partial mock should receive a message with the given
|
44
|
+
# name.
|
45
|
+
#
|
46
|
+
# If more than one method name is given, then the mock object should
|
47
|
+
# expect to receive all the listed melthods. If a hash of method
|
48
|
+
# name/value pairs is given, then the each method will return the
|
49
|
+
# associated result. Any expectations applied to the result of
|
50
|
+
# +should_receive+ will be applied to all the methods defined in the
|
51
|
+
# argument list.
|
52
|
+
#
|
53
|
+
# An expectation object for the method name is returned as the result of
|
54
|
+
# this method. Further expectation constraints can be added by chaining
|
55
|
+
# to the result.
|
56
|
+
#
|
57
|
+
# See Expectation for a list of declarators that can be used.
|
58
|
+
def should_receive(*args)
|
59
|
+
FlexMock.should_receive(args) do |sym|
|
60
|
+
unless @methods_proxied.include?(sym)
|
61
|
+
hide_existing_method(sym)
|
62
|
+
@methods_proxied << sym
|
63
|
+
end
|
64
|
+
ex = @mock.should_receive(sym)
|
65
|
+
ex.mock = self
|
66
|
+
ex
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# :call-seq:
|
71
|
+
# new_instances.should_receive(...)
|
72
|
+
# new_instances { |instance| instance.should_receive(...) }
|
73
|
+
#
|
74
|
+
# new_instances is a short cut method for overriding the behavior of any
|
75
|
+
# new instances created via a mocked class object.
|
76
|
+
#
|
77
|
+
# By default, new_instances will mock the behaviour of the :new and
|
78
|
+
# :allocate methods. If you wish to mock a different set of class
|
79
|
+
# methods, just pass a list of symbols to as arguments.
|
80
|
+
#
|
81
|
+
# For example, to stub only objects created by :make (and not :new
|
82
|
+
# or :allocate), use:
|
83
|
+
#
|
84
|
+
# flexmock(ClassName).new_instances(:make).should_receive(...)
|
85
|
+
#
|
86
|
+
def new_instances(*allocators, &block)
|
87
|
+
fail ArgumentError, "new_instances requires a Class to stub" unless Class === @obj
|
88
|
+
allocators = [:new, :allocate] if allocators.empty?
|
89
|
+
result = ExpectationRecorder.new
|
90
|
+
allocators.each do |m|
|
91
|
+
self.should_receive(m).and_return { |*args|
|
92
|
+
new_obj = invoke_original(m, args)
|
93
|
+
mock = mock_container.flexmock(new_obj)
|
94
|
+
block.call(mock) if block_given?
|
95
|
+
result.apply(mock)
|
96
|
+
new_obj
|
97
|
+
}
|
98
|
+
end
|
99
|
+
result
|
100
|
+
end
|
101
|
+
|
102
|
+
# any_instance is present for backwards compatibility with version 0.5.0.
|
103
|
+
# @deprecated
|
104
|
+
def any_instance(&block)
|
105
|
+
$stderr.puts "any_instance is deprecated, use new_instances instead."
|
106
|
+
new_instances(&block)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Invoke the original definition of method on the object supported by
|
110
|
+
# the stub.
|
111
|
+
def invoke_original(method, args)
|
112
|
+
method_proc = @method_definitions[method]
|
113
|
+
method_proc.call(*args)
|
114
|
+
end
|
115
|
+
private :invoke_original
|
116
|
+
|
117
|
+
# Verify that the mock has been properly called. After verification,
|
118
|
+
# detach the mocking infrastructure from the existing object.
|
119
|
+
def mock_verify
|
120
|
+
@mock.mock_verify
|
121
|
+
end
|
122
|
+
|
123
|
+
# Remove all traces of the mocking framework from the existing object.
|
124
|
+
def mock_teardown
|
125
|
+
if ! detached?
|
126
|
+
@methods_proxied.each do |method_name|
|
127
|
+
remove_current_method(method_name)
|
128
|
+
restore_original_definition(method_name)
|
129
|
+
end
|
130
|
+
@obj.instance_variable_set("@flexmock_proxy", nil)
|
131
|
+
@obj = nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Return the container for this mocking object. Returns nil if the
|
136
|
+
# mock is not in a container. Mock containers make sure that mock objects
|
137
|
+
# inside the container are torn down at the end of a test
|
138
|
+
def mock_container
|
139
|
+
@mock.mock_container
|
140
|
+
end
|
141
|
+
|
142
|
+
# Set the container for this mock object.
|
143
|
+
def mock_container=(container)
|
144
|
+
@mock.mock_container = container
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
# The singleton class of the object.
|
150
|
+
def sclass
|
151
|
+
class << @obj; self; end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Is the current method a singleton method in the object we are
|
155
|
+
# mocking?
|
156
|
+
def singleton?(method_name)
|
157
|
+
@obj.methods(false).include?(method_name.to_s)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Hide the existing method definition with a singleton defintion
|
161
|
+
# that proxies to our mock object. If the current definition is a
|
162
|
+
# singleton, we need to record the definition and remove it before
|
163
|
+
# creating our own singleton method. If the current definition is
|
164
|
+
# not a singleton, all we need to do is override it with our own
|
165
|
+
# singleton.
|
166
|
+
def hide_existing_method(method_name)
|
167
|
+
if @obj.respond_to?(method_name)
|
168
|
+
new_alias = new_name(method_name)
|
169
|
+
unless @obj.respond_to?(new_alias)
|
170
|
+
sclass.class_eval do
|
171
|
+
alias_method(new_alias, method_name)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
my_object = @obj
|
175
|
+
@method_definitions[method_name] = Proc.new { |*args|
|
176
|
+
block = nil
|
177
|
+
if Proc === args.last
|
178
|
+
block = args.last
|
179
|
+
args = args[0...-1]
|
180
|
+
end
|
181
|
+
my_object.send(new_alias, *args, &block)
|
182
|
+
}
|
183
|
+
end
|
184
|
+
remove_current_method(method_name) if singleton?(method_name)
|
185
|
+
define_proxy_method(method_name)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Define a proxy method that forwards to our mock object. The
|
189
|
+
# proxy method is defined as a singleton method on the object
|
190
|
+
# being mocked.
|
191
|
+
def define_proxy_method(method_name)
|
192
|
+
sclass.class_eval %{
|
193
|
+
def #{method_name}(*args, &block) @flexmock_proxy.mock.#{method_name}(*args, &block) end
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
# Restore the original singleton defintion for method_name that
|
198
|
+
# was saved earlier.
|
199
|
+
def restore_original_definition(method_name)
|
200
|
+
method_def = @method_definitions[method_name]
|
201
|
+
if method_def
|
202
|
+
the_alias = new_name(method_name)
|
203
|
+
sclass.class_eval do
|
204
|
+
alias_method(method_name, the_alias)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Remove the current method if it is a singleton method of the
|
210
|
+
# object being mocked.
|
211
|
+
def remove_current_method(method_name)
|
212
|
+
sclass.class_eval { remove_method(method_name) }
|
213
|
+
end
|
214
|
+
|
215
|
+
# Have we been detached from the existing object?
|
216
|
+
def detached?
|
217
|
+
@obj.nil?
|
218
|
+
end
|
219
|
+
|
220
|
+
# Generate a name to be used to alias the original behavior.
|
221
|
+
def new_name(old_name)
|
222
|
+
"flexmock_original_behavior_for_#{old_name}"
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#---
|
4
|
+
# Copyright 2003, 2004, 2005, 2006, 2007 by Jim Weirich (jim@weirichhouse.org).
|
5
|
+
# All rights reserved.
|
6
|
+
|
7
|
+
# Permission is granted for use, copying, modification, distribution,
|
8
|
+
# and distribution of modified versions of this work as long as the
|
9
|
+
# above copyright notice is included.
|
10
|
+
#+++
|
11
|
+
|
12
|
+
require 'flexmock/argument_types'
|
13
|
+
|
14
|
+
class FlexMock
|
15
|
+
|
16
|
+
####################################################################
|
17
|
+
# Translate arbitrary method calls into expectations on the given
|
18
|
+
# mock object.
|
19
|
+
#
|
20
|
+
class Recorder
|
21
|
+
include FlexMock::ArgumentTypes
|
22
|
+
|
23
|
+
# Create a method recorder for the mock +mock+.
|
24
|
+
def initialize(mock)
|
25
|
+
@mock = mock
|
26
|
+
@strict = false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Place the record in strict mode. While recording expectations
|
30
|
+
# in strict mode, the following will be true.
|
31
|
+
#
|
32
|
+
# * All expectations will be expected in the order they were
|
33
|
+
# recorded.
|
34
|
+
# * All expectations will be expected once.
|
35
|
+
# * All arguments will be placed in exact match mode,
|
36
|
+
# including regular expressions and class objects.
|
37
|
+
#
|
38
|
+
# Strict mode is usually used when giving the recorder to a known
|
39
|
+
# good algorithm. Strict mode captures the exact sequence of
|
40
|
+
# calls and validate that the code under test performs the exact
|
41
|
+
# same sequence of calls.
|
42
|
+
#
|
43
|
+
# The recorder may exit strict mode via a
|
44
|
+
# <tt>should_be_strict(false)</tt> call. Non-strict expectations
|
45
|
+
# may be recorded at that point, or even explicit expectations
|
46
|
+
# (using +should_receieve+) can be specified.
|
47
|
+
#
|
48
|
+
def should_be_strict(is_strict=true)
|
49
|
+
@strict = is_strict
|
50
|
+
end
|
51
|
+
|
52
|
+
# Is the recorder in strict mode?
|
53
|
+
def strict?
|
54
|
+
@strict
|
55
|
+
end
|
56
|
+
|
57
|
+
# Record an expectation for receiving the method +sym+ with the
|
58
|
+
# given arguments.
|
59
|
+
def method_missing(sym, *args, &block)
|
60
|
+
expectation = @mock.should_receive(sym).and_return(&block)
|
61
|
+
if strict?
|
62
|
+
args = args.collect { |arg| eq(arg) }
|
63
|
+
expectation.with(*args).ordered.once
|
64
|
+
else
|
65
|
+
expectation.with(*args)
|
66
|
+
end
|
67
|
+
expectation
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#---
|
4
|
+
# Copyright 2003, 2004, 2005, 2006, 2007 by Jim Weirich (jim@weirichhouse.org).
|
5
|
+
# All rights reserved.
|
6
|
+
|
7
|
+
# Permission is granted for use, copying, modification, distribution,
|
8
|
+
# and distribution of modified versions of this work as long as the
|
9
|
+
# above copyright notice is included.
|
10
|
+
#+++
|
11
|
+
|
12
|
+
require 'flexmock/base'
|
13
|
+
|
14
|
+
class FlexMock
|
15
|
+
|
16
|
+
class RSpecFrameworkAdapter
|
17
|
+
def assert_block(msg, &block)
|
18
|
+
Spec::Expectations.fail_with(msg) unless yield
|
19
|
+
end
|
20
|
+
|
21
|
+
def assert_equal(a, b, msg=nil)
|
22
|
+
message = msg || "Expected equal"
|
23
|
+
assert_block(message + "\n<#{a}> expected, but was\n<#{b}>") { a == b }
|
24
|
+
end
|
25
|
+
|
26
|
+
class AssertionFailedError < StandardError; end
|
27
|
+
def assertion_failed_error
|
28
|
+
Spec::Expectations::ExpectationNotMetError
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@framework_adapter = RSpecFrameworkAdapter.new
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#---
|
4
|
+
# Copyright 2003, 2004, 2005, 2006, 2007 by Jim Weirich (jim@weirichhouse.org).
|
5
|
+
# All rights reserved.
|
6
|
+
|
7
|
+
# Permission is granted for use, copying, modification, distribution,
|
8
|
+
# and distribution of modified versions of this work as long as the
|
9
|
+
# above copyright notice is included.
|
10
|
+
#+++
|
11
|
+
|
12
|
+
require 'flexmock/test_unit_integration'
|
13
|
+
|
14
|
+
module Test
|
15
|
+
module Unit
|
16
|
+
class TestCase
|
17
|
+
include FlexMock::ArgumentTypes
|
18
|
+
include FlexMock::MockContainer
|
19
|
+
|
20
|
+
# Alias the original teardown behavior for later use.
|
21
|
+
alias :flexmock_original_teardown :teardown
|
22
|
+
|
23
|
+
# Teardown the test case, verifying any mocks that might have been
|
24
|
+
# defined in this test case.
|
25
|
+
def teardown
|
26
|
+
flexmock_teardown
|
27
|
+
flexmock_original_teardown
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#---
|
4
|
+
# Copyright 2003, 2004, 2005, 2006, 2007 by Jim Weirich (jim@weirichhouse.org).
|
5
|
+
# All rights reserved.
|
6
|
+
|
7
|
+
# Permission is granted for use, copying, modification, distribution,
|
8
|
+
# and distribution of modified versions of this work as long as the
|
9
|
+
# above copyright notice is included.
|
10
|
+
#+++
|
11
|
+
|
12
|
+
require 'test/unit'
|
13
|
+
require 'flexmock/base'
|
14
|
+
|
15
|
+
class FlexMock
|
16
|
+
|
17
|
+
####################################################################
|
18
|
+
# Test::Unit::TestCase Integration.
|
19
|
+
#
|
20
|
+
# Include this module in any TestCase class in a Test::Unit test
|
21
|
+
# suite to get integration with FlexMock. When this module is
|
22
|
+
# included, the mock container methods (e.g. flexmock(), flexstub())
|
23
|
+
# will be available.
|
24
|
+
#
|
25
|
+
# <b>Note:</b> If you define a +teardown+ method in the test case,
|
26
|
+
# <em>dont' forget to invoke the +super+ method!</em> Failure to
|
27
|
+
# invoke super will cause all mocks to not be verified.
|
28
|
+
#
|
29
|
+
module TestCase
|
30
|
+
include ArgumentTypes
|
31
|
+
include MockContainer
|
32
|
+
|
33
|
+
# Teardown the test case, verifying any mocks that might have been
|
34
|
+
# defined in this test case.
|
35
|
+
def teardown
|
36
|
+
super
|
37
|
+
flexmock_teardown
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
####################################################################
|
43
|
+
# Adapter for adapting FlexMock to the Test::Unit framework.
|
44
|
+
#
|
45
|
+
class TestUnitFrameworkAdapter
|
46
|
+
include Test::Unit::Assertions
|
47
|
+
def assertion_failed_error
|
48
|
+
Test::Unit::AssertionFailedError
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@framework_adapter = TestUnitFrameworkAdapter.new
|
53
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#---
|
4
|
+
# Copyright 2003, 2004, 2005, 2006, 2007 by Jim Weirich (jim@weirichhouse.org).
|
5
|
+
# All rights reserved.
|
6
|
+
|
7
|
+
# Permission is granted for use, copying, modification, distribution,
|
8
|
+
# and distribution of modified versions of this work as long as the
|
9
|
+
# above copyright notice is included.
|
10
|
+
#+++
|
11
|
+
|
12
|
+
require 'flexmock/noop'
|
13
|
+
|
14
|
+
class FlexMock
|
15
|
+
|
16
|
+
####################################################################
|
17
|
+
# Base class for all the count validators.
|
18
|
+
#
|
19
|
+
class CountValidator
|
20
|
+
def initialize(expectation, limit)
|
21
|
+
@exp = expectation
|
22
|
+
@limit = limit
|
23
|
+
end
|
24
|
+
|
25
|
+
# If the expectation has been called +n+ times, is it still
|
26
|
+
# eligible to be called again? The default answer compares n to
|
27
|
+
# the established limit.
|
28
|
+
def eligible?(n)
|
29
|
+
n < @limit
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
####################################################################
|
34
|
+
# Validator for exact call counts.
|
35
|
+
#
|
36
|
+
class ExactCountValidator < CountValidator
|
37
|
+
# Validate that the method expectation was called exactly +n+
|
38
|
+
# times.
|
39
|
+
def validate(n)
|
40
|
+
FlexMock.framework_adapter.assert_equal @limit, n,
|
41
|
+
"method '#{@exp}' called incorrect number of times"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
####################################################################
|
46
|
+
# Validator for call counts greater than or equal to a limit.
|
47
|
+
#
|
48
|
+
class AtLeastCountValidator < CountValidator
|
49
|
+
# Validate the method expectation was called no more than +n+
|
50
|
+
# times.
|
51
|
+
def validate(n)
|
52
|
+
FlexMock.framework_adapter.assert_block(
|
53
|
+
"Method '#{@exp}' should be called at least #{@limit} times,\n" +
|
54
|
+
"only called #{n} times") { n >= @limit }
|
55
|
+
end
|
56
|
+
|
57
|
+
# If the expectation has been called +n+ times, is it still
|
58
|
+
# eligible to be called again? Since this validator only
|
59
|
+
# establishes a lower limit, not an upper limit, then the answer
|
60
|
+
# is always true.
|
61
|
+
def eligible?(n)
|
62
|
+
true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
####################################################################
|
67
|
+
# Validator for call counts less than or equal to a limit.
|
68
|
+
#
|
69
|
+
class AtMostCountValidator < CountValidator
|
70
|
+
# Validate the method expectation was called at least +n+ times.
|
71
|
+
def validate(n)
|
72
|
+
FlexMock.framework_adapter.assert_block(
|
73
|
+
"Method '#{@exp}' should be called at most #{@limit} times,\n" +
|
74
|
+
"only called #{n} times") { n <= @limit }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|