capistrano 3.2.1 → 3.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -3
  3. data/CHANGELOG.md +92 -2
  4. data/Gemfile +1 -5
  5. data/README.md +84 -3
  6. data/Rakefile +5 -1
  7. data/capistrano.gemspec +5 -1
  8. data/features/configuration.feature +9 -7
  9. data/features/deploy.feature +9 -8
  10. data/features/step_definitions/assertions.rb +15 -17
  11. data/features/step_definitions/cap_commands.rb +1 -1
  12. data/features/step_definitions/setup.rb +11 -7
  13. data/features/support/env.rb +8 -9
  14. data/features/support/remote_command_helpers.rb +2 -3
  15. data/features/support/vagrant_helpers.rb +35 -0
  16. data/lib/capistrano/all.rb +2 -1
  17. data/lib/capistrano/application.rb +52 -7
  18. data/lib/capistrano/configuration.rb +39 -10
  19. data/lib/capistrano/configuration/filter.rb +56 -0
  20. data/lib/capistrano/configuration/question.rb +23 -11
  21. data/lib/capistrano/configuration/server.rb +14 -5
  22. data/lib/capistrano/configuration/servers.rb +12 -29
  23. data/lib/capistrano/defaults.rb +11 -9
  24. data/lib/capistrano/deploy.rb +1 -0
  25. data/lib/capistrano/dsl.rb +13 -2
  26. data/lib/capistrano/dsl/env.rb +6 -2
  27. data/lib/capistrano/dsl/task_enhancements.rb +5 -3
  28. data/lib/capistrano/git.rb +8 -2
  29. data/lib/capistrano/hg.rb +7 -1
  30. data/lib/capistrano/svn.rb +2 -2
  31. data/lib/capistrano/tasks/deploy.rake +12 -10
  32. data/lib/capistrano/tasks/git.rake +1 -1
  33. data/lib/capistrano/tasks/install.rake +17 -14
  34. data/lib/capistrano/templates/Capfile +6 -4
  35. data/lib/capistrano/templates/deploy.rb.erb +5 -15
  36. data/lib/capistrano/upload_task.rb +9 -0
  37. data/lib/capistrano/version.rb +1 -1
  38. data/spec/integration/dsl_spec.rb +129 -10
  39. data/spec/lib/capistrano/application_spec.rb +24 -6
  40. data/spec/lib/capistrano/configuration/filter_spec.rb +105 -0
  41. data/spec/lib/capistrano/configuration/question_spec.rb +18 -12
  42. data/spec/lib/capistrano/configuration/server_spec.rb +19 -19
  43. data/spec/lib/capistrano/configuration/servers_spec.rb +101 -20
  44. data/spec/lib/capistrano/configuration_spec.rb +24 -3
  45. data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +88 -0
  46. data/spec/lib/capistrano/dsl_spec.rb +2 -13
  47. data/spec/lib/capistrano/git_spec.rb +15 -4
  48. data/spec/lib/capistrano/hg_spec.rb +13 -2
  49. data/spec/lib/capistrano/scm_spec.rb +3 -3
  50. data/spec/lib/capistrano/svn_spec.rb +11 -1
  51. data/spec/lib/capistrano/upload_task_spec.rb +19 -0
  52. data/spec/lib/capistrano/version_validator_spec.rb +4 -4
  53. data/spec/spec_helper.rb +2 -1
  54. data/spec/support/Vagrantfile +1 -1
  55. data/spec/support/test_app.rb +2 -0
  56. metadata +45 -26
  57. data/lib/capistrano/configuration/servers/host_filter.rb +0 -82
  58. data/lib/capistrano/configuration/servers/role_filter.rb +0 -86
  59. data/spec/lib/capistrano/configuration/servers/host_filter_spec.rb +0 -84
  60. data/spec/lib/capistrano/configuration/servers/role_filter_spec.rb +0 -140
@@ -0,0 +1,9 @@
1
+ require 'rake/file_creation_task'
2
+
3
+ module Capistrano
4
+ class UploadTask < Rake::FileCreationTask
5
+ def needed?
6
+ true # always needed because we can't check remote hosts
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Capistrano
2
- VERSION = "3.2.1"
2
+ VERSION = "3.3.3"
3
3
  end
@@ -36,10 +36,10 @@ describe Capistrano::DSL do
36
36
  end
37
37
  end
38
38
 
39
- context 'with filter options' do
39
+ context 'with property filter options' do
40
40
  subject { dsl.release_roles(:all, filter: :active) }
41
41
 
42
- it 'returns all release servers that match the filter' do
42
+ it 'returns all release servers that match the property filter' do
43
43
  expect(subject.map(&:hostname)).to eq %w{example1.com example3.com}
44
44
  end
45
45
  end
@@ -92,7 +92,7 @@ describe Capistrano::DSL do
92
92
  end
93
93
  end
94
94
 
95
- context 'when the attribute `primary` is explicity set' do
95
+ context 'when the attribute `primary` is explicitly set' do
96
96
  subject { dsl.primary(:app) }
97
97
  it 'returns the servers' do
98
98
  expect(subject.hostname).to eq 'example4.com'
@@ -100,6 +100,38 @@ describe Capistrano::DSL do
100
100
  end
101
101
  end
102
102
 
103
+ describe 'setting an internal host filter' do
104
+ subject { dsl.roles(:app) }
105
+ it 'is ignored' do
106
+ dsl.set :filter, { host: 'example3.com' }
107
+ expect(subject.map(&:hostname)).to eq(['example3.com', 'example4.com'])
108
+ end
109
+ end
110
+
111
+ describe 'setting an internal role filter' do
112
+ subject { dsl.roles(:app) }
113
+ it 'ignores it' do
114
+ dsl.set :filter, { role: :web }
115
+ expect(subject.map(&:hostname)).to eq(['example3.com','example4.com'])
116
+ end
117
+ end
118
+
119
+ describe 'setting an internal host and role filter' do
120
+ subject { dsl.roles(:app) }
121
+ it 'ignores it' do
122
+ dsl.set :filter, { role: :web, host: 'example1.com' }
123
+ expect(subject.map(&:hostname)).to eq(['example3.com','example4.com'])
124
+ end
125
+ end
126
+
127
+ describe 'setting an internal regexp host filter' do
128
+ subject { dsl.roles(:all) }
129
+ it 'is ignored' do
130
+ dsl.set :filter, { host: /1/ }
131
+ expect(subject.map(&:hostname)).to eq(%w{example1.com example2.com example3.com example4.com example5.com})
132
+ end
133
+ end
134
+
103
135
  end
104
136
 
105
137
  describe 'when defining role with reserved name' do
@@ -218,11 +250,11 @@ describe Capistrano::DSL do
218
250
 
219
251
  describe 'fetching servers for a role' do
220
252
  it 'roles defined using the `server` syntax are included' do
221
- expect(dsl.roles(:web)).to have(2).items
253
+ expect(dsl.roles(:web).size).to eq(2)
222
254
  end
223
255
 
224
256
  it 'roles defined using the `role` syntax are included' do
225
- expect(dsl.roles(:app)).to have(2).items
257
+ expect(dsl.roles(:app).size).to eq(2)
226
258
  end
227
259
  end
228
260
 
@@ -317,22 +349,22 @@ describe Capistrano::DSL do
317
349
  context 'variable is an non-empty array' do
318
350
  let(:linked_files) { %w{1} }
319
351
 
320
- it { should be_true }
352
+ it { expect(subject).to be_truthy }
321
353
  end
322
354
 
323
355
  context 'variable is an empty array' do
324
356
  let(:linked_files) { [] }
325
- it { should be_false }
357
+ it { expect(subject).to be_falsey }
326
358
  end
327
359
 
328
360
  context 'variable exists, is not an array' do
329
361
  let(:linked_files) { stub }
330
- it { should be_true }
362
+ it { expect(subject).to be_truthy }
331
363
  end
332
364
 
333
365
  context 'variable is nil' do
334
366
  let(:linked_files) { nil }
335
- it { should be_false }
367
+ it { expect(subject).to be_falsey }
336
368
  end
337
369
  end
338
370
 
@@ -368,7 +400,7 @@ describe Capistrano::DSL do
368
400
  end
369
401
 
370
402
  it 'sets the backend pty' do
371
- expect(backend.pty).to be_true
403
+ expect(backend.pty).to be_truthy
372
404
  end
373
405
 
374
406
  it 'sets the backend connection timeout' do
@@ -488,4 +520,91 @@ describe Capistrano::DSL do
488
520
  end
489
521
  end
490
522
  end
523
+
524
+ describe 'local_user' do
525
+ before do
526
+ dsl.set :local_user, -> { Etc.getlogin }
527
+ end
528
+
529
+ describe 'fetching local_user' do
530
+ subject { dsl.local_user }
531
+
532
+ context 'where a local_user is not set' do
533
+ before do
534
+ Etc.expects(:getlogin).returns('login')
535
+ end
536
+
537
+ it 'returns the login name' do
538
+ expect(subject.to_s).to eq 'login'
539
+ end
540
+ end
541
+
542
+ context 'where a local_user is set' do
543
+ before do
544
+ dsl.set(:local_user, -> { 'custom login' })
545
+ end
546
+
547
+ it 'returns the custom name' do
548
+ expect(subject.to_s).to eq 'custom login'
549
+ end
550
+ end
551
+ end
552
+ end
553
+
554
+ describe 'on()' do
555
+
556
+ before do
557
+ dsl.server 'example1.com', roles: %w{web}, active: true
558
+ dsl.server 'example2.com', roles: %w{web}
559
+ dsl.server 'example3.com', roles: %w{app web}, active: true
560
+ dsl.server 'example4.com', roles: %w{app}, primary: true
561
+ dsl.server 'example5.com', roles: %w{db}, no_release: true
562
+ @coordinator = mock('coordinator')
563
+ @coordinator.expects(:each).returns(nil)
564
+ ENV.delete 'ROLES'
565
+ ENV.delete 'HOSTS'
566
+
567
+ end
568
+
569
+ it 'filters by role from the :filter variable' do
570
+ hosts = dsl.roles(:web)
571
+ all = dsl.roles(:all)
572
+ SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
573
+ dsl.set :filter, { role: 'web' }
574
+ dsl.on(all)
575
+ end
576
+
577
+ it 'filters by host and role from the :filter variable' do
578
+ all = dsl.roles(:all)
579
+ SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
580
+ dsl.set :filter, { role: 'db', host: 'example3.com' }
581
+ dsl.on(all)
582
+ end
583
+
584
+ it 'filters from ENV[ROLES]' do
585
+ hosts = dsl.roles(:db)
586
+ all = dsl.roles(:all)
587
+ SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
588
+ ENV['ROLES'] = 'db'
589
+ dsl.on(all)
590
+ end
591
+
592
+ it 'filters from ENV[HOSTS]' do
593
+ hosts = dsl.roles(:db)
594
+ all = dsl.roles(:all)
595
+ SSHKit::Coordinator.expects(:new).with(hosts).returns(@coordinator)
596
+ ENV['HOSTS'] = 'example5.com'
597
+ dsl.on(all)
598
+ end
599
+
600
+ it 'filters by ENV[HOSTS] && ENV[ROLES]' do
601
+ all = dsl.roles(:all)
602
+ SSHKit::Coordinator.expects(:new).with([]).returns(@coordinator)
603
+ ENV['HOSTS'] = 'example5.com'
604
+ ENV['ROLES'] = 'web'
605
+ dsl.on(all)
606
+ end
607
+
608
+ end
609
+
491
610
  end
@@ -6,21 +6,39 @@ describe Capistrano::Application do
6
6
 
7
7
  it "provides a --format option which enables the choice of output formatting"
8
8
 
9
- it "identifies itself as cap and not rake" do
9
+ let(:help_output) do
10
10
  out, _ = capture_io do
11
11
  flags '--help', '-h'
12
12
  end
13
- out.lines.first.should match(/cap \[-f rakefile\]/)
13
+ out
14
+ end
15
+
16
+ it "displays documentation URL as help banner" do
17
+ expect(help_output.lines.first).to match(/capistranorb.com/)
18
+ end
19
+
20
+ %w(quiet silent verbose).each do |switch|
21
+ it "doesn't include --#{switch} in help" do
22
+ expect(help_output).not_to match(/--#{switch}/)
23
+ end
14
24
  end
15
25
 
16
26
  it "overrides the rake method, but still prints the rake version" do
17
27
  out, _ = capture_io do
18
28
  flags '--version', '-V'
19
29
  end
20
- out.should match(/\bCapistrano Version\b/)
21
- out.should match(/\b#{Capistrano::VERSION}\b/)
22
- out.should match(/\bRake Version\b/)
23
- out.should match(/\b#{RAKEVERSION}\b/)
30
+ expect(out).to match(/\bCapistrano Version\b/)
31
+ expect(out).to match(/\b#{Capistrano::VERSION}\b/)
32
+ expect(out).to match(/\bRake Version\b/)
33
+ expect(out).to match(/\b#{RAKEVERSION}\b/)
34
+ end
35
+
36
+ it "overrides the rake method, and sets the sshkit_backend to SSHKit::Backend::Printer" do
37
+ out, _ = capture_io do
38
+ flags '--dry-run', '-n'
39
+ end
40
+ sshkit_backend = Capistrano::Configuration.fetch(:sshkit_backend)
41
+ expect(sshkit_backend).to eq(SSHKit::Backend::Printer)
24
42
  end
25
43
 
26
44
  def flags(*sets)
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ module Capistrano
4
+ class Configuration
5
+
6
+ describe Filter do
7
+ let(:available) { [ Server.new('server1').add_roles([:web,:db]),
8
+ Server.new('server2').add_role(:web),
9
+ Server.new('server3').add_role(:redis),
10
+ Server.new('server4').add_role(:db) ] }
11
+
12
+ describe '#new' do
13
+ it "won't create an invalid type of filter" do
14
+ expect {
15
+ f = Filter.new(:zarg)
16
+ }.to raise_error RuntimeError
17
+ end
18
+
19
+ it 'creates an empty host filter' do
20
+ expect(Filter.new(:host).filter(available)).to be_empty
21
+ end
22
+
23
+ it 'creates a null host filter' do
24
+ expect(Filter.new(:host, :all).filter(available)).to eq(available)
25
+ end
26
+
27
+ it 'creates an empty role filter' do
28
+ expect(Filter.new(:role).filter(available)).to be_empty
29
+ end
30
+
31
+ it 'creates a null role filter' do
32
+ expect(Filter.new(:role, :all).filter(available)).to eq(available)
33
+ end
34
+
35
+ end
36
+
37
+ describe 'host filter' do
38
+ it 'works with a single server' do
39
+ set = Filter.new(:host, 'server1').filter(available.first)
40
+ expect(set.map(&:hostname)).to eq(%w{server1})
41
+ end
42
+ it 'returns all hosts matching a string' do
43
+ set = Filter.new(:host, 'server1').filter(available)
44
+ expect(set.map(&:hostname)).to eq(%w{server1})
45
+ end
46
+ it 'returns all hosts matching a comma-separated string' do
47
+ set = Filter.new(:host, 'server1,server3').filter(available)
48
+ expect(set.map(&:hostname)).to eq(%w{server1 server3})
49
+ end
50
+ it 'returns all hosts matching an array of strings' do
51
+ set = Filter.new(:host, %w{server1 server3}).filter(available)
52
+ expect(set.map(&:hostname)).to eq(%w{server1 server3})
53
+ end
54
+ it 'returns all hosts matching regexp' do
55
+ set = Filter.new(:host, 'server[13]$').filter(available)
56
+ expect(set.map(&:hostname)).to eq(%w{server1 server3})
57
+ end
58
+ it 'correctly identifies a regex with a comma in' do
59
+ set = Filter.new(:host, 'server\d{1,3}$').filter(available)
60
+ expect(set.map(&:hostname)).to eq(%w{server1 server2 server3 server4})
61
+ end
62
+ end
63
+
64
+ describe 'role filter' do
65
+ it 'returns all hosts' do
66
+ set = Filter.new(:role, [:all]).filter(available)
67
+ expect(set.size).to eq(available.size)
68
+ expect(set.first.hostname).to eq('server1')
69
+ end
70
+ it 'returns hosts in a single string role' do
71
+ set = Filter.new(:role, 'web').filter(available)
72
+ expect(set.size).to eq(2)
73
+ expect(set.map(&:hostname)).to eq(%w{server1 server2})
74
+ end
75
+ it 'returns hosts in a single role' do
76
+ set = Filter.new(:role, [:web]).filter(available)
77
+ expect(set.size).to eq(2)
78
+ expect(set.map(&:hostname)).to eq(%w{server1 server2})
79
+ end
80
+ it 'returns hosts in multiple roles specified by a string' do
81
+ set = Filter.new(:role, 'web,db').filter(available)
82
+ expect(set.size).to eq(3)
83
+ expect(set.map(&:hostname)).to eq(%w{server1 server2 server4})
84
+ end
85
+ it 'returns hosts in multiple roles' do
86
+ set = Filter.new(:role, [:web, :db]).filter(available)
87
+ expect(set.size).to eq(3)
88
+ expect(set.map(&:hostname)).to eq(%w{server1 server2 server4})
89
+ end
90
+ it 'returns hosts with regex role selection' do
91
+ set = Filter.new(:role, /red/).filter(available)
92
+ expect(set.map(&:hostname)).to eq(%w{server3})
93
+ end
94
+ it 'returns hosts with regex role selection using a string' do
95
+ set = Filter.new(:role, '/red|web/').filter(available)
96
+ expect(set.map(&:hostname)).to eq(%w{server1 server2 server3})
97
+ end
98
+ it 'returns hosts with combination of string role and regex' do
99
+ set = Filter.new(:role, 'db,/red/').filter(available)
100
+ expect(set.map(&:hostname)).to eq(%w{server1 server3 server4})
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -5,31 +5,38 @@ module Capistrano
5
5
 
6
6
  describe Question do
7
7
 
8
- let(:question) { Question.new(env, key, default) }
8
+ let(:question) { Question.new(key, default, options) }
9
+ let(:question_without_echo) { Question.new(key, default, echo: false) }
9
10
  let(:default) { :default }
10
11
  let(:key) { :branch }
11
- let(:env) { stub }
12
+ let(:options) { nil }
12
13
 
13
14
  describe '.new' do
14
- it 'takes a key, default' do
15
+ it 'takes a key, default, options' do
15
16
  question
16
17
  end
17
18
  end
18
19
 
19
20
  describe '#call' do
20
- subject { question.call }
21
-
22
21
  context 'value is entered' do
23
22
  let(:branch) { 'branch' }
24
23
 
25
24
  before do
26
25
  $stdout.expects(:print).with('Please enter branch (default): ')
26
+ end
27
+
28
+ it 'returns the echoed value' do
27
29
  $stdin.expects(:gets).returns(branch)
30
+ $stdin.expects(:noecho).never
31
+
32
+ expect(question.call).to eq(branch)
28
33
  end
29
34
 
30
- it 'sets the value' do
31
- env.expects(:set).with(key, branch)
32
- question.call
35
+ it 'returns the value but does not echo it' do
36
+ $stdin.expects(:noecho).returns(branch)
37
+ $stdout.expects(:print).with("\n")
38
+
39
+ expect(question_without_echo.call).to eq(branch)
33
40
  end
34
41
  end
35
42
 
@@ -41,11 +48,10 @@ module Capistrano
41
48
  $stdin.expects(:gets).returns('')
42
49
  end
43
50
 
44
- it 'sets the default as the value' do
45
- env.expects(:set).with(key, branch)
46
- question.call
47
- end
48
51
 
52
+ it 'returns the default as the value' do
53
+ expect(question.call).to eq(branch)
54
+ end
49
55
  end
50
56
  end
51
57
  end
@@ -28,7 +28,7 @@ module Capistrano
28
28
  end
29
29
 
30
30
  it 'adds the role' do
31
- expect{subject}.to be_true
31
+ expect(subject).to be_truthy
32
32
  end
33
33
  end
34
34
 
@@ -37,22 +37,22 @@ module Capistrano
37
37
 
38
38
  context 'with the same user, hostname and port' do
39
39
  let(:hostname) { 'root@hostname:1234' }
40
- it { should be_true }
40
+ it { expect(subject).to be_truthy }
41
41
  end
42
42
 
43
43
  context 'with a different user' do
44
44
  let(:hostname) { 'deployer@hostname:1234' }
45
- it { should be_false }
45
+ it { expect(subject).to be_falsey }
46
46
  end
47
47
 
48
48
  context 'with a different port' do
49
49
  let(:hostname) { 'root@hostname:5678' }
50
- it { should be_false }
50
+ it { expect(subject).to be_falsey }
51
51
  end
52
52
 
53
53
  context 'with a different hostname' do
54
54
  let(:hostname) { 'root@otherserver:1234' }
55
- it { should be_false }
55
+ it { expect(subject).to be_falsey }
56
56
  end
57
57
  end
58
58
 
@@ -69,7 +69,7 @@ module Capistrano
69
69
 
70
70
  context 'server is not primary' do
71
71
  it 'is falesy' do
72
- expect(subject).to be_false
72
+ expect(subject).to be_falsey
73
73
  end
74
74
  end
75
75
  end
@@ -149,7 +149,7 @@ module Capistrano
149
149
  end
150
150
 
151
151
  context 'options are empty' do
152
- it { should be_true }
152
+ it { expect(subject).to be_truthy }
153
153
  end
154
154
 
155
155
  context 'value is a symbol' do
@@ -157,34 +157,34 @@ module Capistrano
157
157
 
158
158
  context 'with :filter' do
159
159
  let(:options) { { filter: :active }}
160
- it { should be_true }
160
+ it { expect(subject).to be_truthy }
161
161
  end
162
162
 
163
163
  context 'with :select' do
164
164
  let(:options) { { select: :active }}
165
- it { should be_true }
165
+ it { expect(subject).to be_truthy }
166
166
  end
167
167
 
168
168
  context 'with :exclude' do
169
169
  let(:options) { { exclude: :active }}
170
- it { should be_false }
170
+ it { expect(subject).to be_falsey }
171
171
  end
172
172
  end
173
173
 
174
174
  context 'value does not match server properly' do
175
175
  context 'with :filter' do
176
176
  let(:options) { { filter: :inactive }}
177
- it { should be_false }
177
+ it { expect(subject).to be_falsey }
178
178
  end
179
179
 
180
180
  context 'with :select' do
181
181
  let(:options) { { select: :inactive }}
182
- it { should be_false }
182
+ it { expect(subject).to be_falsey }
183
183
  end
184
184
 
185
185
  context 'with :exclude' do
186
186
  let(:options) { { exclude: :inactive }}
187
- it { should be_true }
187
+ it { expect(subject).to be_truthy }
188
188
  end
189
189
  end
190
190
  end
@@ -194,17 +194,17 @@ module Capistrano
194
194
 
195
195
  context 'with :filter' do
196
196
  let(:options) { { filter: ->(s) { s.properties.active } } }
197
- it { should be_true }
197
+ it { expect(subject).to be_truthy }
198
198
  end
199
199
 
200
200
  context 'with :select' do
201
201
  let(:options) { { select: ->(s) { s.properties.active } } }
202
- it { should be_true }
202
+ it { expect(subject).to be_truthy }
203
203
  end
204
204
 
205
205
  context 'with :exclude' do
206
206
  let(:options) { { exclude: ->(s) { s.properties.active } } }
207
- it { should be_false }
207
+ it { expect(subject).to be_falsey }
208
208
  end
209
209
 
210
210
  end
@@ -212,17 +212,17 @@ module Capistrano
212
212
  context 'value does not match server properly' do
213
213
  context 'with :filter' do
214
214
  let(:options) { { filter: ->(s) { s.properties.inactive } } }
215
- it { should be_false }
215
+ it { expect(subject).to be_falsey }
216
216
  end
217
217
 
218
218
  context 'with :select' do
219
219
  let(:options) { { select: ->(s) { s.properties.inactive } } }
220
- it { should be_false }
220
+ it { expect(subject).to be_falsey }
221
221
  end
222
222
 
223
223
  context 'with :exclude' do
224
224
  let(:options) { { exclude: ->(s) { s.properties.inactive } } }
225
- it { should be_true }
225
+ it { expect(subject).to be_truthy }
226
226
  end
227
227
 
228
228
  end