sippy_cup 0.2.3 → 0.3.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 +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
|