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.
- checksums.yaml +9 -9
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +2 -0
- data/Guardfile +4 -9
- data/README.markdown +28 -28
- data/Rakefile +2 -3
- data/bin/sippy_cup +16 -26
- data/lib/sippy_cup/media.rb +3 -1
- data/lib/sippy_cup/runner.rb +148 -50
- data/lib/sippy_cup/scenario.rb +436 -206
- data/lib/sippy_cup/tasks.rb +3 -3
- data/lib/sippy_cup/version.rb +1 -1
- data/lib/sippy_cup/xml_scenario.rb +57 -0
- data/lib/sippy_cup.rb +1 -0
- data/sippy_cup.gemspec +2 -1
- data/spec/fixtures/dtmf_2833_1.pcap +0 -0
- data/spec/fixtures/scenario.xml +73 -0
- data/spec/sippy_cup/fixtures/test.yml +16 -0
- data/spec/sippy_cup/runner_spec.rb +425 -71
- data/spec/sippy_cup/scenario_spec.rb +820 -71
- data/spec/sippy_cup/xml_scenario_spec.rb +103 -0
- data/spec/spec_helper.rb +5 -2
- metadata +30 -5
- data/tmp/rspec_guard_result +0 -1
@@ -1,103 +1,457 @@
|
|
1
|
-
require '
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
it '
|
12
|
-
|
13
|
-
subject.
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
61
|
-
let(:
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
67
|
-
|
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
|
-
|
80
|
-
let(:command) { "sudo sipp -i 127.0.0.1" }
|
81
|
-
let(:pid) { '1234' }
|
431
|
+
before { subject.sipp_pid = pid }
|
82
432
|
|
83
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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 "
|
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
|
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
|