phene-parallel_tests 0.6.2

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.
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "/../parallel_tests/tasks")
@@ -0,0 +1,60 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{phene-parallel_tests}
8
+ s.version = "0.6.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Grosser", "Geoffrey Hichborn"]
12
+ s.date = %q{2011-08-14}
13
+ s.email = %q{grosser.michael@gmail.com}
14
+ s.executables = ["parallel_cucumber", "parallel_test", "parallel_spec"]
15
+ s.files = [
16
+ "Gemfile",
17
+ "Gemfile.lock",
18
+ "Rakefile",
19
+ "Readme.md",
20
+ "VERSION",
21
+ "bin/parallel_cucumber",
22
+ "bin/parallel_spec",
23
+ "bin/parallel_test",
24
+ "lib/parallel_cucumber.rb",
25
+ "lib/parallel_cucumber/runtime_logger.rb",
26
+ "lib/parallel_specs.rb",
27
+ "lib/parallel_specs/spec_failures_logger.rb",
28
+ "lib/parallel_specs/spec_logger_base.rb",
29
+ "lib/parallel_specs/spec_runtime_logger.rb",
30
+ "lib/parallel_specs/spec_summary_logger.rb",
31
+ "lib/parallel_tests.rb",
32
+ "lib/parallel_tests/grouper.rb",
33
+ "lib/parallel_tests/railtie.rb",
34
+ "lib/parallel_tests/tasks.rb",
35
+ "lib/tasks/parallel_tests.rake",
36
+ "parallel_tests.gemspec",
37
+ "spec/integration_spec.rb",
38
+ "spec/parallel_cucumber_spec.rb",
39
+ "spec/parallel_specs_spec.rb",
40
+ "spec/parallel_tests_spec.rb",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+ s.homepage = %q{http://github.com/grosser/parallel_tests}
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.6.2}
46
+ s.summary = %q{Run tests / specs / features in parallel}
47
+
48
+ if s.respond_to? :specification_version then
49
+ s.specification_version = 3
50
+
51
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
+ s.add_runtime_dependency(%q<parallel>, [">= 0"])
53
+ else
54
+ s.add_dependency(%q<parallel>, [">= 0"])
55
+ end
56
+ else
57
+ s.add_dependency(%q<parallel>, [">= 0"])
58
+ end
59
+ end
60
+
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'CLI' do
4
+ before do
5
+ `rm -rf #{folder}`
6
+ end
7
+
8
+ after do
9
+ `rm -rf #{folder}`
10
+ end
11
+
12
+ def folder
13
+ "/tmp/parallel_tests_tests"
14
+ end
15
+
16
+ def write(file, content)
17
+ path = "#{folder}/spec/#{file}"
18
+ `mkdir -p #{File.dirname(path)}` unless File.exist?(File.dirname(path))
19
+ File.open(path, 'w'){|f| f.write content }
20
+ path
21
+ end
22
+
23
+ def bin_folder
24
+ "#{File.expand_path(File.dirname(__FILE__))}/../bin"
25
+ end
26
+
27
+ def executable
28
+ "#{bin_folder}/parallel_test"
29
+ end
30
+
31
+ def run_specs(options={})
32
+ `cd #{folder} && #{executable} --chunk-timeout 999 -t spec -n #{options[:processes]||2} #{options[:add]} 2>&1`
33
+ end
34
+
35
+ it "runs tests in parallel" do
36
+ write 'xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
37
+ write 'xxx2_spec.rb', 'describe("it"){it("should"){puts "TEST2"}}'
38
+ result = run_specs
39
+
40
+ # test ran and gave their puts
41
+ result.should include('TEST1')
42
+ result.should include('TEST2')
43
+
44
+ # all results present
45
+ result.scan('1 example, 0 failure').size.should == 2 # 2 results
46
+ result.scan('2 examples, 0 failures').size.should == 1 # 1 summary
47
+ result.scan(/Finished in \d+\.\d+ seconds/).size.should == 2
48
+ result.scan(/Took \d+\.\d+ seconds/).size.should == 1 # parallel summary
49
+ $?.success?.should == true
50
+ end
51
+
52
+ it "fails when tests fail" do
53
+ write 'xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
54
+ write 'xxx2_spec.rb', 'describe("it"){it("should"){1.should == 2}}'
55
+ result = run_specs
56
+
57
+ result.scan('1 example, 1 failure').size.should == 1
58
+ result.scan('1 example, 0 failure').size.should == 1
59
+ result.scan('2 examples, 1 failure').size.should == 1
60
+ $?.success?.should == false
61
+ end
62
+
63
+ it "can exec given commands with ENV['TEST_ENV_NUM']" do
64
+ result = `#{executable} -e 'ruby -e "print ENV[:TEST_ENV_NUMBER.to_s].to_i"' -n 4`
65
+ result.gsub('"','').split('').sort.should == %w[0 2 3 4]
66
+ end
67
+
68
+ it "can exec given command non-parallel" do
69
+ result = `#{executable} -e 'ruby -e "sleep(rand(10)/100.0); puts ENV[:TEST_ENV_NUMBER.to_s].inspect"' -n 4 --non-parallel`
70
+ result.split("\n").should == %w["" "2" "3" "4"]
71
+ end
72
+
73
+ it "exists with success if all sub-processes returned success" do
74
+ system("#{executable} -e 'cat /dev/null' -n 4").should == true
75
+ end
76
+
77
+ it "exists with failure if any sub-processes returned failure" do
78
+ system("#{executable} -e 'test -e xxxx' -n 4").should == false
79
+ end
80
+
81
+ it "can run through parallel_spec / parallel_cucumber" do
82
+ version = `#{executable} -v`
83
+ `#{bin_folder}/parallel_spec -v`.should == version
84
+ `#{bin_folder}/parallel_cucumber -v`.should == version
85
+ end
86
+
87
+ it "runs faster with more processes" do
88
+ 2.times{|i|
89
+ write "xxx#{i}_spec.rb", 'describe("it"){it("should"){sleep 5}}; $stderr.puts ENV["TEST_ENV_NUMBER"]'
90
+ }
91
+ t = Time.now
92
+ puts run_specs(:processes => 2)
93
+ expected = 10
94
+ (Time.now - t).should <= expected
95
+ end
96
+
97
+ it "can can with given files" do
98
+ write "x1_spec.rb", "puts '111'"
99
+ write "x2_spec.rb", "puts '222'"
100
+ write "x3_spec.rb", "puts '333'"
101
+ result = run_specs(:add => 'spec/x1_spec.rb spec/x3_spec.rb')
102
+ result.should include('111')
103
+ result.should include('333')
104
+ result.should_not include('222')
105
+ end
106
+
107
+ it "can run with test-options" do
108
+ write "x1_spec.rb", ""
109
+ write "x2_spec.rb", ""
110
+ result = run_specs(:add => "--test-options ' --version'", :processes => 2)
111
+ result.should =~ /\d+\.\d+\.\d+.*\d+\.\d+\.\d+/m # prints version twice
112
+ end
113
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParallelCucumber do
4
+ test_tests_in_groups(ParallelCucumber, 'features', ".feature")
5
+
6
+ describe :run_tests do
7
+ before do
8
+ ParallelCucumber.stub!(:bundler_enabled?).and_return false
9
+ File.stub!(:file?).with('.bundle/environment.rb').and_return false
10
+ File.stub!(:file?).with('script/cucumber').and_return true
11
+ end
12
+
13
+ it "uses TEST_ENV_NUMBER=blank when called for process 0" do
14
+ ParallelCucumber.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER= /}.and_return mocked_process
15
+ ParallelCucumber.run_tests(['xxx'],0,{})
16
+ end
17
+
18
+ it "uses TEST_ENV_NUMBER=2 when called for process 1" do
19
+ ParallelCucumber.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER=2/}.and_return mocked_process
20
+ ParallelCucumber.run_tests(['xxx'],1,{})
21
+ end
22
+
23
+ it "returns the output" do
24
+ io = open('spec/spec_helper.rb')
25
+ ParallelCucumber.stub!(:print)
26
+ ParallelCucumber.should_receive(:open).and_return io
27
+ ParallelCucumber.run_tests(['xxx'],1,{})[:stdout].should =~ /\$LOAD_PATH << File/
28
+ end
29
+
30
+ it "runs bundle exec cucumber when on bundler 0.9" do
31
+ ParallelCucumber.stub!(:bundler_enabled?).and_return true
32
+ ParallelCucumber.should_receive(:open).with{|x,y| x =~ %r{bundle exec cucumber}}.and_return mocked_process
33
+ ParallelCucumber.run_tests(['xxx'],1,{})
34
+ end
35
+
36
+ it "runs script/cucumber when script/cucumber is found" do
37
+ ParallelCucumber.should_receive(:open).with{|x,y| x =~ %r{script/cucumber}}.and_return mocked_process
38
+ ParallelCucumber.run_tests(['xxx'],1,{})
39
+ end
40
+
41
+ it "runs cucumber by default" do
42
+ File.stub!(:file?).with('script/cucumber').and_return false
43
+ ParallelCucumber.should_receive(:open).with{|x,y| x !~ %r{(script/cucumber)|(bundle exec cucumber)}}.and_return mocked_process
44
+ ParallelCucumber.run_tests(['xxx'],1,{})
45
+ end
46
+
47
+ it "uses options passed in" do
48
+ ParallelCucumber.should_receive(:open).with{|x,y| x =~ %r{script/cucumber .* -p default}}.and_return mocked_process
49
+ ParallelCucumber.run_tests(['xxx'],1,:test_options => '-p default')
50
+ end
51
+ end
52
+
53
+ describe :find_results do
54
+ it "finds multiple results in test output" do
55
+ output = <<EOF
56
+ And I should not see "/en/" # features/step_definitions/webrat_steps.rb:87
57
+
58
+ 7 scenarios (3 failed, 4 passed)
59
+ 33 steps (3 failed, 2 skipped, 28 passed)
60
+ /apps/rs/features/signup.feature:2
61
+ Given I am on "/" # features/step_definitions/common_steps.rb:12
62
+ When I click "register" # features/step_definitions/common_steps.rb:6
63
+ And I should have "2" emails # features/step_definitions/user_steps.rb:25
64
+
65
+ 4 scenarios (4 passed)
66
+ 40 steps (40 passed)
67
+
68
+ EOF
69
+ ParallelCucumber.find_results(output).should == ["7 scenarios (3 failed, 4 passed)", "33 steps (3 failed, 2 skipped, 28 passed)", "4 scenarios (4 passed)", "40 steps (40 passed)"]
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,264 @@
1
+ require 'spec_helper'
2
+ require 'parallel_specs/spec_runtime_logger'
3
+ require 'parallel_specs/spec_summary_logger'
4
+ require 'parallel_specs/spec_failures_logger'
5
+
6
+ describe ParallelSpecs do
7
+ test_tests_in_groups(ParallelSpecs, 'spec', '_spec.rb')
8
+
9
+ describe :run_tests do
10
+ before do
11
+ File.stub!(:file?).with('script/spec').and_return false
12
+ File.stub!(:file?).with('spec/spec.opts').and_return false
13
+ File.stub!(:file?).with('spec/parallel_spec.opts').and_return false
14
+ ParallelSpecs.stub!(:bundler_enabled?).and_return false
15
+ end
16
+
17
+ it "uses TEST_ENV_NUMBER=blank when called for process 0" do
18
+ ParallelSpecs.should_receive(:open).with{|x,y|x=~/TEST_ENV_NUMBER= /}.and_return mocked_process
19
+ ParallelSpecs.run_tests(['xxx'],0,{})
20
+ end
21
+
22
+ it "uses TEST_ENV_NUMBER=2 when called for process 1" do
23
+ ParallelSpecs.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER=2/}.and_return mocked_process
24
+ ParallelSpecs.run_tests(['xxx'],1,{})
25
+ end
26
+
27
+ it "runs with color when called from cmdline" do
28
+ ParallelSpecs.should_receive(:open).with{|x,y| x=~/ --tty /}.and_return mocked_process
29
+ $stdout.should_receive(:tty?).and_return true
30
+ ParallelSpecs.run_tests(['xxx'],1,{})
31
+ end
32
+
33
+ it "runs without color when not called from cmdline" do
34
+ ParallelSpecs.should_receive(:open).with{|x,y| x !~ / --tty /}.and_return mocked_process
35
+ $stdout.should_receive(:tty?).and_return false
36
+ ParallelSpecs.run_tests(['xxx'],1,{})
37
+ end
38
+
39
+ it "runs with color for rspec 1 when called for the cmdline" do
40
+ File.should_receive(:file?).with('script/spec').and_return true
41
+ ParallelSpecs.should_receive(:open).with{|x,y| x=~/ RSPEC_COLOR=1 /}.and_return mocked_process
42
+ $stdout.should_receive(:tty?).and_return true
43
+ ParallelSpecs.run_tests(['xxx'],1,{})
44
+ end
45
+
46
+ it "runs without color for rspec 1 when not called for the cmdline" do
47
+ File.should_receive(:file?).with('script/spec').and_return true
48
+ ParallelSpecs.should_receive(:open).with{|x,y| x !~ / RSPEC_COLOR=1 /}.and_return mocked_process
49
+ $stdout.should_receive(:tty?).and_return false
50
+ ParallelSpecs.run_tests(['xxx'],1,{})
51
+ end
52
+
53
+ it "run bundle exec spec when on bundler rspec 1" do
54
+ File.stub!(:file?).with('script/spec').and_return false
55
+ ParallelSpecs.stub!(:bundler_enabled?).and_return true
56
+ ParallelSpecs.stub!(:run).with("bundle show rspec").and_return "/foo/bar/rspec-1.0.2"
57
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{bundle exec spec}}.and_return mocked_process
58
+ ParallelSpecs.run_tests(['xxx'],1,{})
59
+ end
60
+
61
+ it "run bundle exec rspec when on bundler rspec 2" do
62
+ File.stub!(:file?).with('script/spec').and_return false
63
+ ParallelSpecs.stub!(:bundler_enabled?).and_return true
64
+ ParallelSpecs.stub!(:run).with("bundle show rspec").and_return "/foo/bar/rspec-2.0.2"
65
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{bundle exec rspec}}.and_return mocked_process
66
+ ParallelSpecs.run_tests(['xxx'],1,{})
67
+ end
68
+
69
+ it "runs script/spec when script/spec can be found" do
70
+ File.should_receive(:file?).with('script/spec').and_return true
71
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec}}.and_return mocked_process
72
+ ParallelSpecs.run_tests(['xxx'],1,{})
73
+ end
74
+
75
+ it "runs spec when script/spec cannot be found" do
76
+ File.stub!(:file?).with('script/spec').and_return false
77
+ ParallelSpecs.should_receive(:open).with{|x,y| x !~ %r{script/spec}}.and_return mocked_process
78
+ ParallelSpecs.run_tests(['xxx'],1,{})
79
+ end
80
+
81
+ it "uses no -O when no opts where found" do
82
+ File.stub!(:file?).with('spec/spec.opts').and_return false
83
+ ParallelSpecs.should_receive(:open).with{|x,y| x !~ %r{spec/spec.opts}}.and_return mocked_process
84
+ ParallelSpecs.run_tests(['xxx'],1,{})
85
+ end
86
+
87
+ it "uses -O spec/spec.opts when found (with script/spec)" do
88
+ File.stub!(:file?).with('script/spec').and_return true
89
+ File.stub!(:file?).with('spec/spec.opts').and_return true
90
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec\s+ -O spec/spec.opts}}.and_return mocked_process
91
+ ParallelSpecs.run_tests(['xxx'],1,{})
92
+ end
93
+
94
+ it "uses -O spec/parallel_spec.opts when found (with script/spec)" do
95
+ File.stub!(:file?).with('script/spec').and_return true
96
+ File.should_receive(:file?).with('spec/parallel_spec.opts').and_return true
97
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec\s+ -O spec/parallel_spec.opts}}.and_return mocked_process
98
+ ParallelSpecs.run_tests(['xxx'],1,{})
99
+ end
100
+
101
+ it "uses -O spec/parallel_spec.opts with rspec1" do
102
+ File.should_receive(:file?).with('spec/parallel_spec.opts').and_return true
103
+
104
+ ParallelSpecs.stub!(:bundler_enabled?).and_return true
105
+ ParallelSpecs.stub!(:run).with("bundle show rspec").and_return "/foo/bar/rspec-1.0.2"
106
+
107
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{spec\s+ -O spec/parallel_spec.opts}}.and_return mocked_process
108
+ ParallelSpecs.run_tests(['xxx'],1,{})
109
+ end
110
+
111
+ it "uses -O spec/parallel_spec.opts with rspec2" do
112
+ File.should_receive(:file?).with('spec/parallel_spec.opts').and_return true
113
+
114
+ ParallelSpecs.stub!(:bundler_enabled?).and_return true
115
+ ParallelSpecs.stub!(:run).with("bundle show rspec").and_return "/foo/bar/rspec-2.4.2"
116
+
117
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{rspec\s+ --color --tty -O spec/parallel_spec.opts}}.and_return mocked_process
118
+ ParallelSpecs.run_tests(['xxx'],1,{})
119
+ end
120
+
121
+ it "uses options passed in" do
122
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{rspec -f n}}.and_return mocked_process
123
+ ParallelSpecs.run_tests(['xxx'],1, :test_options => '-f n')
124
+ end
125
+
126
+ it "returns the output" do
127
+ io = open('spec/spec_helper.rb')
128
+ ParallelSpecs.stub!(:print)
129
+ ParallelSpecs.should_receive(:open).and_return io
130
+ ParallelSpecs.run_tests(['xxx'],1,{})[:stdout].should =~ /\$LOAD_PATH << File/
131
+ end
132
+ end
133
+
134
+ describe :find_results do
135
+ it "finds multiple results in spec output" do
136
+ output = <<EOF
137
+ ....F...
138
+ ..
139
+ failute fsddsfsd
140
+ ...
141
+ ff.**..
142
+ 0 examples, 0 failures, 0 pending
143
+ ff.**..
144
+ 1 example, 1 failure, 1 pending
145
+ EOF
146
+
147
+ ParallelSpecs.find_results(output).should == ['0 examples, 0 failures, 0 pending','1 example, 1 failure, 1 pending']
148
+ end
149
+
150
+ it "is robust against scrambeled output" do
151
+ output = <<EOF
152
+ ....F...
153
+ ..
154
+ failute fsddsfsd
155
+ ...
156
+ ff.**..
157
+ 0 exFampl*es, 0 failures, 0 pend.ing
158
+ ff.**..
159
+ 1 exampF.les, 1 failures, 1 pend.ing
160
+ EOF
161
+
162
+ ParallelSpecs.find_results(output).should == ['0 examples, 0 failures, 0 pending','1 examples, 1 failures, 1 pending']
163
+ end
164
+ end
165
+
166
+ context "logging" do
167
+
168
+ OutputLogger = Struct.new(:output) do
169
+ attr_reader :flock, :flush
170
+ def puts(s)
171
+ self.output << s
172
+ end
173
+ end
174
+
175
+ before :each do
176
+ @output = OutputLogger.new([])
177
+ @example1 = mock( 'example', :location => '/my/spec/path/to/example:123', :description => 'should do stuff' )
178
+ @example2 = mock( 'example', :location => '/my/spec/path/to/example2:456', :description => 'should do other stuff' )
179
+ @exception1 = mock( :to_s => 'exception', :backtrace => [ '/path/to/error/line:33' ] )
180
+ @failure1 = mock( 'example', :location => '/path/to/example:123', :header => 'header', :exception => @exception1 )
181
+ end
182
+
183
+ describe ParallelSpecs::SpecRuntimeLogger do
184
+ before :each do
185
+ ENV['TEST_ENV_NUMBER'] = '1'
186
+ @logger = ParallelSpecs::SpecRuntimeLogger.new( @output )
187
+ end
188
+
189
+ it "collects runtime information" do
190
+ @logger.example_started
191
+ @logger.example_passed( @example1 )
192
+
193
+ @logger.start_dump
194
+
195
+ @output.output.size.should == 1
196
+ @output.output[0].size.should == 1
197
+ @output.output[0][0].should =~ %r(/path/to/example:([\d\.e\-]+))
198
+ end
199
+ end
200
+
201
+ describe ParallelSpecs::SpecSummaryLogger do
202
+ before :each do
203
+ @logger = ParallelSpecs::SpecSummaryLogger.new( @output )
204
+ end
205
+
206
+ it "should print a summary of failing examples" do
207
+ @logger.example_failed( nil, nil, @failure1 )
208
+
209
+ @logger.dump_failure
210
+
211
+ @output.output.should == ["1 examples failed:", "1)", "header", "exception", "/path/to/error/line:33", ""]
212
+ end
213
+ end
214
+
215
+ describe ParallelSpecs::SpecFailuresLogger do
216
+ before :each do
217
+ @logger = ParallelSpecs::SpecFailuresLogger.new( @output )
218
+ end
219
+
220
+ it "should produce a list of command lines for failing examples" do
221
+ @logger.example_failed( @example1, nil, nil )
222
+ @logger.example_failed( @example2, nil, nil )
223
+
224
+ @logger.dump_failure
225
+
226
+ @output.output.size.should == 2
227
+ @output.output[0].should =~ /r?spec .*? -e "should do stuff"/
228
+ @output.output[1].should =~ /r?spec .*? -e "should do other stuff"/
229
+ end
230
+
231
+ it "should invoke spec for rspec 1" do
232
+ ParallelSpecs.stub!(:bundler_enabled?).and_return true
233
+ ParallelSpecs.stub!(:run).with("bundle show rspec").and_return "/foo/bar/rspec-1.0.2"
234
+ @logger.example_failed( @example1, nil, nil )
235
+
236
+ @logger.dump_failure
237
+
238
+ @output.output[0].should =~ /^bundle exec spec/
239
+ end
240
+
241
+ it "should invoke rspec for rspec 2" do
242
+ ParallelSpecs.stub!(:bundler_enabled?).and_return true
243
+ ParallelSpecs.stub!(:run).with("bundle show rspec").and_return "/foo/bar/rspec-2.0.2"
244
+ @logger.example_failed( @example1, nil, nil )
245
+
246
+ @logger.dump_failure
247
+
248
+ @output.output[0].should =~ /^bundle exec rspec/
249
+ end
250
+
251
+ it "should return relative paths" do
252
+ @logger.example_failed( @example1, nil, nil )
253
+ @logger.example_failed( @example2, nil, nil )
254
+
255
+ @logger.dump_failure
256
+
257
+ @output.output[0].should =~ %r(\./spec/path/to/example)
258
+ @output.output[1].should =~ %r(\./spec/path/to/example2)
259
+ end
260
+
261
+ end
262
+
263
+ end
264
+ end