chef-rewind 0.0.5 → 0.0.6
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.
- data/CHANGELOG +17 -0
- data/Gemfile +42 -2
- data/README.md +2 -0
- data/chef-rewind.gemspec +1 -1
- data/lib/chef/rewind.rb +7 -0
- data/spec/data/cookbooks/angrybash/recipes/default.rb +8 -0
- data/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl +2 -0
- data/spec/data/cookbooks/apache2/recipes/default.rb +3 -0
- data/spec/data/cookbooks/borken/recipes/default.rb +2 -0
- data/spec/data/cookbooks/borken/templates/default/borken.erb +2 -0
- data/spec/data/cookbooks/chefignore +6 -0
- data/spec/data/cookbooks/java/files/default/java.response +2 -0
- data/spec/data/cookbooks/openldap/attributes/default.rb +16 -0
- data/spec/data/cookbooks/openldap/attributes/smokey.rb +1 -0
- data/spec/data/cookbooks/openldap/definitions/client.rb +5 -0
- data/spec/data/cookbooks/openldap/definitions/server.rb +5 -0
- data/spec/data/cookbooks/openldap/files/default/.dotfile +1 -0
- data/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa +1 -0
- data/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir +1 -0
- data/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt +3 -0
- data/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt +3 -0
- data/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile +1 -0
- data/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt +3 -0
- data/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt +3 -0
- data/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt +3 -0
- data/spec/data/cookbooks/openldap/recipes/default.rb +3 -0
- data/spec/data/cookbooks/openldap/recipes/gigantor.rb +3 -0
- data/spec/data/cookbooks/openldap/recipes/one.rb +15 -0
- data/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb +1 -0
- data/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb +1 -0
- data/spec/data/cookbooks/openldap/templates/default/test.erb +1 -0
- data/spec/rewind_recipe_spec.rb +62 -0
- data/spec/rewind_resource_spec.rb +34 -0
- data/spec/spec_helper.rb +81 -0
- data/spec/support/chef_helpers.rb +52 -0
- data/spec/support/lib/chef/provider/easy.rb +35 -0
- data/spec/support/lib/chef/provider/snakeoil.rb +40 -0
- data/spec/support/lib/chef/resource/cat.rb +41 -0
- data/spec/support/lib/chef/resource/one_two_three_four.rb +43 -0
- data/spec/support/lib/chef/resource/zen_master.rb +46 -0
- data/spec/support/matchers/leak.rb +96 -0
- data/spec/support/mock/constant.rb +52 -0
- data/spec/support/mock/platform.rb +18 -0
- data/spec/support/platform_helpers.rb +31 -0
- data/spec/support/platforms/prof/gc.rb +54 -0
- data/spec/support/platforms/prof/win32.rb +46 -0
- data/spec/support/shared/functional/directory_resource.rb +85 -0
- data/spec/support/shared/functional/file_resource.rb +173 -0
- data/spec/support/shared/functional/knife.rb +37 -0
- data/spec/support/shared/functional/securable_resource.rb +394 -0
- data/spec/support/shared/unit/api_error_inspector.rb +192 -0
- data/spec/support/shared/unit/platform_introspector.rb +162 -0
- metadata +99 -4
@@ -0,0 +1,35 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2008 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
class Chef
|
20
|
+
class Provider
|
21
|
+
class Easy < Chef::Provider
|
22
|
+
def load_current_resource
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def action_sell
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def action_buy
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2008 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
class Chef
|
20
|
+
class Provider
|
21
|
+
class SnakeOil < Chef::Provider
|
22
|
+
def load_current_resource
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def action_purr
|
27
|
+
@new_resource.updated_by_last_action(true)
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def action_sell
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def action_buy
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2008 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
class Chef
|
20
|
+
class Resource
|
21
|
+
class Cat < Chef::Resource
|
22
|
+
|
23
|
+
attr_accessor :action
|
24
|
+
|
25
|
+
def initialize(name, run_context=nil)
|
26
|
+
@resource_name = :cat
|
27
|
+
super
|
28
|
+
@action = "sell"
|
29
|
+
end
|
30
|
+
|
31
|
+
def pretty_kitty(arg=nil)
|
32
|
+
set_if_args(@pretty_kitty, arg) do
|
33
|
+
case arg
|
34
|
+
when true, false
|
35
|
+
@pretty_kitty = arg
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#
|
2
|
+
# Author:: John Hampton (<john@cleanoffer.com>)
|
3
|
+
# Copyright:: Copyright (c) 2009 CleanOffer, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
class Chef
|
20
|
+
class Resource
|
21
|
+
class OneTwoThreeFour < Chef::Resource
|
22
|
+
attr_reader :i_can_count
|
23
|
+
|
24
|
+
def initialize(name, run_context)
|
25
|
+
@resource_name = :one_two_three_four
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def i_can_count(tf)
|
30
|
+
@i_can_count = tf
|
31
|
+
end
|
32
|
+
|
33
|
+
def something(arg=nil)
|
34
|
+
set_if_args(@something, arg) do
|
35
|
+
case arg
|
36
|
+
when true, false
|
37
|
+
@something = arg
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/knife'
|
20
|
+
require 'chef/json_compat'
|
21
|
+
|
22
|
+
class Chef
|
23
|
+
class Resource
|
24
|
+
class ZenMaster < Chef::Resource
|
25
|
+
attr_reader :peace
|
26
|
+
|
27
|
+
def initialize(name, run_context=nil)
|
28
|
+
@resource_name = :zen_master
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def peace(tf)
|
33
|
+
@peace = tf
|
34
|
+
end
|
35
|
+
|
36
|
+
def something(arg=nil)
|
37
|
+
set_if_args(@something, arg) do
|
38
|
+
case arg
|
39
|
+
when true, false
|
40
|
+
@something = arg
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2011 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module Matchers
|
20
|
+
module LeakBase
|
21
|
+
include RSpec::Matchers
|
22
|
+
|
23
|
+
def initialize(opts={}, &block)
|
24
|
+
@warmup = opts[:warmup] || 5
|
25
|
+
@iterations = opts[:iterations] || 100
|
26
|
+
@variance = opts[:variance] || 1000
|
27
|
+
end
|
28
|
+
|
29
|
+
def failure_message_for_should
|
30
|
+
"expected final measure [#{@final_measure}] to be greater than or within +/- #{@variance} delta of initial measure [#{@initial_measure}]"
|
31
|
+
end
|
32
|
+
|
33
|
+
def failure_message_for_should_not
|
34
|
+
"expected final measure [#{@final_measure}] to be less than or within +/- #{@variance} delta of initial measure [#{@initial_measure}]"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def match(measure, given_proc)
|
39
|
+
profiler.start
|
40
|
+
|
41
|
+
@initial_measure = 0
|
42
|
+
@final_measure = 0
|
43
|
+
|
44
|
+
@warmup.times do
|
45
|
+
given_proc.call
|
46
|
+
end
|
47
|
+
|
48
|
+
@initial_measure = profiler.send(measure)
|
49
|
+
|
50
|
+
@iterations.times do
|
51
|
+
given_proc.call
|
52
|
+
end
|
53
|
+
|
54
|
+
profiler.stop
|
55
|
+
|
56
|
+
@final_measure = profiler.send(measure)
|
57
|
+
@final_measure > (@initial_measure + @variance)
|
58
|
+
end
|
59
|
+
|
60
|
+
def profiler
|
61
|
+
@profiler ||= begin
|
62
|
+
if Chef::Platform.windows?
|
63
|
+
require File.join(File.dirname(__FILE__), '..', 'prof', 'win32')
|
64
|
+
RSpec::Prof::Win32::Profiler.new
|
65
|
+
else
|
66
|
+
require File.join(File.dirname(__FILE__), '..', 'prof', 'gc')
|
67
|
+
RSpec::Prof::GC::Profiler.new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
class LeakMemory
|
75
|
+
include LeakBase
|
76
|
+
|
77
|
+
def matches?(given_proc)
|
78
|
+
match(:working_set_size, given_proc)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class LeakHandles
|
83
|
+
include LeakBase
|
84
|
+
|
85
|
+
def matches?(given_proc)
|
86
|
+
match(:handle_count, given_proc)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def leak_memory(opts, &block)
|
91
|
+
Matchers::LeakMemory.new(opts, &block)
|
92
|
+
end
|
93
|
+
def leak_handles(opts, &block)
|
94
|
+
Matchers::LeakHandles.new(opts, &block)
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Allows easy mocking of global and class constants
|
2
|
+
|
3
|
+
# Inspired by:
|
4
|
+
# http://missingbit.blogspot.com/2011/07/stubbing-constants-in-rspec_20.html
|
5
|
+
# http://digitaldumptruck.jotabout.com/?p=551
|
6
|
+
|
7
|
+
def mock_constants(constants, &block)
|
8
|
+
saved_constants = {}
|
9
|
+
constants.each do |constant, val|
|
10
|
+
source_object, const_name = parse_constant(constant)
|
11
|
+
saved_constants[constant] = source_object.const_get(const_name)
|
12
|
+
with_warnings(nil) {source_object.const_set(const_name, val) }
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
block.call
|
17
|
+
ensure
|
18
|
+
constants.each do |constant, val|
|
19
|
+
source_object, const_name = parse_constant(constant)
|
20
|
+
with_warnings(nil) { source_object.const_set(const_name, saved_constants[constant]) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_constant(constant)
|
26
|
+
source, _, constant_name = constant.to_s.rpartition('::')
|
27
|
+
[constantize(source), constant_name]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Taken from ActiveSupport
|
31
|
+
|
32
|
+
# File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 3
|
33
|
+
#
|
34
|
+
# Sets $VERBOSE for the duration of the block and back to its original value afterwards.
|
35
|
+
def with_warnings(flag)
|
36
|
+
old_verbose, $VERBOSE = $VERBOSE, flag
|
37
|
+
yield
|
38
|
+
ensure
|
39
|
+
$VERBOSE = old_verbose
|
40
|
+
end
|
41
|
+
|
42
|
+
# File activesupport/lib/active_support/inflector/methods.rb, line 209
|
43
|
+
def constantize(camel_cased_word)
|
44
|
+
names = camel_cased_word.split('::')
|
45
|
+
names.shift if names.empty? || names.first.empty?
|
46
|
+
|
47
|
+
constant = Object
|
48
|
+
names.each do |name|
|
49
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
50
|
+
end
|
51
|
+
constant
|
52
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# makes Chef think it's running on a certain platform..useful for unit testing
|
2
|
+
# platform-specific functionality.
|
3
|
+
#
|
4
|
+
# If a block is given yields to the block with +RUBY_PLATFORM+ set to
|
5
|
+
# 'i386-mingw32' (windows) or 'x86_64-darwin11.2.0' (unix). Usueful for
|
6
|
+
# testing code that mixes in platform specific modules like +Chef::Mixin::Securable+
|
7
|
+
# or +Chef::FileAccessControl+
|
8
|
+
def platform_mock(platform = :unix, &block)
|
9
|
+
Chef::Platform.stub!(:windows?).and_return(platform == :windows ? true : false)
|
10
|
+
ENV['SYSTEMDRIVE'] = (platform == :windows ? 'C:' : nil)
|
11
|
+
if block_given?
|
12
|
+
mock_constants({"RUBY_PLATFORM" => (platform == :windows ? 'i386-mingw32' : 'x86_64-darwin11.2.0'),
|
13
|
+
"File::PATH_SEPARATOR" => (platform == :windows ? ";" : ":"),
|
14
|
+
"File::ALT_SEPARATOR" => (platform == :windows ? "\\" : nil) }) do
|
15
|
+
yield
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
def ruby_19?
|
2
|
+
!!(RUBY_VERSION =~ /^1.9/)
|
3
|
+
end
|
4
|
+
|
5
|
+
def ruby_18?
|
6
|
+
!!(RUBY_VERSION =~ /^1.8/)
|
7
|
+
end
|
8
|
+
|
9
|
+
def windows?
|
10
|
+
!!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
|
11
|
+
end
|
12
|
+
|
13
|
+
# def jruby?
|
14
|
+
|
15
|
+
def unix?
|
16
|
+
!windows?
|
17
|
+
end
|
18
|
+
|
19
|
+
def os_x?
|
20
|
+
!!(RUBY_PLATFORM =~ /darwin/)
|
21
|
+
end
|
22
|
+
|
23
|
+
def solaris?
|
24
|
+
!!(RUBY_PLATFORM =~ /solaris/)
|
25
|
+
end
|
26
|
+
|
27
|
+
def freebsd?
|
28
|
+
!!(RUBY_PLATFORM =~ /freebsd/)
|
29
|
+
end
|
30
|
+
|
31
|
+
DEV_NULL = windows? ? 'NUL' : '/dev/null'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2011 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module RSpec
|
20
|
+
module Prof
|
21
|
+
module GC
|
22
|
+
class Profiler
|
23
|
+
|
24
|
+
# GC 1 invokes.
|
25
|
+
# Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms)
|
26
|
+
# 1 0.012 159240 212940 10647 0.00000000000001530000
|
27
|
+
LINE_PATTERN = /^\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)$/
|
28
|
+
|
29
|
+
def start
|
30
|
+
::GC::Profiler.enable unless ::GC::Profiler.enabled?
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
::GC::Profiler.disable
|
35
|
+
end
|
36
|
+
|
37
|
+
def working_set_size
|
38
|
+
begin
|
39
|
+
::GC.start
|
40
|
+
::GC::Profiler.result.scan(LINE_PATTERN)[-1][2].to_i if ::GC::Profiler.enabled?
|
41
|
+
ensure
|
42
|
+
::GC::Profiler.clear
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def handle_count
|
47
|
+
0
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|