continuum-stager-api 0.1.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.
- checksums.yaml +15 -0
- data/.gitignore +22 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +37 -0
- data/Rakefile +7 -0
- data/apcera-stager-api-contrib.gemspec +25 -0
- data/lib/apcera/stager/error.rb +6 -0
- data/lib/apcera/stager/loader.rb +10 -0
- data/lib/apcera/stager/stager.rb +270 -0
- data/lib/continuum-stager-api.rb +1 -0
- data/spec/apcera/stager/stager_spec.rb +496 -0
- data/spec/fixtures/cassettes/complete.yml +55 -0
- data/spec/fixtures/cassettes/dependencies_add.yml +53 -0
- data/spec/fixtures/cassettes/dependencies_remove.yml +53 -0
- data/spec/fixtures/cassettes/done.yml +28 -0
- data/spec/fixtures/cassettes/download.yml +37400 -0
- data/spec/fixtures/cassettes/environment_add.yml +53 -0
- data/spec/fixtures/cassettes/environment_remove.yml +53 -0
- data/spec/fixtures/cassettes/fail.yml +28 -0
- data/spec/fixtures/cassettes/invalid/complete.yml +55 -0
- data/spec/fixtures/cassettes/invalid/dependencies_add.yml +53 -0
- data/spec/fixtures/cassettes/invalid/dependencies_remove.yml +53 -0
- data/spec/fixtures/cassettes/invalid/done.yml +55 -0
- data/spec/fixtures/cassettes/invalid/download.yml +54 -0
- data/spec/fixtures/cassettes/invalid/environment_add.yml +53 -0
- data/spec/fixtures/cassettes/invalid/environment_remove.yml +53 -0
- data/spec/fixtures/cassettes/invalid/fail.yml +28 -0
- data/spec/fixtures/cassettes/invalid/metadata.yml +28 -0
- data/spec/fixtures/cassettes/invalid/provides_add.yml +53 -0
- data/spec/fixtures/cassettes/invalid/provides_remove.yml +53 -0
- data/spec/fixtures/cassettes/invalid/relaunch.yml +53 -0
- data/spec/fixtures/cassettes/invalid/snapshot.yml +53 -0
- data/spec/fixtures/cassettes/invalid/templates_add.yml +53 -0
- data/spec/fixtures/cassettes/invalid/templates_remove.yml +53 -0
- data/spec/fixtures/cassettes/invalid/upload.yml +55 -0
- data/spec/fixtures/cassettes/metadata.yml +51 -0
- data/spec/fixtures/cassettes/provides_add.yml +53 -0
- data/spec/fixtures/cassettes/provides_remove.yml +116 -0
- data/spec/fixtures/cassettes/relaunch.yml +28 -0
- data/spec/fixtures/cassettes/snapshot.yml +28 -0
- data/spec/fixtures/cassettes/templates_add.yml +53 -0
- data/spec/fixtures/cassettes/templates_remove.yml +53 -0
- data/spec/fixtures/cassettes/upload.yml +30 -0
- data/spec/spec_helper.rb +31 -0
- metadata +189 -0
@@ -0,0 +1 @@
|
|
1
|
+
require File.join('apcera', 'stager', 'loader.rb')
|
@@ -0,0 +1,496 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Apcera::Stager do
|
4
|
+
before do
|
5
|
+
@appdir = "site"
|
6
|
+
|
7
|
+
# See mock server directory for setup!
|
8
|
+
@stager_url = "http://example.com"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should raise an exception when initialized without a stager url" do
|
12
|
+
expect { Apcera::Stager.new }.to raise_error(Apcera::Error::StagerURLRequired)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should initialize with the stager url passed as an argument" do
|
16
|
+
stager = Apcera::Stager.new({:stager_url => @stager_url})
|
17
|
+
stager.class.should == Apcera::Stager
|
18
|
+
stager.stager_url.should == @stager_url
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should initialize when the ENV variable STAGER_URL is present" do
|
22
|
+
begin
|
23
|
+
ENV["STAGER_URL"] = @stager_url
|
24
|
+
stager = Apcera::Stager.new
|
25
|
+
stager.class.should == Apcera::Stager
|
26
|
+
stager.stager_url.should == @stager_url
|
27
|
+
ensure
|
28
|
+
ENV["STAGER_URL"] = nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context do
|
33
|
+
before do
|
34
|
+
@stager = Apcera::Stager.new({:stager_url => @stager_url})
|
35
|
+
|
36
|
+
# Best way to get our current path is to get the gem_dir!
|
37
|
+
spec = Gem::Specification.find_by_name("continuum-stager-api").gem_dir
|
38
|
+
|
39
|
+
# Lets write files in spec/tmp for tests!
|
40
|
+
@stager.root_path = File.join(spec, "spec", "tmp")
|
41
|
+
@stager.pkg_path = File.join(@stager.root_path, "pkg.tar.gz")
|
42
|
+
@stager.updated_pkg_path = File.join(@stager.root_path, "updated.tar.gz")
|
43
|
+
@stager.system_options = { :out => "/dev/null", :err => "/dev/null" }
|
44
|
+
|
45
|
+
# We don't want to exit in tests.
|
46
|
+
@stager.stub(:exit0r)
|
47
|
+
@stager.stub(:output_error)
|
48
|
+
@stager.stub(:output)
|
49
|
+
end
|
50
|
+
|
51
|
+
after do
|
52
|
+
# Don't trust urls above, they could be changed in tests.
|
53
|
+
# This does an actual delete and we want to be specific.
|
54
|
+
spec = Gem::Specification.find_by_name("continuum-stager-api").gem_dir
|
55
|
+
test_files = File.join(spec, "spec", "tmp", "*")
|
56
|
+
|
57
|
+
# Remove the test files.
|
58
|
+
FileUtils.rm_rf Dir.glob(test_files)
|
59
|
+
end
|
60
|
+
|
61
|
+
context "download" do
|
62
|
+
it "should download the app package to pkg.tar.gz" do
|
63
|
+
VCR.use_cassette('download') do
|
64
|
+
@stager.download
|
65
|
+
end
|
66
|
+
File.exists?(@stager.pkg_path).should == true
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should bubble errors to fail" do
|
70
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
71
|
+
|
72
|
+
VCR.use_cassette('invalid/download') do
|
73
|
+
expect { @stager.download }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "extract" do
|
79
|
+
it "should decompress the package to a supplied path" do
|
80
|
+
VCR.use_cassette('download') do
|
81
|
+
@stager.download
|
82
|
+
end
|
83
|
+
|
84
|
+
@stager.extract(@appdir)
|
85
|
+
File.exists?(File.join(@stager.root_path, @appdir)).should == true
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should bubble errors to fail" do
|
89
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
90
|
+
|
91
|
+
err = Apcera::Error::ExecuteError.new
|
92
|
+
@stager.should_receive(:execute_app).and_raise(err)
|
93
|
+
|
94
|
+
VCR.use_cassette('download') do
|
95
|
+
@stager.download
|
96
|
+
end
|
97
|
+
|
98
|
+
expect { @stager.extract(@appdir) }.to raise_error(err)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "execute" do
|
103
|
+
it "should execute commands with clean bundler environment" do
|
104
|
+
Bundler.should_receive(:with_clean_env).at_least(:once).and_yield
|
105
|
+
|
106
|
+
VCR.use_cassette('download') do
|
107
|
+
@stager.download
|
108
|
+
end
|
109
|
+
|
110
|
+
@stager.extract(@appdir)
|
111
|
+
|
112
|
+
@stager.execute("cat thing").should == nil
|
113
|
+
@stager.execute("cat #{File.join(@stager.app_path, "app", "Gemfile")}").should == true
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should bubble errors to fail" do
|
117
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
118
|
+
|
119
|
+
VCR.use_cassette('download') do
|
120
|
+
@stager.download
|
121
|
+
end
|
122
|
+
|
123
|
+
@stager.extract(@appdir)
|
124
|
+
|
125
|
+
cmd = "cat thing"
|
126
|
+
expect {@stager.execute(cmd) }.to raise_error(Apcera::Error::ExecuteError, "failed to execute: #{cmd}.\n")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context "execute_app" do
|
131
|
+
it "should execute commands in app dir with clean bundler environment" do
|
132
|
+
Bundler.should_receive(:with_clean_env).at_least(:once).and_yield
|
133
|
+
|
134
|
+
VCR.use_cassette('download') do
|
135
|
+
@stager.download
|
136
|
+
end
|
137
|
+
|
138
|
+
@stager.extract(@appdir)
|
139
|
+
|
140
|
+
@stager.execute_app("cat thing").should == nil
|
141
|
+
@stager.execute_app("cat #{File.join("app", "Gemfile")}").should == true
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should bubble errors to fail" do
|
145
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
146
|
+
|
147
|
+
VCR.use_cassette('download') do
|
148
|
+
@stager.download
|
149
|
+
end
|
150
|
+
|
151
|
+
@stager.extract(@appdir)
|
152
|
+
|
153
|
+
cmd = "cat thing"
|
154
|
+
expect {@stager.execute_app(cmd) }.to raise_error(Apcera::Error::ExecuteError, "failed to execute: #{cmd}.\n")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context "upload" do
|
159
|
+
it "should compress a new package and send to the staging coordinator" do
|
160
|
+
VCR.use_cassette('download') do
|
161
|
+
@stager.download
|
162
|
+
end
|
163
|
+
|
164
|
+
@stager.extract(@appdir)
|
165
|
+
|
166
|
+
VCR.use_cassette('upload') do
|
167
|
+
@stager.upload
|
168
|
+
end
|
169
|
+
|
170
|
+
File.exists?(File.join(@stager.root_path, "#{@appdir}.tar.gz"))
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should bubble errors to fail" do
|
174
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
175
|
+
|
176
|
+
VCR.use_cassette('download') do
|
177
|
+
@stager.download
|
178
|
+
end
|
179
|
+
|
180
|
+
@stager.extract(@appdir)
|
181
|
+
|
182
|
+
VCR.use_cassette('invalid/upload') do
|
183
|
+
expect { @stager.upload }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context "complete" do
|
189
|
+
it "should compress a new package and send to the staging coordinator then be done" do
|
190
|
+
VCR.use_cassette('download') do
|
191
|
+
@stager.download
|
192
|
+
end
|
193
|
+
|
194
|
+
@stager.extract(@appdir)
|
195
|
+
|
196
|
+
VCR.use_cassette('complete') do
|
197
|
+
@stager.complete
|
198
|
+
end
|
199
|
+
|
200
|
+
File.exists?(File.join(@stager.root_path, "#{@appdir}.tar.gz"))
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should bubble errors to fail" do
|
204
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
205
|
+
|
206
|
+
VCR.use_cassette('download') do
|
207
|
+
@stager.download
|
208
|
+
end
|
209
|
+
|
210
|
+
@stager.extract(@appdir)
|
211
|
+
|
212
|
+
VCR.use_cassette('invalid/complete') do
|
213
|
+
expect { @stager.complete }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context "done" do
|
219
|
+
it "should send done to the staging coordinator" do
|
220
|
+
@stager.should_receive(:exit0r).with(0)
|
221
|
+
|
222
|
+
VCR.use_cassette('done') do
|
223
|
+
@stager.done
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should bubble errors to fail" do
|
228
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
229
|
+
|
230
|
+
VCR.use_cassette('invalid/done') do
|
231
|
+
expect { @stager.done }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
context "snapshot" do
|
237
|
+
it "should send a snapshot request to the staging coordinator" do
|
238
|
+
VCR.use_cassette('download') do
|
239
|
+
@stager.download
|
240
|
+
end
|
241
|
+
@stager.extract(@appdir)
|
242
|
+
VCR.use_cassette('snapshot') do
|
243
|
+
@stager.snapshot
|
244
|
+
end
|
245
|
+
VCR.use_cassette('done') do
|
246
|
+
@stager.done
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should bubble errors to fail" do
|
251
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
252
|
+
|
253
|
+
VCR.use_cassette('download') do
|
254
|
+
@stager.download
|
255
|
+
end
|
256
|
+
@stager.extract(@appdir)
|
257
|
+
VCR.use_cassette('invalid/snapshot') do
|
258
|
+
expect { @stager.snapshot }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
context "fail" do
|
264
|
+
it "should have a fail hook that exits with exit code 1" do
|
265
|
+
# Make sure we don't exit and that we called exit 1.
|
266
|
+
@stager.should_receive(:exit0r).with(1)
|
267
|
+
|
268
|
+
VCR.use_cassette('fail') do
|
269
|
+
@stager.fail.should == "OK"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context "meta" do
|
275
|
+
it "should recieve package metadata and cache it" do
|
276
|
+
VCR.use_cassette('metadata') do
|
277
|
+
@stager.meta.class.should == Hash
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
it "should throw errors" do
|
282
|
+
VCR.use_cassette('invalid/metadata') do
|
283
|
+
expect { @stager.meta }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
context "relaunch" do
|
289
|
+
it "should allow you to trigger a stager relaunch" do
|
290
|
+
@stager.should_receive(:exit0r).with(0) { 0 }
|
291
|
+
|
292
|
+
VCR.use_cassette('relaunch') do
|
293
|
+
@stager.relaunch.should == 0
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
it "should bubble errors to fail" do
|
298
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
299
|
+
|
300
|
+
VCR.use_cassette('invalid/relaunch') do
|
301
|
+
expect { @stager.relaunch }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
context "start_command" do
|
307
|
+
it "should return the package start command" do
|
308
|
+
VCR.use_cassette('metadata') do
|
309
|
+
@stager.start_command.should == "./startme"
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
it "should allow you to set the start command" do
|
314
|
+
VCR.use_cassette('environment_add') do
|
315
|
+
@stager.start_command = "./startme"
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
context "start_path" do
|
321
|
+
it "should return the package start path" do
|
322
|
+
VCR.use_cassette('metadata') do
|
323
|
+
@stager.start_path.should == "/app"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
it "should allow you to set the start path" do
|
328
|
+
VCR.use_cassette('environment_add') do
|
329
|
+
@stager.start_path = "/app"
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
context "environment_add" do
|
335
|
+
it "should add an environment variable" do
|
336
|
+
VCR.use_cassette('environment_add') do
|
337
|
+
@stager.environment_add("TEST_VAR", "foo")
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
it "should bubble errors to fail" do
|
342
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
343
|
+
|
344
|
+
VCR.use_cassette('invalid/environment_add') do
|
345
|
+
expect { @stager.environment_add("TEST_VAR", "foo") }.to raise_error
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
context "environment_remove" do
|
351
|
+
it "should environment_remove an environment variable" do
|
352
|
+
VCR.use_cassette('environment_remove') do
|
353
|
+
@stager.environment_remove("TEST_VAR", "foo")
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
it "should bubble errors to fail" do
|
358
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
359
|
+
|
360
|
+
VCR.use_cassette('invalid/environment_remove') do
|
361
|
+
expect { @stager.environment_remove("TEST_VAR", "foo") }.to raise_error
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
context "provides_add" do
|
367
|
+
it "should add to its list of provides" do
|
368
|
+
VCR.use_cassette('provides_add') do
|
369
|
+
@stager.provides_add("os", "linux")
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
it "should bubble errors to fail" do
|
374
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
375
|
+
|
376
|
+
VCR.use_cassette('invalid/provides_add') do
|
377
|
+
expect { @stager.provides_add("os", "linux") }.to raise_error
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
context "provides_remove" do
|
383
|
+
it "should remove from its list of provides" do
|
384
|
+
VCR.use_cassette('provides_remove') do
|
385
|
+
@stager.provides_remove("os", "linux")
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
it "should bubble errors to fail" do
|
390
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
391
|
+
|
392
|
+
VCR.use_cassette('invalid/provides_remove') do
|
393
|
+
expect { @stager.provides_remove("os", "linux") }.to raise_error
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
context "dependencies_add" do
|
399
|
+
it "should add to its list of dependencies" do
|
400
|
+
VCR.use_cassette('dependencies_add') do
|
401
|
+
@stager.dependencies_add("os", "linux")
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
it "should bubble errors to fail" do
|
406
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
407
|
+
|
408
|
+
VCR.use_cassette('invalid/dependencies_add') do
|
409
|
+
expect { @stager.dependencies_add("os", "linux") }.to raise_error
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
context "dependencies_remove" do
|
415
|
+
it "should remove from its list of dependencies" do
|
416
|
+
VCR.use_cassette('dependencies_remove') do
|
417
|
+
@stager.dependencies_remove("os", "linux")
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
it "should bubble errors to fail" do
|
422
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
423
|
+
|
424
|
+
VCR.use_cassette('invalid/dependencies_remove') do
|
425
|
+
expect { @stager.dependencies_remove("os", "linux") }.to raise_error
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
context "templates_add" do
|
431
|
+
it "should add to its list of templates" do
|
432
|
+
VCR.use_cassette('templates_add') do
|
433
|
+
@stager.templates_add("/path/to/template")
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
it "should add to its list of templates with delimiters" do
|
438
|
+
VCR.use_cassette('templates_add') do
|
439
|
+
@stager.templates_add("/path/to/template", "{{", "}}")
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
it "should bubble errors to fail" do
|
444
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
445
|
+
|
446
|
+
VCR.use_cassette('invalid/templates_add') do
|
447
|
+
expect { @stager.templates_add("/path/to/template") }.to raise_error
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
context "templates_remove" do
|
453
|
+
it "should remove from its list of templates" do
|
454
|
+
VCR.use_cassette('templates_remove') do
|
455
|
+
@stager.templates_remove("/path/to/template")
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
it "should remove from its list of templates with delimiters" do
|
460
|
+
VCR.use_cassette('templates_remove') do
|
461
|
+
@stager.templates_remove("/path/to/template", "{{", "}}")
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
it "should bubble errors to fail" do
|
466
|
+
@stager.should_receive(:exit0r).with(1) { raise }
|
467
|
+
|
468
|
+
VCR.use_cassette('invalid/templates_remove') do
|
469
|
+
expect { @stager.templates_remove("/path/to/template") }.to raise_error
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
context "exit0r" do
|
475
|
+
before do
|
476
|
+
@stager.unstub(:exit0r)
|
477
|
+
end
|
478
|
+
|
479
|
+
it "should wrap successful exit" do
|
480
|
+
begin
|
481
|
+
@stager.exit0r(0)
|
482
|
+
rescue SystemExit => e
|
483
|
+
e.status.should == 0
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
it "should wrap errored exit" do
|
488
|
+
begin
|
489
|
+
@stager.exit0r(1)
|
490
|
+
rescue SystemExit => e
|
491
|
+
e.status.should == 1
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|