spinach 0.1.5.4 → 0.2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/README.markdown +18 -12
- data/features/background.feature +13 -0
- data/features/reporting/show_step_source_location.feature +11 -1
- data/features/steps/automatic_feature_generation.rb +7 -7
- data/features/steps/background.rb +30 -0
- data/features/steps/exit_status.rb +12 -13
- data/features/steps/feature_name_guessing.rb +7 -7
- data/features/steps/reporting/display_run_summary.rb +22 -22
- data/features/steps/reporting/error_reporting.rb +7 -7
- data/features/steps/reporting/show_step_source_location.rb +63 -14
- data/features/steps/reporting/undefined_feature_reporting.rb +7 -7
- data/features/steps/rspec_compatibility.rb +14 -14
- data/features/support/error_reporting.rb +5 -5
- data/features/support/filesystem.rb +71 -0
- data/features/support/spinach_runner.rb +5 -6
- data/lib/spinach.rb +11 -6
- data/lib/spinach/background.rb +11 -0
- data/lib/spinach/capybara.rb +7 -1
- data/lib/spinach/cli.rb +36 -13
- data/lib/spinach/config.rb +40 -6
- data/lib/spinach/dsl.rb +14 -11
- data/lib/spinach/exceptions.rb +1 -1
- data/lib/spinach/feature.rb +16 -0
- data/lib/spinach/frameworks.rb +2 -0
- data/lib/spinach/{suites → frameworks}/minitest.rb +0 -0
- data/lib/spinach/{suites → frameworks}/rspec.rb +0 -0
- data/lib/spinach/generators/feature_generator.rb +12 -23
- data/lib/spinach/generators/step_generator.rb +5 -5
- data/lib/spinach/hookable.rb +6 -4
- data/lib/spinach/hooks.rb +12 -4
- data/lib/spinach/parser.rb +6 -8
- data/lib/spinach/parser/visitor.rb +109 -0
- data/lib/spinach/reporter.rb +10 -6
- data/lib/spinach/reporter/stdout.rb +41 -16
- data/lib/spinach/reporter/stdout/error_reporting.rb +2 -2
- data/lib/spinach/runner.rb +9 -6
- data/lib/spinach/runner/feature_runner.rb +40 -34
- data/lib/spinach/runner/scenario_runner.rb +63 -36
- data/lib/spinach/scenario.rb +12 -0
- data/lib/spinach/step.rb +10 -0
- data/lib/spinach/version.rb +1 -1
- data/spinach.gemspec +3 -3
- data/test/spinach/background_test.rb +6 -0
- data/test/spinach/capybara_test.rb +30 -13
- data/test/spinach/cli_test.rb +46 -1
- data/test/spinach/config_test.rb +39 -0
- data/test/spinach/dsl_test.rb +12 -10
- data/test/spinach/feature_steps_test.rb +3 -3
- data/test/spinach/feature_test.rb +6 -0
- data/test/spinach/{suites → frameworks}/minitest_test.rb +2 -2
- data/test/spinach/generators/feature_generator_test.rb +18 -58
- data/test/spinach/generators/step_generator_test.rb +3 -3
- data/test/spinach/generators_test.rb +12 -10
- data/test/spinach/hookable_test.rb +8 -0
- data/test/spinach/hooks_test.rb +6 -7
- data/test/spinach/parser/visitor_test.rb +173 -0
- data/test/spinach/parser_test.rb +14 -27
- data/test/spinach/reporter/stdout/error_reporting_test.rb +9 -9
- data/test/spinach/reporter/stdout_test.rb +15 -19
- data/test/spinach/reporter_test.rb +15 -0
- data/test/spinach/runner/feature_runner_test.rb +79 -69
- data/test/spinach/runner/scenario_runner_test.rb +118 -92
- data/test/spinach/runner_test.rb +10 -6
- data/test/spinach/scenario_test.rb +6 -0
- data/test/spinach/step_test.rb +6 -0
- data/test/spinach_test.rb +7 -7
- metadata +60 -39
- data/lib/spinach/suites.rb +0 -2
data/test/spinach/config_test.rb
CHANGED
@@ -5,6 +5,17 @@ describe Spinach::Config do
|
|
5
5
|
Spinach::Config.new
|
6
6
|
end
|
7
7
|
|
8
|
+
describe '#features_path' do
|
9
|
+
it 'returns a default' do
|
10
|
+
subject[:features_path].must_be_kind_of String
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'can be overwritten' do
|
14
|
+
subject[:features_path] = 'test'
|
15
|
+
subject[:features_path].must_equal 'test'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
8
19
|
describe '#step_definitions_path' do
|
9
20
|
it 'returns a default' do
|
10
21
|
subject[:step_definitions_path].must_be_kind_of String
|
@@ -42,4 +53,32 @@ describe Spinach::Config do
|
|
42
53
|
subject[:failure_exceptions].must_include RuntimeError
|
43
54
|
end
|
44
55
|
end
|
56
|
+
|
57
|
+
describe '#config_path' do
|
58
|
+
it 'returns a default' do
|
59
|
+
subject[:config_path].must_equal 'config/spinach.yml'
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'can be overwritten' do
|
63
|
+
subject[:config_path] = 'my_config_file.yml'
|
64
|
+
subject[:config_path].must_equal 'my_config_file.yml'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#parse_from_file' do
|
69
|
+
before do
|
70
|
+
subject[:config_path] = 'a_path'
|
71
|
+
YAML.stubs(:load_file).returns({support_path: 'my_path', config_path: 'another_path'})
|
72
|
+
subject.parse_from_file
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'sets the options' do
|
76
|
+
subject[:support_path].must_equal 'my_path'
|
77
|
+
end
|
78
|
+
|
79
|
+
it "doesn't set the config_path option" do
|
80
|
+
subject[:config_path].must_equal 'a_path'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
45
84
|
end
|
data/test/spinach/dsl_test.rb
CHANGED
@@ -15,18 +15,9 @@ describe Spinach::DSL do
|
|
15
15
|
step_executed = true
|
16
16
|
end
|
17
17
|
|
18
|
-
@feature.new.
|
18
|
+
@feature.new.execute(stub(name: 'I say goodbye'))
|
19
19
|
step_executed.must_equal true
|
20
20
|
end
|
21
|
-
|
22
|
-
it 'returns step source location' do
|
23
|
-
@feature.When('I say goodbye') do
|
24
|
-
'You say hello'
|
25
|
-
end
|
26
|
-
|
27
|
-
@feature.new.execute_step('I say goodbye').first.must_include '/dsl_test.rb'
|
28
|
-
@feature.new.execute_step('I say goodbye').last.must_be_kind_of Fixnum
|
29
|
-
end
|
30
21
|
end
|
31
22
|
|
32
23
|
describe '#When, #Then, #And, #But' do
|
@@ -50,5 +41,16 @@ describe Spinach::DSL do
|
|
50
41
|
@feature.new.name.must_equal "A cool feature"
|
51
42
|
end
|
52
43
|
end
|
44
|
+
|
45
|
+
describe '#step_location_for' do
|
46
|
+
it 'returns step source location' do
|
47
|
+
@feature.When('I say goodbye') do
|
48
|
+
'You say hello'
|
49
|
+
end
|
50
|
+
|
51
|
+
@feature.new.step_location_for('I say goodbye').first.must_include '/dsl_test.rb'
|
52
|
+
@feature.new.step_location_for('I say goodbye').last.must_be_kind_of Fixnum
|
53
|
+
end
|
54
|
+
end
|
53
55
|
end
|
54
56
|
end
|
@@ -31,16 +31,16 @@ describe Spinach::FeatureSteps do
|
|
31
31
|
end.new
|
32
32
|
end
|
33
33
|
|
34
|
-
describe '
|
34
|
+
describe 'execute' do
|
35
35
|
it 'runs defined step correctly' do
|
36
|
-
feature.
|
36
|
+
feature.execute(stub(name: 'I go to the toilet'))
|
37
37
|
|
38
38
|
feature.pee.must_equal true
|
39
39
|
end
|
40
40
|
|
41
41
|
it 'raises an exception if step is not defined' do
|
42
42
|
proc {
|
43
|
-
feature.
|
43
|
+
feature.execute(stub(name: 'I am lost'))
|
44
44
|
}.must_raise Spinach::StepNotDefinedException
|
45
45
|
end
|
46
46
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require_relative '../../test_helper'
|
2
2
|
|
3
|
-
describe "minitest
|
3
|
+
describe "minitest framework" do
|
4
4
|
before do
|
5
|
-
require_relative '../../../lib/spinach/
|
5
|
+
require_relative '../../../lib/spinach/frameworks/minitest'
|
6
6
|
end
|
7
7
|
|
8
8
|
it "adds MiniTest::Assertion into the failure exceptions" do
|
@@ -4,81 +4,41 @@ require_relative '../../../lib/spinach/generators'
|
|
4
4
|
module Spinach::Generators
|
5
5
|
describe FeatureGenerator do
|
6
6
|
subject do
|
7
|
-
FeatureGenerator.new(
|
7
|
+
FeatureGenerator.new(feature)
|
8
8
|
end
|
9
9
|
|
10
|
-
let(:
|
11
|
-
Spinach::Parser.new("
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
10
|
+
let(:feature) do
|
11
|
+
Spinach::Parser.new("""
|
12
|
+
Feature: Cheezburger can I has
|
13
|
+
Background:
|
14
|
+
Given I liek cheezburger
|
15
|
+
Scenario: Some Lulz
|
16
|
+
Given I haz a sad
|
17
|
+
When I get some lulz
|
18
|
+
Then I haz a happy
|
19
|
+
Scenario: Some sad
|
20
|
+
Given I haz a happy
|
21
|
+
When I get some lulz
|
22
|
+
Then I am OMG ROFLMAO""").parse
|
21
23
|
end
|
22
24
|
|
23
25
|
describe "#name" do
|
24
|
-
it "returns the
|
26
|
+
it "returns the feature name" do
|
25
27
|
subject.name.must_equal 'Cheezburger can I has'
|
26
28
|
end
|
27
|
-
|
28
|
-
it "returns nil if no name present" do
|
29
|
-
data['name'] = nil
|
30
|
-
subject.name.must_equal nil
|
31
|
-
end
|
32
29
|
end
|
33
30
|
|
34
31
|
describe "#steps" do
|
35
32
|
it "returns a correct number different steps for this data" do
|
36
|
-
subject.steps.length.must_equal
|
37
|
-
end
|
38
|
-
|
39
|
-
describe "includes the correct steps" do
|
40
|
-
it "includes 'Given I haz a sad'" do
|
41
|
-
subject.steps.any?{ |step|
|
42
|
-
(step['keyword'] == 'Given') && (step['name'] == 'I haz a sad')
|
43
|
-
}.must_equal true
|
44
|
-
end
|
45
|
-
|
46
|
-
it "includes 'When I get some lulz'" do
|
47
|
-
subject.steps.any?{ |step|
|
48
|
-
(step['keyword'] == 'When') && (step['name'] == 'I get some lulz')
|
49
|
-
}.must_equal true
|
50
|
-
end
|
51
|
-
|
52
|
-
it "includes 'Then I has a happy'" do
|
53
|
-
subject.steps.any?{ |step|
|
54
|
-
(step['keyword'] == 'Then') && (step['name'] == 'I haz a happy')
|
55
|
-
}.must_equal true
|
56
|
-
end
|
57
|
-
|
58
|
-
it "does not include 'Given I haz a happy'" do
|
59
|
-
subject.steps.any?{ |step|
|
60
|
-
(step['keyword'] == 'Given') && (step['name'] == 'I haz a happy')
|
61
|
-
}.must_equal false
|
62
|
-
end
|
63
|
-
|
64
|
-
it "includes 'Then I am OMG ROFLMAO'" do
|
65
|
-
subject.steps.any?{ |step|
|
66
|
-
(step['keyword'] == 'Then') && (step['name'] == 'I am OMG ROFLMAO')
|
67
|
-
}.must_equal true
|
68
|
-
end
|
33
|
+
subject.steps.length.must_equal 5
|
69
34
|
end
|
70
35
|
end
|
71
36
|
|
72
37
|
describe "#generate" do
|
73
38
|
it "generates an entire feature_steps class definition" do
|
74
39
|
result = subject.generate
|
75
|
-
|
76
|
-
|
77
|
-
feature_runner.stubs(data: data)
|
78
|
-
error_count = 0
|
79
|
-
CheezburgerCanIHas.any_instance.
|
80
|
-
expects(:raise).with("step not implemented").times(6).returns(nil)
|
81
|
-
feature_runner.run.must_equal true
|
40
|
+
result.must_match /Given 'I haz a sad' do/
|
41
|
+
result.must_match /raise 'step not implemented'/
|
82
42
|
end
|
83
43
|
end
|
84
44
|
|
@@ -4,11 +4,11 @@ require_relative '../../../lib/spinach/generators'
|
|
4
4
|
module Spinach::Generators
|
5
5
|
describe StepGenerator do
|
6
6
|
subject do
|
7
|
-
StepGenerator.new(
|
7
|
+
StepGenerator.new(step)
|
8
8
|
end
|
9
9
|
|
10
|
-
let(:
|
11
|
-
|
10
|
+
let(:step) do
|
11
|
+
stub(keyword: 'Given', name: 'I has a sad')
|
12
12
|
end
|
13
13
|
|
14
14
|
describe "#generate" do
|
@@ -6,16 +6,18 @@ describe Spinach::Generators do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
let(:data) do
|
9
|
-
Spinach::Parser.new("
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
9
|
+
Spinach::Parser.new("""
|
10
|
+
Feature: Cheezburger can I has
|
11
|
+
Background:
|
12
|
+
Given I liek cheezburger
|
13
|
+
Scenario: Some Lulz
|
14
|
+
Given I haz a sad
|
15
|
+
When I get some lulz
|
16
|
+
Then I haz a happy
|
17
|
+
Scenario: Some sad
|
18
|
+
Given I haz a happy
|
19
|
+
When I get some lulz
|
20
|
+
Then I am OMG ROFLMAO""").parse
|
19
21
|
end
|
20
22
|
|
21
23
|
describe "#bind" do
|
@@ -48,6 +48,14 @@ describe Spinach::Hookable do
|
|
48
48
|
subject.run_hook(:before_save, 1, 2)
|
49
49
|
array.must_equal [1, 2]
|
50
50
|
end
|
51
|
+
|
52
|
+
it "yields to hook block even if nothing is hooked" do
|
53
|
+
called = false
|
54
|
+
subject.run_hook(:before_save) do
|
55
|
+
called = true
|
56
|
+
end
|
57
|
+
called.must_equal true
|
58
|
+
end
|
51
59
|
end
|
52
60
|
|
53
61
|
describe "#reset_hooks" do
|
data/test/spinach/hooks_test.rb
CHANGED
@@ -7,7 +7,7 @@ describe Spinach::Hooks do
|
|
7
7
|
|
8
8
|
describe "hooks" do
|
9
9
|
%w{before_run after_run before_feature after_feature on_undefined_feature
|
10
|
-
before_scenario after_scenario before_step after_step on_successful_step
|
10
|
+
before_scenario around_scenario after_scenario before_step after_step on_successful_step
|
11
11
|
on_failed_step on_error_step on_undefined_step on_skipped_step}.each do |callback|
|
12
12
|
describe "#{callback}" do
|
13
13
|
it "responds to #{callback}" do
|
@@ -26,11 +26,10 @@ describe Spinach::Hooks do
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
29
|
+
|
29
30
|
describe "#on_tag" do
|
30
|
-
let(:
|
31
|
-
|
32
|
-
'tags' => [{'name' => '@javascript'}, {'name' => '@capture'}]
|
33
|
-
}
|
31
|
+
let(:scenario) do
|
32
|
+
stub(tags: ['javascript', 'capture'])
|
34
33
|
end
|
35
34
|
|
36
35
|
it "calls the block if the scenario includes the tag" do
|
@@ -38,7 +37,7 @@ describe Spinach::Hooks do
|
|
38
37
|
subject.on_tag('javascript') do
|
39
38
|
assertion = true
|
40
39
|
end
|
41
|
-
subject.run_before_scenario(
|
40
|
+
subject.run_before_scenario(scenario)
|
42
41
|
assertion.must_equal true
|
43
42
|
end
|
44
43
|
|
@@ -47,7 +46,7 @@ describe Spinach::Hooks do
|
|
47
46
|
subject.on_tag('screenshot') do
|
48
47
|
assertion = true
|
49
48
|
end
|
50
|
-
subject.run_before_scenario(
|
49
|
+
subject.run_before_scenario(scenario)
|
51
50
|
assertion.wont_equal true
|
52
51
|
end
|
53
52
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Spinach
|
4
|
+
class Parser
|
5
|
+
describe Visitor do
|
6
|
+
let(:feature) { Feature.new }
|
7
|
+
let(:visitor) { Visitor.new }
|
8
|
+
|
9
|
+
describe '#visit' do
|
10
|
+
it 'makes ast accept self' do
|
11
|
+
ast = stub('AST')
|
12
|
+
ast.expects(:accept).with(visitor)
|
13
|
+
|
14
|
+
visitor.visit(ast)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns the feature' do
|
18
|
+
ast = stub_everything
|
19
|
+
visitor.instance_variable_set(:@feature, feature)
|
20
|
+
visitor.visit(ast).must_equal feature
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#visit_Feature' do
|
25
|
+
before do
|
26
|
+
@background = stub_everything
|
27
|
+
@scenarios = [stub_everything, stub_everything, stub_everything]
|
28
|
+
@node = stub(scenarios: @scenarios, name: 'Go shopping', background: @background)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'sets the name' do
|
32
|
+
visitor.visit_Feature(@node)
|
33
|
+
visitor.feature.name.must_equal 'Go shopping'
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'iterates over its children' do
|
37
|
+
@scenarios.each do |scenario|
|
38
|
+
scenario.expects(:accept).with visitor
|
39
|
+
end
|
40
|
+
|
41
|
+
visitor.visit_Feature(@node)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#visit_Scenario' do
|
46
|
+
before do
|
47
|
+
@steps = [stub_everything, stub_everything, stub_everything]
|
48
|
+
@tags = [stub_everything, stub_everything, stub_everything]
|
49
|
+
@node = stub(
|
50
|
+
tags: @tags,
|
51
|
+
steps: @steps,
|
52
|
+
name: 'Go shopping on Saturday morning',
|
53
|
+
line: 3
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'adds the scenario to the feature' do
|
58
|
+
visitor.visit_Scenario(@node)
|
59
|
+
visitor.feature.scenarios.length.must_equal 1
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'sets the name' do
|
63
|
+
visitor.visit_Scenario(@node)
|
64
|
+
visitor.feature.scenarios.first.name.must_equal 'Go shopping on Saturday morning'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'sets the line' do
|
68
|
+
visitor.visit_Scenario(@node)
|
69
|
+
visitor.feature.scenarios.first.line.must_equal 3
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'sets the tags' do
|
73
|
+
@tags.each do |step|
|
74
|
+
step.expects(:accept).with visitor
|
75
|
+
end
|
76
|
+
visitor.visit_Scenario(@node)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'iterates over its children' do
|
80
|
+
@steps.each do |step|
|
81
|
+
step.expects(:accept).with visitor
|
82
|
+
end
|
83
|
+
visitor.visit_Scenario(@node)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#visit_Background' do
|
89
|
+
before do
|
90
|
+
@steps = [stub_everything, stub_everything, stub_everything]
|
91
|
+
@node = stub(
|
92
|
+
steps: @steps,
|
93
|
+
line: 3
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'adds the background to the feature' do
|
98
|
+
visitor.visit_Background(@node)
|
99
|
+
visitor.feature.background.must_be_kind_of Background
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'sets the line' do
|
103
|
+
visitor.visit_Background(@node)
|
104
|
+
visitor.feature.background.line.must_equal 3
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'iterates over its children' do
|
108
|
+
@steps.each do |step|
|
109
|
+
step.expects(:accept).with visitor
|
110
|
+
end
|
111
|
+
visitor.visit_Background(@node)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'visits the background' do
|
115
|
+
visitor.visit_Background(@node)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe '#visit_Tag' do
|
120
|
+
it 'adds the tag to the current scenario' do
|
121
|
+
tags = ['tag1', 'tag2', 'tag3']
|
122
|
+
scenario = stub(tags: tags)
|
123
|
+
visitor.instance_variable_set(:@current_scenario, scenario)
|
124
|
+
|
125
|
+
visitor.visit_Tag(stub(name: 'tag4'))
|
126
|
+
scenario.tags.must_equal ['tag1', 'tag2', 'tag3', 'tag4']
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#visit_Step' do
|
131
|
+
before do
|
132
|
+
@node = stub(name: 'Baz', line: 3, keyword: 'Given')
|
133
|
+
@steps = [stub(name: 'Foo'), stub(name: 'Bar')]
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'adds the step to the step set' do
|
137
|
+
step_set = stub(steps: @steps)
|
138
|
+
visitor.instance_variable_set(:@current_step_set, step_set)
|
139
|
+
|
140
|
+
visitor.visit_Step(@node)
|
141
|
+
step_set.steps.length.must_equal 3
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'sets the name' do
|
145
|
+
step_set = stub(steps: [])
|
146
|
+
visitor.instance_variable_set(:@current_step_set, step_set)
|
147
|
+
|
148
|
+
visitor.visit_Step(@node)
|
149
|
+
|
150
|
+
step_set.steps.first.name.must_equal 'Baz'
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'sets the keyword' do
|
154
|
+
step_set = stub(steps: [])
|
155
|
+
visitor.instance_variable_set(:@current_step_set, step_set)
|
156
|
+
|
157
|
+
visitor.visit_Step(@node)
|
158
|
+
|
159
|
+
step_set.steps.first.keyword.must_equal 'Given'
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'sets the line' do
|
163
|
+
step_set = stub(steps: [])
|
164
|
+
visitor.instance_variable_set(:@current_step_set, step_set)
|
165
|
+
|
166
|
+
visitor.visit_Step(@node)
|
167
|
+
|
168
|
+
step_set.steps.first.line.must_equal 3
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|