test-belt 0.2.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/Gemfile.lock +23 -0
- data/lib/test_belt/callbacks.rb +22 -0
- data/lib/test_belt/callbacks/case.rb +86 -0
- data/lib/test_belt/callbacks/suite.rb +106 -0
- data/lib/test_belt/callbacks/test.rb +58 -0
- data/lib/test_belt/context.rb +40 -0
- data/lib/test_belt/default_test.rb +18 -0
- data/lib/test_belt/helper.rb +24 -3
- data/lib/test_belt/matchers.rb +29 -0
- data/lib/test_belt/matchers/base.rb +21 -0
- data/lib/test_belt/matchers/have_accessors.rb +23 -0
- data/lib/test_belt/matchers/have_class_methods.rb +40 -0
- data/lib/test_belt/matchers/have_files.rb +38 -0
- data/lib/test_belt/matchers/have_instance_methods.rb +44 -0
- data/lib/test_belt/matchers/have_readers.rb +26 -0
- data/lib/test_belt/matchers/have_writers.rb +30 -0
- data/lib/test_belt/should.rb +76 -0
- data/lib/test_belt/skip.rb +41 -0
- data/lib/test_belt/subject.rb +47 -0
- data/lib/test_belt/version.rb +1 -1
- data/test/callbacks_test.rb +172 -0
- data/test/fixtures/{shoulda_macros/thing.rb → thing.rb} +0 -0
- data/test/helpers_test.rb +175 -0
- data/test/matchers_test.rb +135 -0
- data/test/rake_tasks_test.rb +8 -17
- metadata +33 -31
- data/lib/test_belt/shoulda_macros.rb +0 -9
- data/lib/test_belt/shoulda_macros/classes.rb +0 -105
- data/lib/test_belt/shoulda_macros/context.rb +0 -25
- data/lib/test_belt/shoulda_macros/files.rb +0 -49
- data/lib/test_belt/test_unit.rb +0 -8
- data/lib/test_belt/test_unit/context.rb +0 -71
- data/lib/test_belt/test_unit/runner.rb +0 -48
- data/lib/test_belt/test_unit/test_case.rb +0 -26
- data/test/shoulda_macros/classes_test.rb +0 -58
- data/test/shoulda_macros/context_test.rb +0 -28
- data/test/shoulda_macros/files_test.rb +0 -36
- data/test/test_unit/context_test.rb +0 -65
- data/test/test_unit/runner_test.rb +0 -31
- data/test/test_unit/test_case_test.rb +0 -30
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test_belt/matchers/have_readers'
|
2
|
+
require 'test_belt/matchers/have_writers'
|
3
|
+
|
4
|
+
module TestBelt::Matchers
|
5
|
+
module HaveAccessors
|
6
|
+
|
7
|
+
def self.included(receiver)
|
8
|
+
receiver.send(:extend, ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def have_accessors(*meths)
|
13
|
+
meths.collect do |meth|
|
14
|
+
[ ::TestBelt::Matchers::HaveReaders::Matcher.new(meth),
|
15
|
+
::TestBelt::Matchers::HaveWriters::Matcher.new(meth)
|
16
|
+
]
|
17
|
+
end.flatten
|
18
|
+
end
|
19
|
+
alias_method :have_accessor, :have_accessors
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TestBelt::Matchers
|
2
|
+
module HaveClassMethods
|
3
|
+
|
4
|
+
def self.included(receiver)
|
5
|
+
receiver.send(:extend, ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def have_class_methods(*meths)
|
10
|
+
meths.collect do |meth|
|
11
|
+
Matcher.new(meth)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
alias_method :have_class_method, :have_class_methods
|
15
|
+
end
|
16
|
+
|
17
|
+
class Matcher < ::TestBelt::Matchers::Base
|
18
|
+
def initialize(method)
|
19
|
+
unless method.kind_of?(::String) || method.kind_of?(::Symbol)
|
20
|
+
raise ArgumentError, "please specify the method name using a string or symbol"
|
21
|
+
end
|
22
|
+
@method = method
|
23
|
+
end
|
24
|
+
|
25
|
+
def desc
|
26
|
+
"respond to class method ##{@method}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches?(subject)
|
30
|
+
@subject = subject
|
31
|
+
subject.class.respond_to?(@method)
|
32
|
+
end
|
33
|
+
|
34
|
+
def fail_message
|
35
|
+
"#{@subject.class.name} does not have the class method ##{@method}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module TestBelt::Matchers
|
2
|
+
module HaveFiles
|
3
|
+
|
4
|
+
def self.included(receiver)
|
5
|
+
receiver.send(:extend, ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def have_files(*files)
|
10
|
+
files.collect do |file|
|
11
|
+
Matcher.new(file)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
alias_method :have_file, :have_files
|
15
|
+
alias_method :have_directories, :have_files
|
16
|
+
alias_method :have_directory, :have_files
|
17
|
+
end
|
18
|
+
|
19
|
+
class Matcher < ::TestBelt::Matchers::Base
|
20
|
+
def initialize(file_path)
|
21
|
+
@file_path = file_path
|
22
|
+
end
|
23
|
+
|
24
|
+
def desc
|
25
|
+
"have the file path '#{@file_path}'"
|
26
|
+
end
|
27
|
+
|
28
|
+
def matches?(subject)
|
29
|
+
File.exists?(@file_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def fail_message
|
33
|
+
"'#{@file}' does not exist"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module TestBelt::Matchers
|
2
|
+
module HaveInstanceMethods
|
3
|
+
|
4
|
+
def self.included(receiver)
|
5
|
+
receiver.send(:extend, ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def have_instance_methods(*meths)
|
10
|
+
meths.collect do |meth|
|
11
|
+
Matcher.new(meth)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
alias_method :have_instance_method, :have_instance_methods
|
15
|
+
end
|
16
|
+
|
17
|
+
class Matcher < ::TestBelt::Matchers::Base
|
18
|
+
def initialize(method)
|
19
|
+
unless method.kind_of?(::String) || method.kind_of?(::Symbol)
|
20
|
+
raise ArgumentError, "please specify the method name using a string or symbol"
|
21
|
+
end
|
22
|
+
@method = method
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_type
|
26
|
+
"instance method"
|
27
|
+
end
|
28
|
+
|
29
|
+
def desc
|
30
|
+
"respond to #{method_type} ##{@method}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def matches?(subject)
|
34
|
+
@subject = subject
|
35
|
+
subject.respond_to?(@method)
|
36
|
+
end
|
37
|
+
|
38
|
+
def fail_message
|
39
|
+
"#{@subject.class.name} does not have instance method ##{@method}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test_belt/matchers/have_instance_methods'
|
2
|
+
|
3
|
+
module TestBelt::Matchers
|
4
|
+
module HaveReaders
|
5
|
+
|
6
|
+
def self.included(receiver)
|
7
|
+
receiver.send(:extend, ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def have_readers(*meths)
|
12
|
+
meths.collect do |meth|
|
13
|
+
Matcher.new(meth)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
alias_method :have_reader, :have_readers
|
17
|
+
end
|
18
|
+
|
19
|
+
class Matcher < ::TestBelt::Matchers::HaveInstanceMethods::Matcher
|
20
|
+
def method_type
|
21
|
+
"reader"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test_belt/matchers/have_instance_methods'
|
2
|
+
|
3
|
+
module TestBelt::Matchers
|
4
|
+
module HaveWriters
|
5
|
+
|
6
|
+
def self.included(receiver)
|
7
|
+
receiver.send(:extend, ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def have_writers(*meths)
|
12
|
+
meths.collect do |meth|
|
13
|
+
Matcher.new(meth)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
alias_method :have_writer, :have_writers
|
17
|
+
end
|
18
|
+
|
19
|
+
class Matcher < ::TestBelt::Matchers::HaveInstanceMethods::Matcher
|
20
|
+
def initialize(method)
|
21
|
+
super("#{method}=")
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_type
|
25
|
+
"writer"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module TestBelt
|
2
|
+
module Should
|
3
|
+
|
4
|
+
# This defines a class method 'should' that takes a string
|
5
|
+
# describing the test and a block that executes the test. The
|
6
|
+
# test will be named:
|
7
|
+
# "test: #{context} should #{description}."
|
8
|
+
|
9
|
+
# Usage:
|
10
|
+
# class SomeTest < Test::Unit::TestCase
|
11
|
+
# extend TestBelt::Should
|
12
|
+
# end
|
13
|
+
|
14
|
+
def should(matchers_or_desc, &block)
|
15
|
+
tests = should_tests(matchers_or_desc, &block)
|
16
|
+
context = should_context
|
17
|
+
|
18
|
+
each_should(context, tests) do |name, code|
|
19
|
+
define_method(name) do
|
20
|
+
begin
|
21
|
+
self.class._testbelt_setups.each {|sb| instance_eval(&sb)}
|
22
|
+
instance_eval(&code)
|
23
|
+
ensure
|
24
|
+
self.class._testbelt_teardowns.reverse.each {|tb| instance_eval(&tb)}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# This method is identical to the should version, however this one does
|
31
|
+
# nothing but skip any tests described or matched on
|
32
|
+
def should_eventually(matchers_or_desc, &block)
|
33
|
+
tests = should_tests(matchers_or_desc, &block)
|
34
|
+
context = should_context
|
35
|
+
|
36
|
+
each_should(context, tests) do |name, code|
|
37
|
+
define_method(name) { skip }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def each_should(context, tests)
|
44
|
+
tests.each do |t|
|
45
|
+
yield should_test_name(context, t[0]), t[1]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def should_tests(matchers_or_desc, &block)
|
50
|
+
unless matchers_or_desc.kind_of?(::Array) || block_given?
|
51
|
+
raise ArgumentError, "either specify a test block and description or a set of matchers"
|
52
|
+
end
|
53
|
+
|
54
|
+
if matchers_or_desc.kind_of?(::Array)
|
55
|
+
matchers_or_desc.collect do |matcher|
|
56
|
+
[matcher.desc, Proc.new {assert_matcher(matcher)}]
|
57
|
+
end
|
58
|
+
else
|
59
|
+
[[matchers_or_desc.to_s, block]]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def should_context
|
64
|
+
'' # TODO: context stuff needs to be added to test name
|
65
|
+
end
|
66
|
+
|
67
|
+
def should_test_name(context, name)
|
68
|
+
test_name = ["test:", "should", "#{name}. "].flatten.join(' ').to_sym
|
69
|
+
if instance_methods.include?(test_name.to_s)
|
70
|
+
warn " * WARNING: '#{test_name}' is already defined"
|
71
|
+
end
|
72
|
+
test_name
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Test::Unit
|
2
|
+
class TestCase
|
3
|
+
|
4
|
+
alias_method(:orig_add_error, :add_error)
|
5
|
+
def add_error(*args, &block)
|
6
|
+
unless args.first.kind_of?(::TestBelt::TestSkipped)
|
7
|
+
orig_add_error *args, &block
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module TestBelt
|
15
|
+
class TestSkipped < Exception; end
|
16
|
+
|
17
|
+
module Skip
|
18
|
+
|
19
|
+
# Sometimes while testing, you may wish to define some tests but not run
|
20
|
+
# them just yet. LeftRight provides some nice UI for dealing with skipped
|
21
|
+
# tests. Test Belt provides a handy 'skip' method for test cases. Use
|
22
|
+
# this method to skip individual tests. If you wish to skip a whole
|
23
|
+
# context of tests, add the skip method to a before [[Callback]] and all
|
24
|
+
# the tests will be skipped for the class. Since callbacks inherit, any
|
25
|
+
# subclassed tests will be skipped as well.
|
26
|
+
|
27
|
+
# Usage:
|
28
|
+
# class SomeTest < Test::Unit::TestCase
|
29
|
+
# include TestBelt::Skip
|
30
|
+
# end
|
31
|
+
|
32
|
+
def skip(halt_test=true)
|
33
|
+
if defined? ::LeftRight
|
34
|
+
::LeftRight.state.skip = true
|
35
|
+
::LeftRight.state.skipped_count += 1
|
36
|
+
end
|
37
|
+
raise ::TestBelt::TestSkipped if halt_test
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module TestBelt
|
2
|
+
module Subject
|
3
|
+
|
4
|
+
# This provides a class method 'subject' on a TestCase. Use this method
|
5
|
+
# to initialize the subject of the TestCase. This may be a model, class,
|
6
|
+
# or whatever. The subject method takes a block and the result of the
|
7
|
+
# block is used as the subject value. Note, the block is for every test
|
8
|
+
# withing the scope of the TestCase instance, and after any setup/before
|
9
|
+
# callbacks. The subject value is available using the 'subject instance
|
10
|
+
# method on the TestCase. Any sub-classed TestCase that does not define
|
11
|
+
# it's own subject will inherite the subject of it's super class.
|
12
|
+
|
13
|
+
# Usage:
|
14
|
+
# class SomeTest < Test::Unit::TestCase
|
15
|
+
# include TestBelt::Subject
|
16
|
+
# end
|
17
|
+
|
18
|
+
def self.included(receiver)
|
19
|
+
receiver.send(:extend, ClassMethods)
|
20
|
+
receiver.send(:include, InstanceMethods)
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def subject(&block)
|
25
|
+
raise ArgumentError, "please provide a subject block" unless block_given?
|
26
|
+
@_testbelt_subject = block
|
27
|
+
end
|
28
|
+
|
29
|
+
def _testbelt_subject
|
30
|
+
@_testbelt_subject || begin
|
31
|
+
superclass._testbelt_subject
|
32
|
+
rescue NoMethodError
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module InstanceMethods
|
39
|
+
def subject
|
40
|
+
@_testbelt_subject ||= if (sb = self.class._testbelt_subject)
|
41
|
+
instance_eval(&sb)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/test_belt/version.rb
CHANGED
@@ -0,0 +1,172 @@
|
|
1
|
+
require "test/helper"
|
2
|
+
|
3
|
+
module TestBelt
|
4
|
+
|
5
|
+
class TestCallbacksTest < Test::Unit::TestCase
|
6
|
+
include TestBelt
|
7
|
+
|
8
|
+
context "A test with callbacks"
|
9
|
+
subject { 'subject' }
|
10
|
+
before {
|
11
|
+
@before = 'before'
|
12
|
+
@before_subject = subject
|
13
|
+
@expected_after = 'after'
|
14
|
+
}
|
15
|
+
setup {
|
16
|
+
@setup = 'setup'
|
17
|
+
@setup_subject = subject
|
18
|
+
@expected_teardown = 'teardown'
|
19
|
+
}
|
20
|
+
|
21
|
+
should "run the it's callbacks appropriately" do
|
22
|
+
assert_equal 'before', @before
|
23
|
+
assert_equal 'setup', @setup
|
24
|
+
assert_equal 'subject', @before_subject
|
25
|
+
assert_equal 'subject', @setup_subject
|
26
|
+
end
|
27
|
+
|
28
|
+
after {
|
29
|
+
assert_equal 'after', @expected_after
|
30
|
+
}
|
31
|
+
teardown {
|
32
|
+
assert_equal 'teardown', @expected_teardown
|
33
|
+
}
|
34
|
+
end
|
35
|
+
class RootCallbacksTest < Test::Unit::TestCase
|
36
|
+
include TestBelt
|
37
|
+
|
38
|
+
before {
|
39
|
+
@before = ['root']
|
40
|
+
}
|
41
|
+
after {
|
42
|
+
assert_equal ['nested nested', 'nested'], @after
|
43
|
+
}
|
44
|
+
end
|
45
|
+
class NestedCallbacksTest < RootCallbacksTest
|
46
|
+
before {
|
47
|
+
@before << 'nested'
|
48
|
+
}
|
49
|
+
after {
|
50
|
+
@after << 'nested'
|
51
|
+
}
|
52
|
+
end
|
53
|
+
class NestedNestedCallbacksTest < NestedCallbacksTest
|
54
|
+
should "call it's nested callbacks in order" do
|
55
|
+
assert_equal ['root', 'nested'], @before
|
56
|
+
end
|
57
|
+
|
58
|
+
after {
|
59
|
+
@after = ['nested nested']
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
class CaseCallbacksTest < Test::Unit::TestCase
|
67
|
+
include TestBelt
|
68
|
+
|
69
|
+
context "A test with case callbacks"
|
70
|
+
|
71
|
+
before_once {
|
72
|
+
@expected_after = 'after'
|
73
|
+
if (@@before_once rescue nil)
|
74
|
+
@before_once_fail = true
|
75
|
+
end
|
76
|
+
@@before_once = true
|
77
|
+
}
|
78
|
+
setup_once {
|
79
|
+
@expected_teardown = 'teardown'
|
80
|
+
if (@@setup_once rescue nil)
|
81
|
+
@setup_once_fail = true
|
82
|
+
end
|
83
|
+
@@setup_once = true
|
84
|
+
}
|
85
|
+
|
86
|
+
should "run the case callbacks just once" do
|
87
|
+
assert true
|
88
|
+
end
|
89
|
+
should "not run the case callbacks more than once" do
|
90
|
+
assert true
|
91
|
+
end
|
92
|
+
|
93
|
+
after_once {
|
94
|
+
if (@@after_once rescue nil)
|
95
|
+
@after_once_fail = true
|
96
|
+
end
|
97
|
+
@@after_once = true
|
98
|
+
raise 'looks like before_once did not run' unless @expected_after == 'after'
|
99
|
+
raise "before_once did not run once" unless (@@before_once rescue nil)
|
100
|
+
raise "before_once ran more than once" unless @before_once_fail.nil?
|
101
|
+
raise "after_once did not run once" unless (@@after_once rescue nil)
|
102
|
+
raise "after_once ran more than once" unless @after_once_fail.nil?
|
103
|
+
}
|
104
|
+
teardown_once {
|
105
|
+
if (@@teardown_once rescue nil)
|
106
|
+
@teardown_once_fail = true
|
107
|
+
end
|
108
|
+
@@teardown_once = true
|
109
|
+
raise 'looks like setup_once did not run' unless @expected_teardown == 'teardown'
|
110
|
+
raise "setup_once did not run once" unless (@@setup_once rescue nil)
|
111
|
+
raise "setup_once ran more than once" unless @setup_once_fail.nil?
|
112
|
+
raise "teardown_once did not run once" unless (@@teardown_once rescue nil)
|
113
|
+
raise "teardown_once ran more than once" unless @teardown_once_fail.nil?
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
class SuiteCallbacksTest < Test::Unit::TestCase
|
121
|
+
include TestBelt
|
122
|
+
|
123
|
+
context "A test with suite callbacks"
|
124
|
+
|
125
|
+
suite_started {
|
126
|
+
@started_assert = true
|
127
|
+
@expected_finished = 'finished'
|
128
|
+
if (@@started rescue nil)
|
129
|
+
@started_fail = true
|
130
|
+
end
|
131
|
+
@@started = true
|
132
|
+
}
|
133
|
+
on_suite_started {
|
134
|
+
@on_started_assert = true
|
135
|
+
@expected_on_finished = 'on finished'
|
136
|
+
if (@@on_started rescue nil)
|
137
|
+
@on_started_fail = true
|
138
|
+
end
|
139
|
+
@@on_started = true
|
140
|
+
}
|
141
|
+
|
142
|
+
should "run the suite callbacks just once" do
|
143
|
+
assert true
|
144
|
+
end
|
145
|
+
should "not run the suite callbacks more than once" do
|
146
|
+
assert true
|
147
|
+
end
|
148
|
+
|
149
|
+
suite_finished {
|
150
|
+
if (@@finished rescue nil)
|
151
|
+
@finished_fail = true
|
152
|
+
end
|
153
|
+
@@finished = true
|
154
|
+
raise 'looks like suite_started did not run' unless @expected_finished == 'finished'
|
155
|
+
raise "suite_started did not run once" unless (@@started rescue nil)
|
156
|
+
raise "suite_started ran more than once" unless @started_fail.nil?
|
157
|
+
raise "suite_finished did not run once" unless (@@finished rescue nil)
|
158
|
+
raise "suite_finished ran more than once" unless @finished_fail.nil?
|
159
|
+
}
|
160
|
+
on_suite_finished {
|
161
|
+
if (@@on_finished rescue nil)
|
162
|
+
@on_finished_fail = true
|
163
|
+
end
|
164
|
+
@@on_finished = true
|
165
|
+
raise 'looks like on_suite_started did not run' unless @expected_on_finished == 'on finished'
|
166
|
+
raise "on_suite_started did not run once" unless (@@on_started rescue nil)
|
167
|
+
raise "on_suite_started ran more than once" unless @on_started_fail.nil?
|
168
|
+
raise "on_suite_finished did not run once" unless (@@on_finished rescue nil)
|
169
|
+
raise "on_suite_finished ran more than once" unless @on_finished_fail.nil?
|
170
|
+
}
|
171
|
+
end
|
172
|
+
end
|