librarian-chef 0.0.1.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,451 @@
1
+ require 'pathname'
2
+ require 'securerandom'
3
+
4
+ require 'librarian'
5
+ require 'librarian/helpers'
6
+ require 'librarian/error'
7
+ require 'librarian/action/resolve'
8
+ require 'librarian/action/install'
9
+ require 'librarian/action/update'
10
+ require 'librarian/chef'
11
+
12
+ module Librarian
13
+ module Chef
14
+ module Source
15
+ describe Git do
16
+
17
+ let(:project_path) do
18
+ project_path = Pathname.new(__FILE__).expand_path
19
+ project_path = project_path.dirname until project_path.join("Rakefile").exist?
20
+ project_path
21
+ end
22
+ let(:tmp_path) { project_path.join("tmp/spec/integration/chef/source/git") }
23
+ after { tmp_path.rmtree if tmp_path && tmp_path.exist? }
24
+
25
+ let(:cookbooks_path) { tmp_path.join("cookbooks") }
26
+
27
+ # depends on repo_path being defined in each context
28
+ let(:env) { Environment.new(:project_path => repo_path) }
29
+
30
+ context "a single dependency with a git source" do
31
+
32
+ let(:sample_path) { tmp_path.join("sample") }
33
+ let(:sample_metadata) do
34
+ Helpers.strip_heredoc(<<-METADATA)
35
+ version "0.6.5"
36
+ METADATA
37
+ end
38
+
39
+ let(:first_sample_path) { cookbooks_path.join("first-sample") }
40
+ let(:first_sample_metadata) do
41
+ Helpers.strip_heredoc(<<-METADATA)
42
+ version "3.2.1"
43
+ METADATA
44
+ end
45
+
46
+ let(:second_sample_path) { cookbooks_path.join("second-sample") }
47
+ let(:second_sample_metadata) do
48
+ Helpers.strip_heredoc(<<-METADATA)
49
+ version "4.3.2"
50
+ METADATA
51
+ end
52
+
53
+ before do
54
+ sample_path.rmtree if sample_path.exist?
55
+ sample_path.mkpath
56
+ sample_path.join("metadata.rb").open("wb") { |f| f.write(sample_metadata) }
57
+ Dir.chdir(sample_path) do
58
+ `git init`
59
+ `git config user.name "Simba"`
60
+ `git config user.email "simba@savannah-pride.gov"`
61
+ `git add metadata.rb`
62
+ `git commit -m "Initial commit."`
63
+ end
64
+
65
+ cookbooks_path.rmtree if cookbooks_path.exist?
66
+ cookbooks_path.mkpath
67
+ first_sample_path.mkpath
68
+ first_sample_path.join("metadata.rb").open("wb") { |f| f.write(first_sample_metadata) }
69
+ second_sample_path.mkpath
70
+ second_sample_path.join("metadata.rb").open("wb") { |f| f.write(second_sample_metadata) }
71
+ Dir.chdir(cookbooks_path) do
72
+ `git init`
73
+ `git config user.name "Simba"`
74
+ `git config user.email "simba@savannah-pride.gov"`
75
+ `git add .`
76
+ `git commit -m "Initial commit."`
77
+ end
78
+ end
79
+
80
+ context "resolving" do
81
+ let(:repo_path) { tmp_path.join("repo/resolve") }
82
+ before do
83
+ repo_path.rmtree if repo_path.exist?
84
+ repo_path.mkpath
85
+ repo_path.join("cookbooks").mkpath
86
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
87
+ #!/usr/bin/env ruby
88
+ cookbook "sample", :git => #{sample_path.to_s.inspect}
89
+ CHEFFILE
90
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
91
+ end
92
+
93
+ context "the resolve" do
94
+ it "should not raise an exception" do
95
+ expect { Action::Resolve.new(env).run }.to_not raise_error
96
+ end
97
+ end
98
+
99
+ context "the results" do
100
+ before { Action::Resolve.new(env).run }
101
+
102
+ it "should create the lockfile" do
103
+ repo_path.join("Cheffile.lock").should exist
104
+ end
105
+
106
+ it "should not attempt to install the sample cookbok" do
107
+ repo_path.join("cookbooks/sample").should_not exist
108
+ end
109
+ end
110
+ end
111
+
112
+ context "installing" do
113
+ let(:repo_path) { tmp_path.join("repo/install") }
114
+ before do
115
+ repo_path.rmtree if repo_path.exist?
116
+ repo_path.mkpath
117
+ repo_path.join("cookbooks").mkpath
118
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
119
+ #!/usr/bin/env ruby
120
+ cookbook "sample", :git => #{sample_path.to_s.inspect}
121
+ CHEFFILE
122
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
123
+
124
+ Action::Resolve.new(env).run
125
+ end
126
+
127
+ context "the install" do
128
+ it "should not raise an exception" do
129
+ expect { Action::Install.new(env).run }.to_not raise_error
130
+ end
131
+ end
132
+
133
+ context "the results" do
134
+ before { Action::Install.new(env).run }
135
+
136
+ it "should create the lockfile" do
137
+ repo_path.join("Cheffile.lock").should exist
138
+ end
139
+
140
+ it "should create the directory for the cookbook" do
141
+ repo_path.join("cookbooks/sample").should exist
142
+ end
143
+
144
+ it "should copy the cookbook files into the cookbook directory" do
145
+ repo_path.join("cookbooks/sample/metadata.rb").should exist
146
+ end
147
+ end
148
+ end
149
+
150
+ context "resolving and and separately installing" do
151
+ let(:repo_path) { tmp_path.join("repo/resolve-install") }
152
+ before do
153
+ repo_path.rmtree if repo_path.exist?
154
+ repo_path.mkpath
155
+ repo_path.join("cookbooks").mkpath
156
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
157
+ #!/usr/bin/env ruby
158
+ cookbook "sample", :git => #{sample_path.to_s.inspect}
159
+ CHEFFILE
160
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
161
+
162
+ Action::Resolve.new(env).run
163
+ repo_path.join("tmp").rmtree if repo_path.join("tmp").exist?
164
+ end
165
+
166
+ context "the install" do
167
+ it "should not raise an exception" do
168
+ expect { Action::Install.new(env).run }.to_not raise_error
169
+ end
170
+ end
171
+
172
+ context "the results" do
173
+ before { Action::Install.new(env).run }
174
+
175
+ it "should create the directory for the cookbook" do
176
+ repo_path.join("cookbooks/sample").should exist
177
+ end
178
+
179
+ it "should copy the cookbook files into the cookbook directory" do
180
+ repo_path.join("cookbooks/sample/metadata.rb").should exist
181
+ end
182
+ end
183
+ end
184
+
185
+ context "resolving, changing, and resolving" do
186
+ let(:repo_path) { tmp_path.join("repo/resolve-update") }
187
+ before do
188
+ repo_path.rmtree if repo_path.exist?
189
+ repo_path.mkpath
190
+ repo_path.join("cookbooks").mkpath
191
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
192
+ git #{cookbooks_path.to_s.inspect}
193
+ cookbook "first-sample"
194
+ CHEFFILE
195
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
196
+ Action::Resolve.new(env).run
197
+
198
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
199
+ git #{cookbooks_path.to_s.inspect}
200
+ cookbook "first-sample"
201
+ cookbook "second-sample"
202
+ CHEFFILE
203
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
204
+ end
205
+
206
+ context "the second resolve" do
207
+ it "should not raise an exception" do
208
+ expect { Action::Resolve.new(env).run }.to_not raise_error
209
+ end
210
+ end
211
+ end
212
+
213
+ end
214
+
215
+ context "with a path" do
216
+
217
+ let(:git_path) { tmp_path.join("big-git-repo") }
218
+ let(:sample_path) { git_path.join("buttercup") }
219
+ let(:sample_metadata) do
220
+ Helpers.strip_heredoc(<<-METADATA)
221
+ version "0.6.5"
222
+ METADATA
223
+ end
224
+
225
+ before do
226
+ git_path.rmtree if git_path.exist?
227
+ git_path.mkpath
228
+ sample_path.mkpath
229
+ sample_path.join("metadata.rb").open("wb") { |f| f.write(sample_metadata) }
230
+ Dir.chdir(git_path) do
231
+ `git init`
232
+ `git config user.name "Simba"`
233
+ `git config user.email "simba@savannah-pride.gov"`
234
+ `git add .`
235
+ `git commit -m "Initial commit."`
236
+ end
237
+ end
238
+
239
+ context "if no path option is given" do
240
+ let(:repo_path) { tmp_path.join("repo/resolve") }
241
+ before do
242
+ repo_path.rmtree if repo_path.exist?
243
+ repo_path.mkpath
244
+ repo_path.join("cookbooks").mkpath
245
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
246
+ #!/usr/bin/env ruby
247
+ cookbook "sample",
248
+ :git => #{git_path.to_s.inspect}
249
+ CHEFFILE
250
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
251
+ end
252
+
253
+ it "should not resolve" do
254
+ expect{ Action::Resolve.new(env).run }.to raise_error
255
+ end
256
+ end
257
+
258
+ context "if the path option is wrong" do
259
+ let(:repo_path) { tmp_path.join("repo/resolve") }
260
+ before do
261
+ repo_path.rmtree if repo_path.exist?
262
+ repo_path.mkpath
263
+ repo_path.join("cookbooks").mkpath
264
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
265
+ #!/usr/bin/env ruby
266
+ cookbook "sample",
267
+ :git => #{git_path.to_s.inspect},
268
+ :path => "jelly"
269
+ CHEFFILE
270
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
271
+ end
272
+
273
+ it "should not resolve" do
274
+ expect{ Action::Resolve.new(env).run }.to raise_error
275
+ end
276
+ end
277
+
278
+ context "if the path option is right" do
279
+ let(:repo_path) { tmp_path.join("repo/resolve") }
280
+ before do
281
+ repo_path.rmtree if repo_path.exist?
282
+ repo_path.mkpath
283
+ repo_path.join("cookbooks").mkpath
284
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
285
+ #!/usr/bin/env ruby
286
+ cookbook "sample",
287
+ :git => #{git_path.to_s.inspect},
288
+ :path => "buttercup"
289
+ CHEFFILE
290
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
291
+ end
292
+
293
+ context "the resolve" do
294
+ it "should not raise an exception" do
295
+ expect { Action::Resolve.new(env).run }.to_not raise_error
296
+ end
297
+ end
298
+
299
+ context "the results" do
300
+ before { Action::Resolve.new(env).run }
301
+
302
+ it "should create the lockfile" do
303
+ repo_path.join("Cheffile.lock").should exist
304
+ end
305
+ end
306
+ end
307
+
308
+ end
309
+
310
+ context "missing a metadata" do
311
+ let(:git_path) { tmp_path.join("big-git-repo") }
312
+ let(:repo_path) { tmp_path.join("repo/resolve") }
313
+ before do
314
+ git_path.rmtree if git_path.exist?
315
+ git_path.mkpath
316
+ Dir.chdir(git_path) do
317
+ `git init`
318
+ `git config user.name "Simba"`
319
+ `git config user.email "simba@savannah-pride.gov"`
320
+ `touch not-a-metadata`
321
+ `git add .`
322
+ `git commit -m "Initial commit."`
323
+ end
324
+ repo_path.rmtree if repo_path.exist?
325
+ repo_path.mkpath
326
+ repo_path.join("cookbooks").mkpath
327
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
328
+ cookbook "sample",
329
+ :git => #{git_path.to_s.inspect}
330
+ CHEFFILE
331
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
332
+ end
333
+
334
+ context "the resolve" do
335
+ it "should raise an exception" do
336
+ expect { Action::Resolve.new(env).run }.to raise_error
337
+ end
338
+
339
+ it "should explain the problem" do
340
+ expect { Action::Resolve.new(env).run }.
341
+ to raise_error(Error, /no metadata file found/i)
342
+ end
343
+ end
344
+
345
+ context "the results" do
346
+ before { Action::Resolve.new(env).run rescue nil }
347
+
348
+ it "should not create the lockfile" do
349
+ repo_path.join("Cheffile.lock").should_not exist
350
+ end
351
+
352
+ it "should not create the directory for the cookbook" do
353
+ repo_path.join("cookbooks/sample").should_not exist
354
+ end
355
+ end
356
+ end
357
+
358
+ context "when upstream updates" do
359
+ let(:git_path) { tmp_path.join("upstream-updates-repo") }
360
+ let(:repo_path) { tmp_path.join("repo/resolve-with-upstream-updates") }
361
+
362
+ let(:sample_metadata) do
363
+ Helpers.strip_heredoc(<<-METADATA)
364
+ version "0.6.5"
365
+ METADATA
366
+ end
367
+ before do
368
+
369
+ # set up the git repo as normal, but let's also set up a release-stable branch
370
+ # from which our Cheffile will only pull stable releases
371
+ git_path.rmtree if git_path.exist?
372
+ git_path.mkpath
373
+ git_path.join("metadata.rb").open("w+b"){|f| f.write(sample_metadata)}
374
+
375
+ Dir.chdir(git_path) do
376
+ `git init`
377
+ `git config user.name "Simba"`
378
+ `git config user.email "simba@savannah-pride.gov"`
379
+ `git add metadata.rb`
380
+ `git commit -m "Initial Commit."`
381
+ `git checkout -b some-branch --quiet`
382
+ `echo 'hi' > some-file`
383
+ `git add some-file`
384
+ `git commit -m 'Some File.'`
385
+ `git checkout master --quiet`
386
+ end
387
+
388
+ # set up the chef repo as normal, except the Cheffile points to the release-stable
389
+ # branch - we expect when the upstream copy of that branch is changed, then we can
390
+ # fetch & merge those changes when we update
391
+ repo_path.rmtree if repo_path.exist?
392
+ repo_path.mkpath
393
+ repo_path.join("cookbooks").mkpath
394
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
395
+ cookbook "sample",
396
+ :git => #{git_path.to_s.inspect},
397
+ :ref => "some-branch"
398
+ CHEFFILE
399
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
400
+ Action::Resolve.new(env).run
401
+
402
+ # change the upstream copy of that branch: we expect to be able to pull the latest
403
+ # when we re-resolve
404
+ Dir.chdir(git_path) do
405
+ `git checkout some-branch --quiet`
406
+ `echo 'ho' > some-other-file`
407
+ `git add some-other-file`
408
+ `git commit -m 'Some Other File.'`
409
+ `git checkout master --quiet`
410
+ end
411
+ end
412
+
413
+ let(:metadata_file) { repo_path.join("cookbooks/sample/metadata.rb") }
414
+ let(:old_code_file) { repo_path.join("cookbooks/sample/some-file") }
415
+ let(:new_code_file) { repo_path.join("cookbooks/sample/some-other-file") }
416
+
417
+ context "when updating not a cookbook from that source" do
418
+ before do
419
+ Action::Update.new(env).run
420
+ end
421
+
422
+ it "should pull the tip from upstream" do
423
+ Action::Install.new(env).run
424
+
425
+ metadata_file.should exist #sanity
426
+ old_code_file.should exist #sanity
427
+
428
+ new_code_file.should_not exist # the assertion
429
+ end
430
+ end
431
+
432
+ context "when updating a cookbook from that source" do
433
+ before do
434
+ Action::Update.new(env, :names => %w(sample)).run
435
+ end
436
+
437
+ it "should pull the tip from upstream" do
438
+ Action::Install.new(env).run
439
+
440
+ metadata_file.should exist #sanity
441
+ old_code_file.should exist #sanity
442
+
443
+ new_code_file.should exist # the assertion
444
+ end
445
+ end
446
+ end
447
+
448
+ end
449
+ end
450
+ end
451
+ end
@@ -0,0 +1,217 @@
1
+ require 'pathname'
2
+ require 'json'
3
+ require 'webmock'
4
+
5
+ require 'librarian'
6
+ require 'librarian/helpers'
7
+ require 'librarian/action/resolve'
8
+ require 'librarian/action/install'
9
+ require 'librarian/chef'
10
+
11
+ module Librarian
12
+ module Chef
13
+ module Source
14
+ describe Site do
15
+
16
+ include WebMock::API
17
+
18
+ let(:project_path) do
19
+ project_path = Pathname.new(__FILE__).expand_path
20
+ project_path = project_path.dirname until project_path.join("Rakefile").exist?
21
+ project_path
22
+ end
23
+ let(:tmp_path) { project_path.join("tmp/spec/integration/chef/source/site") }
24
+ after { tmp_path.rmtree if tmp_path && tmp_path.exist? }
25
+ let(:sample_path) { tmp_path.join("sample") }
26
+ let(:sample_metadata) do
27
+ Helpers.strip_heredoc(<<-METADATA)
28
+ version "0.6.5"
29
+ METADATA
30
+ end
31
+
32
+ let(:api_url) { "http://site.cookbooks.com" }
33
+
34
+ let(:sample_index_data) do
35
+ {
36
+ "name" => "sample",
37
+ "versions" => [
38
+ "#{api_url}/cookbooks/sample/versions/0_6_5"
39
+ ]
40
+ }
41
+ end
42
+ let(:sample_0_6_5_data) do
43
+ {
44
+ "version" => "0.6.5",
45
+ "file" => "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar.gz"
46
+ }
47
+ end
48
+ let(:sample_0_6_5_package) do
49
+ s = StringIO.new
50
+ z = Zlib::GzipWriter.new(s, Zlib::NO_COMPRESSION)
51
+ t = Archive::Tar::Minitar::Output.new(z)
52
+ t.tar.add_file_simple("sample/metadata.rb", :mode => 0700,
53
+ :size => sample_metadata.bytesize){|io| io.write(sample_metadata)}
54
+ t.close
55
+ z.close unless z.closed?
56
+ s.string
57
+ end
58
+
59
+ # depends on repo_path being defined in each context
60
+ let(:env) { Environment.new(:project_path => repo_path) }
61
+
62
+ before do
63
+ stub_request(:get, "#{api_url}/cookbooks/sample").
64
+ to_return(:body => JSON.dump(sample_index_data))
65
+
66
+ stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5").
67
+ to_return(:body => JSON.dump(sample_0_6_5_data))
68
+
69
+ stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar.gz").
70
+ to_return(:body => sample_0_6_5_package)
71
+ end
72
+
73
+ after do
74
+ WebMock.reset!
75
+ end
76
+
77
+ context "a single dependency with a site source" do
78
+
79
+ context "resolving" do
80
+ let(:repo_path) { tmp_path.join("repo/resolve") }
81
+ before do
82
+ repo_path.rmtree if repo_path.exist?
83
+ repo_path.mkpath
84
+ repo_path.join("cookbooks").mkpath
85
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
86
+ #!/usr/bin/env ruby
87
+ cookbook "sample", :site => #{api_url.inspect}
88
+ CHEFFILE
89
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
90
+ end
91
+
92
+ context "the resolve" do
93
+ it "should not raise an exception" do
94
+ expect { Action::Resolve.new(env).run }.to_not raise_error
95
+ end
96
+ end
97
+
98
+ context "the results" do
99
+ before { Action::Resolve.new(env).run }
100
+
101
+ it "should create the lockfile" do
102
+ repo_path.join("Cheffile.lock").should exist
103
+ end
104
+
105
+ it "should not attempt to install the cookbok" do
106
+ repo_path.join("cookbooks/sample").should_not exist
107
+ end
108
+ end
109
+ end
110
+
111
+ context "intalling" do
112
+ let(:repo_path) { tmp_path.join("repo/install") }
113
+ before do
114
+ repo_path.rmtree if repo_path.exist?
115
+ repo_path.mkpath
116
+ repo_path.join("cookbooks").mkpath
117
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
118
+ #!/usr/bin/env ruby
119
+ cookbook "sample", :site => #{api_url.inspect}
120
+ CHEFFILE
121
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
122
+
123
+ Action::Resolve.new(env).run
124
+ end
125
+
126
+ context "the install" do
127
+ it "should not raise an exception" do
128
+ expect { Action::Install.new(env).run }.to_not raise_error
129
+ end
130
+ end
131
+
132
+ context "the results" do
133
+ before { Action::Install.new(env).run }
134
+
135
+ it "should create the lockfile" do
136
+ repo_path.join("Cheffile.lock").should exist
137
+ end
138
+
139
+ it "should create a directory for the cookbook" do
140
+ repo_path.join("cookbooks/sample").should exist
141
+ end
142
+
143
+ it "should copy the cookbook files into the cookbook directory" do
144
+ repo_path.join("cookbooks/sample/metadata.rb").should exist
145
+ end
146
+ end
147
+ end
148
+
149
+ context "resolving and separately installing" do
150
+ let(:repo_path) { tmp_path.join("repo/resolve-install") }
151
+ before do
152
+ repo_path.rmtree if repo_path.exist?
153
+ repo_path.mkpath
154
+ repo_path.join("cookbooks").mkpath
155
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
156
+ #!/usr/bin/env ruby
157
+ cookbook "sample", :site => #{api_url.inspect}
158
+ CHEFFILE
159
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
160
+
161
+ Action::Resolve.new(env).run
162
+ repo_path.join("tmp").rmtree if repo_path.join("tmp").exist?
163
+ end
164
+
165
+ context "the install" do
166
+ it "should not raise an exception" do
167
+ expect { Action::Install.new(env).run }.to_not raise_error
168
+ end
169
+ end
170
+
171
+ context "the results" do
172
+ before { Action::Install.new(env).run }
173
+
174
+ it "should create a directory for the cookbook" do
175
+ repo_path.join("cookbooks/sample").should exist
176
+ end
177
+
178
+ it "should copy the cookbook files into the cookbook directory" do
179
+ repo_path.join("cookbooks/sample/metadata.rb").should exist
180
+ end
181
+ end
182
+ end
183
+
184
+ end
185
+
186
+ context "when the repo path has a space" do
187
+
188
+ let(:repo_path) { tmp_path.join("repo/with extra spaces/resolve") }
189
+
190
+ before do
191
+ repo_path.rmtree if repo_path.exist?
192
+ repo_path.mkpath
193
+ repo_path.join("cookbooks").mkpath
194
+
195
+ cheffile = Helpers.strip_heredoc(<<-CHEFFILE)
196
+ #!/usr/bin/env ruby
197
+ cookbook "sample", :site => #{api_url.inspect}
198
+ CHEFFILE
199
+ repo_path.join("Cheffile").open("wb") { |f| f.write(cheffile) }
200
+ end
201
+
202
+ after do
203
+ repo_path.rmtree
204
+ end
205
+
206
+ context "the resolution" do
207
+ it "should not raise an exception" do
208
+ expect { Action::Resolve.new(env).run }.to_not raise_error
209
+ end
210
+ end
211
+
212
+ end
213
+
214
+ end
215
+ end
216
+ end
217
+ end