sippy_cup 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,103 +1,457 @@
1
- require 'sippy_cup/runner'
1
+ require 'spec_helper'
2
2
 
3
3
  describe SippyCup::Runner do
4
+ before do
5
+ Dir.chdir "/tmp"
6
+ end
7
+
8
+ let(:settings) { {} }
9
+ let(:default_settings) { { logger: logger } }
10
+ let(:command) { "sudo sipp -i 127.0.0.1" }
11
+ let(:pid) { '1234' }
12
+
13
+ let(:logger) { double }
14
+
15
+ before { logger.stub :info }
16
+
17
+ let(:manifest) do
18
+ <<-MANIFEST
19
+ name: foobar
20
+ source: 'doo@dah.com'
21
+ destination: 'foo@bar.com'
22
+ max_concurrent: 5
23
+ calls_per_second: 2
24
+ number_of_calls: 10
25
+ steps:
26
+ - invite
27
+ - wait_for_answer
28
+ - ack_answer
29
+ - sleep 3
30
+ - send_digits 'abc'
31
+ - sleep 5
32
+ - send_digits '#'
33
+ - wait_for_hangup
34
+ MANIFEST
35
+ end
36
+ let(:scenario) { SippyCup::Scenario.from_manifest manifest }
37
+
38
+ subject { SippyCup::Runner.new scenario, default_settings.merge(settings) }
39
+
40
+ def expect_command_execution(command = anything)
41
+ Process.stub :wait2
42
+ subject.stub :process_exit_status
43
+
44
+ subject.should_receive(:spawn).with(command, anything)
45
+ end
46
+
4
47
  describe '#run' do
48
+ it "executes the correct command to invoke SIPp" do
49
+ full_scenario_path = File.join(Dir.tmpdir, '/scenario.*')
50
+ expect_command_execution %r{sudo sipp -i doo@dah.com -p 8836 -sf #{full_scenario_path} -l 5 -m 10 -r 2 -s 1 foo@bar.com}
51
+ subject.run
52
+ end
53
+
54
+ it "ensures that input files are not left on the filesystem" do
55
+ FakeFS do
56
+ Dir.mkdir("/tmp") unless Dir.exist?("/tmp")
57
+ expect_command_execution.and_raise
58
+ expect { subject.run }.to raise_error
59
+ Dir.entries(Dir.tmpdir).should eql(['.', '..'])
60
+ end
61
+ end
62
+
5
63
  context "System call fails/doesn't fail" do
6
- let(:settings) { Hash.new }
7
- let(:command) { "sudo sipp -i 127.0.0.1" }
8
- let(:pid) { '1234' }
9
-
10
- subject { SippyCup::Runner.new settings }
11
- it 'should raise an error when the system call fails' do
12
- subject.logger.stub :info
13
- subject.should_receive(:prepare_command).and_return command
14
- subject.should_receive(:spawn).with(command).and_raise(Errno::ENOENT)
15
- Process.stub :wait
16
- lambda {subject.run}.should raise_error RuntimeError
17
- end
18
-
19
- it 'should not raise an error when the system call is successful' do
20
- subject.logger.stub :info
21
- subject.should_receive(:prepare_command).and_return command
22
- subject.should_receive(:spawn).with(command).and_return pid
23
- Process.stub :wait
24
- lambda {subject.run}.should_not raise_error
25
- end
26
- end
27
-
28
- context "specifying a stats file" do
29
- let(:settings) { { stats_file: 'stats.csv' } }
30
- let(:command) { "sudo sipp -i 127.0.0.1 -trace_stats -stf stats.csv" }
31
- let(:pid) { '1234' }
32
-
33
- subject { SippyCup::Runner.new settings }
34
- it 'should display the path to the csv file when one is specified' do
35
- subject.logger.should_receive(:info).twice
36
- subject.should_receive(:prepare_command).and_return command
37
- subject.should_receive(:spawn).with(command).and_return pid
38
- Process.stub :wait
39
- subject.logger.should_receive(:info).with "Statistics logged at #{File.expand_path settings[:stats_file]}"
64
+ it 'raises an error when the system call fails' do
65
+ expect_command_execution.and_raise(Errno::ENOENT)
66
+ expect { subject.run }.to raise_error Errno::ENOENT
67
+ end
68
+
69
+ it 'does not raise an error when the system call is successful' do
70
+ expect_command_execution
71
+ expect { subject.run }.not_to raise_error
72
+ end
73
+ end
74
+
75
+ context "specifying a source port in the manifest" do
76
+ let(:manifest) do
77
+ <<-MANIFEST
78
+ name: foobar
79
+ source: 'doo@dah.com'
80
+ destination: 'foo@bar.com'
81
+ max_concurrent: 5
82
+ calls_per_second: 2
83
+ number_of_calls: 10
84
+ source_port: 1234
85
+ steps:
86
+ - invite
87
+ - wait_for_answer
88
+ - ack_answer
89
+ - sleep 3
90
+ - send_digits 'abc'
91
+ - sleep 5
92
+ - send_digits '#'
93
+ - wait_for_hangup
94
+ MANIFEST
95
+ end
96
+
97
+ it 'should set the -p option' do
98
+ expect_command_execution(/-p 1234/)
99
+ subject.run
100
+ end
101
+ end
102
+
103
+ context "specifying a from_user in the Scenario" do
104
+ let(:manifest) do
105
+ <<-MANIFEST
106
+ name: foobar
107
+ source: 'doo@dah.com'
108
+ destination: 'foo@bar.com'
109
+ max_concurrent: 5
110
+ calls_per_second: 2
111
+ number_of_calls: 10
112
+ from_user: frank
113
+ steps:
114
+ - invite
115
+ - wait_for_answer
116
+ - ack_answer
117
+ - sleep 3
118
+ - send_digits 'abc'
119
+ - sleep 5
120
+ - send_digits '#'
121
+ - wait_for_hangup
122
+ MANIFEST
123
+ end
124
+
125
+ it 'should set the -s option' do
126
+ expect_command_execution(/-s frank/)
127
+ subject.run
128
+ end
129
+ end
130
+
131
+ context "specifying a media port" do
132
+ let(:manifest) do
133
+ <<-MANIFEST
134
+ name: foobar
135
+ source: 'doo@dah.com'
136
+ destination: 'foo@bar.com'
137
+ max_concurrent: 5
138
+ calls_per_second: 2
139
+ number_of_calls: 10
140
+ media_port: 6000
141
+ steps:
142
+ - invite
143
+ - wait_for_answer
144
+ - ack_answer
145
+ - sleep 3
146
+ - send_digits 'abc'
147
+ - sleep 5
148
+ - send_digits '#'
149
+ - wait_for_hangup
150
+ MANIFEST
151
+ end
152
+
153
+ it 'should set the -mp option' do
154
+ expect_command_execution(/-mp 6000/)
155
+ subject.run
156
+ end
157
+ end
158
+
159
+ context "specifying a stats file in the manifest" do
160
+ let(:manifest) do
161
+ <<-MANIFEST
162
+ name: foobar
163
+ source: 'doo@dah.com'
164
+ destination: 'foo@bar.com'
165
+ max_concurrent: 5
166
+ calls_per_second: 2
167
+ number_of_calls: 10
168
+ stats_file: stats.csv
169
+ steps:
170
+ - invite
171
+ - wait_for_answer
172
+ - ack_answer
173
+ - sleep 3
174
+ - send_digits 'abc'
175
+ - sleep 5
176
+ - send_digits '#'
177
+ - wait_for_hangup
178
+ MANIFEST
179
+ end
180
+
181
+ it 'should turn on -trace_stats, set the -stf option to the filename provided, and set the stats interval to 1 second' do
182
+ expect_command_execution(/-trace_stat -stf stats.csv -fd 1/)
183
+ subject.run
184
+ end
185
+
186
+ context 'with a stats interval provided' do
187
+ let(:manifest) do
188
+ <<-MANIFEST
189
+ name: foobar
190
+ source: 'doo@dah.com'
191
+ destination: 'foo@bar.com'
192
+ max_concurrent: 5
193
+ calls_per_second: 2
194
+ number_of_calls: 10
195
+ stats_file: stats.csv
196
+ stats_interval: 3
197
+ steps:
198
+ - invite
199
+ - wait_for_answer
200
+ - ack_answer
201
+ - sleep 3
202
+ - send_digits 'abc'
203
+ - sleep 5
204
+ - send_digits '#'
205
+ - wait_for_hangup
206
+ MANIFEST
207
+ end
208
+
209
+ it "passes the interval to the -fd option" do
210
+ expect_command_execution(/-fd 3/)
211
+ subject.run
212
+ end
213
+ end
214
+
215
+ it 'logs the path to the csv file' do
216
+ expect_command_execution
217
+ logger.should_receive(:info).with "Statistics logged at #{File.expand_path('stats.csv')}"
40
218
  subject.run
41
219
  end
42
220
  end
43
221
 
44
222
  context "no stats file" do
45
- let(:settings) { Hash.new }
46
- let(:command) { "sudo sipp -i 127.0.0.1" }
47
- let(:pid) { '1234' }
48
-
49
- subject { SippyCup::Runner.new settings }
50
- it 'should not display a csv file path if none is specified' do
51
- subject.logger.should_receive(:info).ordered.with(/Preparing to run SIPp command/)
52
- subject.logger.should_receive(:info).ordered.with(/Test completed successfully/)
53
- subject.should_receive(:prepare_command).and_return command
54
- subject.should_receive(:spawn).with(command).and_return pid
55
- Process.stub :wait
223
+ it 'does not log a statistics file path' do
224
+ logger.should_receive(:info).with(/Statistics logged at/).never
225
+ expect_command_execution
226
+ subject.run
227
+ end
228
+ end
229
+
230
+ context "specifying a variables file" do
231
+ let(:manifest) do
232
+ <<-MANIFEST
233
+ name: foobar
234
+ source: 'doo@dah.com'
235
+ destination: 'foo@bar.com'
236
+ max_concurrent: 5
237
+ calls_per_second: 2
238
+ number_of_calls: 10
239
+ scenario_variables: /path/to/vars.csv
240
+ steps:
241
+ - invite
242
+ - wait_for_answer
243
+ - ack_answer
244
+ - sleep 3
245
+ - send_digits 'abc'
246
+ - sleep 5
247
+ - send_digits '#'
248
+ - wait_for_hangup
249
+ MANIFEST
250
+ end
251
+
252
+ it 'uses CSV in the test run' do
253
+ logger.should_receive(:info).ordered.with(/Preparing to run SIPp command/)
254
+ logger.should_receive(:info).ordered.with(/Test completed successfully/)
255
+ expect_command_execution(%r{-inf /path/to/vars.csv})
56
256
  subject.run
57
257
  end
58
258
  end
59
259
 
60
- context "CSV file" do
61
- let(:settings) { {scenario_variables: "/path/to/csv", scenario: "/path/to/scenario", source: "127.0.0.1",
62
- destination: "127.0.0.1", max_concurrent: 5, calls_per_second: 5,
63
- number_of_calls: 5} }
64
- let(:pid) { "1234" }
260
+ context 'with a transport mode specified' do
261
+ let(:manifest) do
262
+ <<-MANIFEST
263
+ name: foobar
264
+ source: 'doo@dah.com'
265
+ destination: 'foo@bar.com'
266
+ max_concurrent: 5
267
+ calls_per_second: 2
268
+ number_of_calls: 10
269
+ transport_mode: t1
270
+ steps:
271
+ - invite
272
+ - wait_for_answer
273
+ - ack_answer
274
+ - sleep 3
275
+ - send_digits 'abc'
276
+ - sleep 5
277
+ - send_digits '#'
278
+ - wait_for_hangup
279
+ MANIFEST
280
+ end
65
281
 
66
- subject { SippyCup::Runner.new settings }
67
- it 'should use CSV into the test run' do
68
- subject.logger.should_receive(:info).ordered.with(/Preparing to run SIPp command/)
69
- subject.logger.should_receive(:info).ordered.with(/Test completed successfully/)
70
- subject.should_receive(:spawn).with(/\-inf \/path\/to\/csv/)
71
- Process.stub :wait
282
+ it "passes the transport mode to the -t option" do
283
+ expect_command_execution(/-t t1/)
72
284
  subject.run
73
285
  end
74
286
  end
75
287
 
288
+ describe 'SIPp exit status handling' do
289
+ let(:error_string) { "Some error" }
290
+ let(:exit_code) { 255 }
291
+ let(:command) { "sh -c 'echo \"#{error_string}\" 1>&2; exit #{exit_code}'" }
292
+
293
+ let(:settings) { { command: command } }
294
+
295
+ context "with normal operation" do
296
+ let(:exit_code) { 0 }
297
+
298
+ it "doesn't raise anything if SIPp returns 0" do
299
+ quietly do
300
+ subject.run.should be_true
301
+ end
302
+ end
303
+ end
304
+
305
+ context "with at least one call failure" do
306
+ let(:exit_code) { 1 }
307
+
308
+ it "returns false if SIPp returns 1" do
309
+ quietly do
310
+ logger.should_receive(:info).ordered.with(/Test completed successfully but some calls failed./)
311
+ subject.run.should be_false
312
+ end
313
+ end
314
+ end
315
+
316
+ context "with an exit from inside SIPp" do
317
+ let(:exit_code) { 97 }
318
+
319
+ it "raises a ExitOnInternalCommand error if SIPp returns 97" do
320
+ quietly do
321
+ expect { subject.run }.to raise_error SippyCup::ExitOnInternalCommand, error_string
322
+ end
323
+ end
324
+ end
325
+
326
+ context "with no calls processed" do
327
+ let(:exit_code) { 99 }
328
+
329
+ it "raises a NoCallsProcessed error if SIPp returns 99" do
330
+ quietly do
331
+ expect { subject.run }.to raise_error SippyCup::NoCallsProcessed, error_string
332
+ end
333
+ end
334
+ end
335
+
336
+ context "with a fatal error" do
337
+ let(:exit_code) { 255 }
338
+
339
+ it "raises a FatalError error if SIPp returns 255" do
340
+ quietly do
341
+ expect { subject.run }.to raise_error SippyCup::FatalError, error_string
342
+ end
343
+ end
344
+ end
345
+
346
+ context "with a socket binding fatal error" do
347
+ let(:exit_code) { 254 }
348
+
349
+ it "raises a FatalSocketBindingError error if SIPp returns 254" do
350
+ quietly do
351
+ expect { subject.run }.to raise_error SippyCup::FatalSocketBindingError, error_string
352
+ end
353
+ end
354
+ end
355
+
356
+ context "with a generic undocumented fatal error" do
357
+ let(:exit_code) { 128 }
358
+
359
+ it "raises a SippGenericError error if SIPp returns 255" do
360
+ quietly do
361
+ expect { subject.run }.to raise_error SippyCup::SippGenericError, error_string
362
+ end
363
+ end
364
+
365
+ it "raises a SippGenericError error with the appropriate message" do
366
+ quietly do
367
+ expect { subject.run }.to raise_error SippyCup::SippGenericError, error_string
368
+ end
369
+ end
370
+ end
371
+ end
372
+
373
+ describe "SIPp stdout/stderr" do
374
+ let(:output_string) { "Some output" }
375
+ let(:error_string) { "Some error" }
376
+ let(:command) { "sh -c 'echo \"#{output_string}\"' && sh -c 'echo \"#{error_string}\" 1>&2'" }
377
+
378
+ let(:settings) { { command: command } }
379
+
380
+ def active_thread_count
381
+ Thread.list.select { |t| t.status != 'aborting' }.size
382
+ end
383
+
384
+ context "by default" do
385
+ it "proxies stdout to the terminal" do
386
+ quietly do
387
+ capture(:stdout) { subject.run }.strip.should == output_string
388
+ end
389
+ end
390
+
391
+ it "proxies stderr to the terminal" do
392
+ quietly do
393
+ capture(:stderr) { subject.run }.strip.should == error_string
394
+ end
395
+ end
396
+
397
+ it "does not leak threads" do
398
+ original_thread_count = active_thread_count
399
+ quietly do
400
+ subject.run
401
+ end
402
+ sleep 0.1
403
+ active_thread_count.should == original_thread_count
404
+ end
405
+ end
406
+
407
+ context "with :full_sipp_output disabled" do
408
+ let(:settings) { { command: command, full_sipp_output: false } }
409
+
410
+ it "swallows stdout from SIPp" do
411
+ capture(:stdout) { subject.run }.should == ''
412
+ end
413
+
414
+ it "swallows stderr from SIPp" do
415
+ capture(:stderr) { subject.run }.should == ''
416
+ end
417
+
418
+ it "does not leak threads" do
419
+ quietly do
420
+ original_thread_count = active_thread_count
421
+ subject.run
422
+ sleep 0.1
423
+ active_thread_count.should == original_thread_count
424
+ end
425
+ end
426
+ end
427
+ end
76
428
  end
77
429
 
78
430
  describe '#stop' do
79
- let(:settings) { Hash.new }
80
- let(:command) { "sudo sipp -i 127.0.0.1" }
81
- let(:pid) { '1234' }
431
+ before { subject.sipp_pid = pid }
82
432
 
83
- subject { SippyCup::Runner.new settings }
84
-
85
- it "should try to kill the SIPp process if there is a PID" do
86
- subject.sipp_pid = pid
433
+ it "tries to kill the SIPp process if there is a PID" do
87
434
  Process.should_receive(:kill).with("KILL", pid)
88
435
  subject.stop
89
436
  end
90
437
 
91
- it "should not try to kill the SIPp process if there is a PID" do
92
- subject.sipp_pid = nil
93
- Process.should_receive(:kill).never
94
- subject.stop
438
+ context "if there is no PID available" do
439
+ let(:pid) { nil }
440
+
441
+ it "doesn't try to kill the SIPp process" do
442
+ Process.should_receive(:kill).never
443
+ subject.stop
444
+ end
95
445
  end
96
446
 
97
- it "should raise a RuntimeError if the PID does not exist" do
98
- subject.sipp_pid = pid
447
+ it "raises a Errno::ESRCH if the PID does not exist" do
99
448
  Process.should_receive(:kill).with("KILL", pid).and_raise(Errno::ESRCH)
100
- expect { subject.stop }.to raise_error RuntimeError
449
+ expect { subject.stop }.to raise_error Errno::ESRCH
450
+ end
451
+
452
+ it "raises a Errno::EPERM if the user has no permission to kill the process" do
453
+ Process.should_receive(:kill).with("KILL", pid).and_raise(Errno::EPERM)
454
+ expect { subject.stop }.to raise_error Errno::EPERM
101
455
  end
102
456
  end
103
457
  end