sippy_cup 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/Guardfile +1 -1
- data/README.markdown +30 -3
- data/bin/sippy_cup +2 -1
- data/lib/sippy_cup/runner.rb +27 -8
- data/lib/sippy_cup/scenario.rb +78 -11
- data/lib/sippy_cup/version.rb +1 -1
- data/sippy_cup.gemspec +1 -0
- data/spec/sippy_cup/runner_spec.rb +62 -10
- data/spec/sippy_cup/scenario_spec.rb +71 -1
- data/tmp/rspec_guard_result +1 -0
- metadata +22 -39
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 91c172b68ed8bdeec0588dbf52f2810c3e34a6ef
|
4
|
+
data.tar.gz: f426a399e082eb65b1e6f951ca33a0c3275a78aa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 908e81ae00679f9067498bf35c9f1a8bd539a7847d910734b5df8c4a9be7cb8fee3e176ed91886f263cf6032628c2d269fe873a10dcfda3a679aa0a54bcd575f
|
7
|
+
data.tar.gz: d4cf2de02b7e39e190de47572b8b0ce81b87fca01b07c8cbaa578bad4591042f48e2154d20329fb7fdf0859cf321513b8197211b8e06d9821f7a93a7db9b3a8e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# [0.2.2](https://github.com/bklang/sippy_cup/compare/v0.2.1...v0.2.2)
|
2
|
+
* Feature: Added support for REGISTER messages
|
3
|
+
* Bugfix: Enable testing through proxies
|
4
|
+
* Several enhancements to the test runner, including ability to specify a data file and the RTCP port
|
5
|
+
|
1
6
|
# [0.2.1](https://github.com/bklang/sippy_cup/compare/v0.2.0...v0.2.1)
|
2
7
|
* Bugfix: fix the name of the compiled files
|
3
8
|
* Bugfix: Fix SIPp arg to enable trace stats
|
data/Guardfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
ENV['SKIP_RCOV'] = 'true'
|
2
2
|
|
3
3
|
group 'rspec' do
|
4
|
-
guard 'rspec', :cli => '--format documentation' do
|
4
|
+
guard 'rspec', :cli => '--format documentation', :all_on_start => true, :all_after_pass => true do
|
5
5
|
watch(%r{^spec/.+_spec\.rb$})
|
6
6
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
7
|
watch('spec/spec_helper.rb') { "spec/" }
|
data/README.markdown
CHANGED
@@ -35,6 +35,8 @@ SippyCup relies on the following to generate scenarios and the associated media
|
|
35
35
|
|
36
36
|
If you do not have Ruby 1.9.3 available (check using `ruby --version`), we recommend installing Ruby with [RVM](http://rvm.io)
|
37
37
|
|
38
|
+
### Install via gem (production)
|
39
|
+
|
38
40
|
Once Ruby is installed, install SippyCup:
|
39
41
|
|
40
42
|
```
|
@@ -43,6 +45,24 @@ gem install sippy_cup
|
|
43
45
|
|
44
46
|
Now you can start creating scenario files like in the examples below.
|
45
47
|
|
48
|
+
### Install from repository (development)
|
49
|
+
|
50
|
+
You use `bundle` command (from the "[bundler](http://bundler.io/)" package) to install from the source directly. First, clone the repository into a working directory.
|
51
|
+
|
52
|
+
Install `bundle` via gem:
|
53
|
+
|
54
|
+
```
|
55
|
+
gem install bundler --no-ri --no-rdoc
|
56
|
+
```
|
57
|
+
|
58
|
+
Then build the `sippy_cup` application with `bundle`.
|
59
|
+
|
60
|
+
```
|
61
|
+
bundle install
|
62
|
+
```
|
63
|
+
|
64
|
+
Using `bundle` will then install the gem dependencies and allow you to run `sippy_cup` from your working directory.
|
65
|
+
|
46
66
|
## Examples
|
47
67
|
|
48
68
|
### Simple Example
|
@@ -62,7 +82,7 @@ steps:
|
|
62
82
|
- send_digits '3125551234'
|
63
83
|
- sleep 5
|
64
84
|
- send_digits '#'
|
65
|
-
-
|
85
|
+
- wait_for_hangup
|
66
86
|
```
|
67
87
|
|
68
88
|
Both `source` and `destination` above may be optionally supplied with a port number, eg. `192.0.2.200:5061`
|
@@ -92,7 +112,7 @@ scenario = SippyCup::Scenario.new 'Sippy Cup', source: '192.168.5.5:10001', dest
|
|
92
112
|
s.sleep 5
|
93
113
|
s.send_digits '#'
|
94
114
|
|
95
|
-
s.
|
115
|
+
s.wait_for_hangup
|
96
116
|
end
|
97
117
|
|
98
118
|
# Create the scenario XML and PCAP media. File will be named after the scenario name, in our case:
|
@@ -118,6 +138,7 @@ Each command below can take [SIPp attributes](http://sipp.sourceforge.net/doc/re
|
|
118
138
|
|
119
139
|
* `sleep <seconds>` Wait a specified number of seconds
|
120
140
|
* `invite` Send a SIP INVITE to the specified target
|
141
|
+
* `register <username> [password]` Register the specified user to the target with an optional password
|
121
142
|
* `receive_trying` Expect to receive a `100 Trying` response from the target
|
122
143
|
* `receive_ringing` Expect to receive a `180 Ringing` response from the target
|
123
144
|
* `receive_progress` Expect to receive a `183 Progress` response from the target
|
@@ -127,7 +148,7 @@ Each command below can take [SIPp attributes](http://sipp.sourceforge.net/doc/re
|
|
127
148
|
* `send_digits <string>` Send a DTMF string. May send one or many digits, including `0-9`, `*`, `#`, and `A-D`
|
128
149
|
* `send_bye` Send a `BYE` (hangup request)
|
129
150
|
* `receive_bye` Expect to receive a `BYE` from the target
|
130
|
-
* `ack_bye` Send
|
151
|
+
* `ack_bye` Send a `200 OK` response to a `BYE`
|
131
152
|
* `wait_for_hangup` Convenient shortcut for `receive_bye; ack_bye`
|
132
153
|
|
133
154
|
### Alternate Output File Path
|
@@ -168,6 +189,12 @@ Each parameter has an impact on the test, and may either be changed once the YAM
|
|
168
189
|
|
169
190
|
<dt>full_sipp_output</dt>
|
170
191
|
<dd>By default, SippyCup will hide SIPp's command line output while running a scenario. Set this parameter to `true` to see full command line output</dd>
|
192
|
+
|
193
|
+
<dt>rtcp_port</dt>
|
194
|
+
<dd>By default, SIPp assigns RTCP ports dynamically. However, if there is a need for a static RTCP port (say, for data collection purposes), it can be done by supplying a port number here.</dd>
|
195
|
+
|
196
|
+
<dt>scenario_variables</dt>
|
197
|
+
<dd>If you're using sippy_cup to run a SIPp XML file, there may be CSV fields in the scenario ([field0], [field1], etc.). Specify a path to a CSV file containing the required information using this option. (File is semicolon delimeted, information can be found [here](http://sipp.sourceforge.net/doc/reference.html#inffile).)</dd>
|
171
198
|
</dl>
|
172
199
|
|
173
200
|
### Additional SIPp Scenario Attributes
|
data/bin/sippy_cup
CHANGED
@@ -10,7 +10,7 @@ def usage
|
|
10
10
|
puts "--run, -r Run the scenario. If used without -c, must supply a previously"
|
11
11
|
puts " compiled SippyCup file"
|
12
12
|
puts "--help, -h Print this usage information"
|
13
|
-
puts "--version, -
|
13
|
+
puts "--version, -V Print SippyCup version"
|
14
14
|
end
|
15
15
|
|
16
16
|
opts = GetoptLong.new(
|
@@ -34,6 +34,7 @@ opts.each do |opt, arg|
|
|
34
34
|
usage
|
35
35
|
exit 0
|
36
36
|
when '--version'
|
37
|
+
require 'sippy_cup/version'
|
37
38
|
puts "SippyCup version #{SippyCup::VERSION}"
|
38
39
|
exit 0
|
39
40
|
end
|
data/lib/sippy_cup/runner.rb
CHANGED
@@ -3,13 +3,20 @@ require 'active_support/core_ext/hash'
|
|
3
3
|
|
4
4
|
module SippyCup
|
5
5
|
class Runner
|
6
|
+
attr_accessor :sipp_pid
|
7
|
+
attr_accessor :logger
|
8
|
+
|
6
9
|
def initialize(opts = {})
|
7
10
|
@options = ActiveSupport::HashWithIndifferentAccess.new opts
|
11
|
+
@logger = @options[:logger] || Logger.new(STDOUT)
|
8
12
|
end
|
9
13
|
|
10
14
|
def compile
|
11
15
|
raise ArgumentError, "Must provide scenario steps" unless @options[:steps]
|
12
|
-
|
16
|
+
|
17
|
+
scenario_opts = {source: @options[:source], destination: @options[:destination]}
|
18
|
+
scenario_opts[:filename] = @options[:filename] if @options[:filename]
|
19
|
+
scenario = SippyCup::Scenario.new @options[:name].titleize, scenario_opts
|
13
20
|
@options[:steps].each do |step|
|
14
21
|
instruction, arg = step.split ' ', 2
|
15
22
|
if arg && !arg.empty?
|
@@ -30,26 +37,38 @@ module SippyCup
|
|
30
37
|
command = "sudo sipp"
|
31
38
|
source_port = @options[:source_port] || '8836'
|
32
39
|
sip_user = @options[:sip_user] || '1'
|
33
|
-
command << " -i #{@options[:source]} -p #{source_port} -sf #{File.expand_path @options[:scenario]}"
|
40
|
+
command << " -i #{@options[:source]} -p #{source_port} -sf #{File.expand_path @options[:scenario]}.xml"
|
34
41
|
command << " -l #{@options[:max_concurrent]} -m #{@options[:number_of_calls]} -r #{@options[:calls_per_second]}"
|
35
42
|
command << " -s #{sip_user}"
|
36
43
|
if @options[:stats_file]
|
37
44
|
stats_interval = @options[:stats_interval] || 1
|
38
45
|
command << " -trace_stat -stf #{@options[:stats_file]} -fd #{stats_interval}"
|
39
46
|
end
|
40
|
-
command << "#{@options[:
|
47
|
+
command << " -inf #{@options[:scenario_variables]}" if @options[:scenario_variables]
|
48
|
+
command << " #{@options[:destination]}"
|
41
49
|
command << " > /dev/null 2>&1" unless @options[:full_sipp_output]
|
42
50
|
command
|
43
51
|
end
|
44
52
|
|
45
53
|
def run
|
46
54
|
command = prepare_command
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
55
|
+
@logger.info "Preparing to run SIPp command: #{command}"
|
56
|
+
|
57
|
+
begin
|
58
|
+
@sipp_pid = spawn command
|
59
|
+
Process.wait @sipp_pid
|
60
|
+
rescue Exception => e
|
61
|
+
raise RuntimeError, "Command #{command} failed"
|
62
|
+
end
|
63
|
+
|
64
|
+
@logger.info "Test completed successfully!"
|
65
|
+
@logger.info "Statistics logged at #{File.expand_path @options[:stats_file]}" if @options[:stats_file]
|
52
66
|
end
|
53
67
|
|
68
|
+
def stop
|
69
|
+
Process.kill "KILL", @sipp_pid if @sipp_pid
|
70
|
+
rescue Exception => e
|
71
|
+
raise RuntimeError, "Killing #{@sipp_pid} failed"
|
72
|
+
end
|
54
73
|
end
|
55
74
|
end
|
data/lib/sippy_cup/scenario.rb
CHANGED
@@ -3,6 +3,7 @@ require 'yaml'
|
|
3
3
|
|
4
4
|
module SippyCup
|
5
5
|
class Scenario
|
6
|
+
USER_AGENT = "SIPp/sippy_cup"
|
6
7
|
VALID_DTMF = %w{0 1 2 3 4 5 6 7 8 9 0 * # A B C D}.freeze
|
7
8
|
MSEC = 1_000
|
8
9
|
|
@@ -12,6 +13,7 @@ module SippyCup
|
|
12
13
|
end
|
13
14
|
|
14
15
|
parse_args args
|
16
|
+
@rtcp_port = args[:rtcp_port]
|
15
17
|
@filename = args[:filename] || name.downcase.gsub(/\W+/, '_')
|
16
18
|
@filename = File.expand_path @filename
|
17
19
|
@doc = builder.doc
|
@@ -53,18 +55,20 @@ module SippyCup
|
|
53
55
|
|
54
56
|
def invite(opts = {})
|
55
57
|
opts[:retrans] ||= 500
|
58
|
+
rtp_string = @static_rtcp ? "m=audio #{@rtcp_port.to_i - 1} RTP/AVP 0 101\na=rtcp:#{@rtcp_port}\n" : "m=audio [media_port] RTP/AVP 0 101\n"
|
56
59
|
# FIXME: The DTMF mapping (101) is hard-coded. It would be better if we could
|
57
60
|
# get this from the DTMF payload generator
|
58
61
|
msg = <<-INVITE
|
59
62
|
|
60
63
|
INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
|
61
64
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
62
|
-
From:
|
65
|
+
From: "#{@from_user}" <sip:#{@from_user}@[local_ip]>;tag=[call_number]
|
63
66
|
To: <sip:[service]@[remote_ip]:[remote_port]>
|
64
67
|
Call-ID: [call_id]
|
65
68
|
CSeq: [cseq] INVITE
|
66
|
-
Contact: sip:#{@from_user}@[local_ip]:[local_port]
|
69
|
+
Contact: <sip:#{@from_user}@[local_ip]:[local_port];transport=[transport]>
|
67
70
|
Max-Forwards: 100
|
71
|
+
User-Agent: #{USER_AGENT}
|
68
72
|
Content-Type: application/sdp
|
69
73
|
Content-Length: [len]
|
70
74
|
|
@@ -73,7 +77,7 @@ module SippyCup
|
|
73
77
|
s=-
|
74
78
|
c=IN IP[media_ip_type] [media_ip]
|
75
79
|
t=0 0
|
76
|
-
|
80
|
+
#{rtp_string}
|
77
81
|
a=rtpmap:0 PCMU/8000
|
78
82
|
a=rtpmap:101 telephone-event/8000
|
79
83
|
a=fmtp:101 0-15
|
@@ -82,6 +86,54 @@ module SippyCup
|
|
82
86
|
@scenario << send
|
83
87
|
end
|
84
88
|
|
89
|
+
def register(user, password = nil, opts = {})
|
90
|
+
opts[:retrans] ||= 500
|
91
|
+
user, domain = parse_user user
|
92
|
+
msg = register_message user, domain: domain
|
93
|
+
send = new_send msg, opts
|
94
|
+
@scenario << send
|
95
|
+
register_auth(user, password, domain: domain) if password
|
96
|
+
end
|
97
|
+
|
98
|
+
def register_message(user, opts = {})
|
99
|
+
<<-REGISTER
|
100
|
+
|
101
|
+
REGISTER sip:#{opts[:domain]} SIP/2.0
|
102
|
+
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
103
|
+
From: <sip:#{user}@#{opts[:domain]}>;tag=[call_number]
|
104
|
+
To: <sip:#{user}@#{opts[:domain]}>
|
105
|
+
Call-ID: [call_id]
|
106
|
+
CSeq: [cseq] REGISTER
|
107
|
+
Contact: <sip:#{@from_user}@[local_ip]:[local_port];transport=[transport]>
|
108
|
+
Max-Forwards: 10
|
109
|
+
Expires: 120
|
110
|
+
User-Agent: #{USER_AGENT}
|
111
|
+
Content-Length: 0
|
112
|
+
REGISTER
|
113
|
+
end
|
114
|
+
|
115
|
+
def register_auth(user, password, opts = {})
|
116
|
+
opts[:retrans] ||= 500
|
117
|
+
@scenario << new_recv(response: '401', auth: true, optional: false)
|
118
|
+
msg = <<-AUTH
|
119
|
+
|
120
|
+
REGISTER sip:#{opts[:domain]} SIP/2.0
|
121
|
+
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
122
|
+
From: <sip:#{user}@#{opts[:domain]}>;tag=[call_number]
|
123
|
+
To: <sip:#{user}@#{opts[:domain]}>
|
124
|
+
Call-ID: [call_id]
|
125
|
+
CSeq: [cseq] REGISTER
|
126
|
+
Contact: <sip:#{@from_user}@[local_ip]:[local_port];transport=[transport]>
|
127
|
+
Max-Forwards: 20
|
128
|
+
Expires: 3600
|
129
|
+
[authentication username=#{user} password=#{password}]
|
130
|
+
User-Agent: #{USER_AGENT}
|
131
|
+
Content-Length: 0
|
132
|
+
AUTH
|
133
|
+
send = new_send msg, opts
|
134
|
+
@scenario << send
|
135
|
+
end
|
136
|
+
|
85
137
|
def receive_trying(opts = {})
|
86
138
|
opts[:optional] = true if opts[:optional].nil?
|
87
139
|
opts.merge! response: 100
|
@@ -108,6 +160,8 @@ module SippyCup
|
|
108
160
|
recv = new_recv opts
|
109
161
|
# Record Record Set: Make the Route headers available via [route] later
|
110
162
|
recv['rrs'] = true
|
163
|
+
# Response Time Duration: Record the response time
|
164
|
+
recv['rtd'] = true
|
111
165
|
@scenario << recv
|
112
166
|
end
|
113
167
|
alias :receive_200 :receive_answer
|
@@ -127,14 +181,15 @@ module SippyCup
|
|
127
181
|
|
128
182
|
ACK [next_url] SIP/2.0
|
129
183
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
130
|
-
From: <sip:#{@from_user}@[local_ip]>;tag=[call_number]
|
184
|
+
From: "#{@from_user}" <sip:#{@from_user}@[local_ip]>;tag=[call_number]
|
131
185
|
[last_To:]
|
132
|
-
[routes]
|
133
186
|
Call-ID: [call_id]
|
134
187
|
CSeq: [cseq] ACK
|
135
|
-
Contact: sip:#{@from_user}@[local_ip]:[local_port]
|
188
|
+
Contact: <sip:#{@from_user}@[local_ip]:[local_port];transport=[transport]>
|
136
189
|
Max-Forwards: 100
|
190
|
+
User-Agent: #{USER_AGENT}
|
137
191
|
Content-Length: 0
|
192
|
+
[routes]
|
138
193
|
ACK
|
139
194
|
@scenario << new_send(msg, opts)
|
140
195
|
start_media
|
@@ -167,15 +222,17 @@ module SippyCup
|
|
167
222
|
def send_bye(opts = {})
|
168
223
|
msg = <<-MSG
|
169
224
|
|
170
|
-
BYE
|
225
|
+
BYE [next_url] SIP/2.0
|
171
226
|
[last_Via:]
|
172
|
-
[
|
227
|
+
From: "#{@from_user}" <sip:#{@from_user}@[local_ip]>;tag=[call_number]
|
173
228
|
[last_To:]
|
174
229
|
[last_Call-ID]
|
175
230
|
CSeq: [cseq] BYE
|
176
|
-
Contact: <sip
|
231
|
+
Contact: <sip:#{@from_user}@[local_ip]:[local_port];transport=[transport]>
|
177
232
|
Max-Forwards: 100
|
233
|
+
User-Agent: #{USER_AGENT}
|
178
234
|
Content-Length: 0
|
235
|
+
[routes]
|
179
236
|
MSG
|
180
237
|
@scenario << new_send(msg, opts)
|
181
238
|
end
|
@@ -200,12 +257,13 @@ module SippyCup
|
|
200
257
|
[last_Via:]
|
201
258
|
[last_From:]
|
202
259
|
[last_To:]
|
203
|
-
[routes]
|
204
260
|
[last_Call-ID:]
|
205
261
|
[last_CSeq:]
|
206
|
-
Contact: <sip
|
262
|
+
Contact: <sip:#{@from_user}@[local_ip]:[local_port];transport=[transport]>
|
207
263
|
Max-Forwards: 100
|
264
|
+
User-Agent: #{USER_AGENT}
|
208
265
|
Content-Length: 0
|
266
|
+
[routes]
|
209
267
|
ACK
|
210
268
|
@scenario << new_send(msg, opts)
|
211
269
|
end
|
@@ -226,6 +284,15 @@ module SippyCup
|
|
226
284
|
puts "done."
|
227
285
|
end
|
228
286
|
|
287
|
+
#TODO: SIPS support?
|
288
|
+
def parse_user(user)
|
289
|
+
user.slice! 0, 4 if user =~ /sip:/
|
290
|
+
user = user.split(":")[0]
|
291
|
+
user, domain = user.split("@")
|
292
|
+
domain ||= "[remote_ip]"
|
293
|
+
[user, domain]
|
294
|
+
end
|
295
|
+
|
229
296
|
private
|
230
297
|
def pause(msec)
|
231
298
|
pause = Nokogiri::XML::Node.new 'pause', @doc
|
data/lib/sippy_cup/version.rb
CHANGED
data/sippy_cup.gemspec
CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.homepage = "https://github.com/bklang/sippy_cup"
|
12
12
|
s.summary = "SIPp profile and RTP stream generator"
|
13
13
|
s.description = "This tool makes it easier to generate SIPp load tests with DTMF interactions."
|
14
|
+
s.license = 'MIT'
|
14
15
|
|
15
16
|
s.files = `git ls-files`.split("\n")
|
16
17
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -5,18 +5,22 @@ describe SippyCup::Runner do
|
|
5
5
|
context "System call fails/doesn't fail" do
|
6
6
|
let(:settings) { Hash.new }
|
7
7
|
let(:command) { "sudo sipp -i 127.0.0.1" }
|
8
|
+
let(:pid) { '1234' }
|
9
|
+
|
8
10
|
subject { SippyCup::Runner.new settings }
|
9
11
|
it 'should raise an error when the system call fails' do
|
10
|
-
subject.stub :
|
12
|
+
subject.logger.stub :info
|
11
13
|
subject.should_receive(:prepare_command).and_return command
|
12
|
-
subject.should_receive(:
|
14
|
+
subject.should_receive(:spawn).with(command).and_raise(Errno::ENOENT)
|
15
|
+
Process.stub :wait
|
13
16
|
lambda {subject.run}.should raise_error RuntimeError
|
14
17
|
end
|
15
18
|
|
16
19
|
it 'should not raise an error when the system call is successful' do
|
17
|
-
subject.stub :
|
20
|
+
subject.logger.stub :info
|
18
21
|
subject.should_receive(:prepare_command).and_return command
|
19
|
-
subject.should_receive(:
|
22
|
+
subject.should_receive(:spawn).with(command).and_return pid
|
23
|
+
Process.stub :wait
|
20
24
|
lambda {subject.run}.should_not raise_error
|
21
25
|
end
|
22
26
|
end
|
@@ -24,12 +28,15 @@ describe SippyCup::Runner do
|
|
24
28
|
context "specifying a stats file" do
|
25
29
|
let(:settings) { { stats_file: 'stats.csv' } }
|
26
30
|
let(:command) { "sudo sipp -i 127.0.0.1 -trace_stats -stf stats.csv" }
|
31
|
+
let(:pid) { '1234' }
|
32
|
+
|
27
33
|
subject { SippyCup::Runner.new settings }
|
28
34
|
it 'should display the path to the csv file when one is specified' do
|
29
|
-
subject.should_receive(:
|
35
|
+
subject.logger.should_receive(:info).twice
|
30
36
|
subject.should_receive(:prepare_command).and_return command
|
31
|
-
subject.should_receive(:
|
32
|
-
|
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]}"
|
33
40
|
subject.run
|
34
41
|
end
|
35
42
|
end
|
@@ -37,15 +44,60 @@ describe SippyCup::Runner do
|
|
37
44
|
context "no stats file" do
|
38
45
|
let(:settings) { Hash.new }
|
39
46
|
let(:command) { "sudo sipp -i 127.0.0.1" }
|
47
|
+
let(:pid) { '1234' }
|
48
|
+
|
40
49
|
subject { SippyCup::Runner.new settings }
|
41
50
|
it 'should not display a csv file path if none is specified' do
|
42
|
-
subject.should_receive(:
|
43
|
-
subject.should_receive(:
|
51
|
+
subject.logger.should_receive(:info).ordered.with(/Preparing to run SIPp command/)
|
52
|
+
subject.logger.should_receive(:info).ordered.with(/Test completed successfully/)
|
44
53
|
subject.should_receive(:prepare_command).and_return command
|
45
|
-
subject.should_receive(:
|
54
|
+
subject.should_receive(:spawn).with(command).and_return pid
|
55
|
+
Process.stub :wait
|
56
|
+
subject.run
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
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" }
|
65
|
+
|
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
|
46
72
|
subject.run
|
47
73
|
end
|
48
74
|
end
|
49
75
|
|
50
76
|
end
|
77
|
+
|
78
|
+
describe '#stop' do
|
79
|
+
let(:settings) { Hash.new }
|
80
|
+
let(:command) { "sudo sipp -i 127.0.0.1" }
|
81
|
+
let(:pid) { '1234' }
|
82
|
+
|
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
|
87
|
+
Process.should_receive(:kill).with("KILL", pid)
|
88
|
+
subject.stop
|
89
|
+
end
|
90
|
+
|
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
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should raise a RuntimeError if the PID does not exist" do
|
98
|
+
subject.sipp_pid = pid
|
99
|
+
Process.should_receive(:kill).with("KILL", pid).and_raise(Errno::ESRCH)
|
100
|
+
expect { subject.stop }.to raise_error RuntimeError
|
101
|
+
end
|
102
|
+
end
|
51
103
|
end
|
@@ -48,7 +48,7 @@ describe SippyCup::Scenario do
|
|
48
48
|
end
|
49
49
|
|
50
50
|
describe 'media-dependent operations' do
|
51
|
-
let(:media) {
|
51
|
+
let(:media) { double :media }
|
52
52
|
let(:scenario) do
|
53
53
|
SippyCup::Media.should_receive(:new).once.and_return media
|
54
54
|
scenario = SippyCup::Scenario.new 'Test', source: '127.0.0.1:5061', destination: '127.0.0.1:5060'
|
@@ -69,4 +69,74 @@ describe SippyCup::Scenario do
|
|
69
69
|
scenario.send_digits '136'
|
70
70
|
end
|
71
71
|
end
|
72
|
+
|
73
|
+
describe "#register" do
|
74
|
+
let(:scenario) { SippyCup::Scenario.new 'Test', source: '127.0.0.1:5061', destination: '127.0.0.1:5060' }
|
75
|
+
|
76
|
+
it %q{should only call #register_message if only user is passed} do
|
77
|
+
scenario.should_receive(:register_message).with 'foo', domain: "example.com"
|
78
|
+
scenario.should_not_receive(:register_auth)
|
79
|
+
scenario.register 'foo@example.com'
|
80
|
+
end
|
81
|
+
|
82
|
+
it %q{should call #register_auth if user and password are passed} do
|
83
|
+
scenario.should_receive(:register_auth).with 'sally', 'seekrut', domain: "[remote_ip]"
|
84
|
+
scenario.register 'sally', 'seekrut'
|
85
|
+
end
|
86
|
+
|
87
|
+
it %q{should not modify the passed in user if a domain is given} do
|
88
|
+
scenario.register 'foo@example.com'
|
89
|
+
|
90
|
+
xml = scenario.to_xml
|
91
|
+
xml.should =~ %r{foo@example\.com}
|
92
|
+
end
|
93
|
+
|
94
|
+
it %q{should interpolate the target IP if no domain is given} do
|
95
|
+
scenario.register 'sally'
|
96
|
+
|
97
|
+
xml = scenario.to_xml
|
98
|
+
xml.should =~ %r{sally@\[remote_ip\]}
|
99
|
+
end
|
100
|
+
|
101
|
+
it %q{should add an auth to registers which specify a password} do
|
102
|
+
scenario.register 'foo@example.com', 'seekrut'
|
103
|
+
|
104
|
+
xml = scenario.to_xml
|
105
|
+
xml.should =~ %r{recv response="401" optional="false" auth="true"}
|
106
|
+
xml.should =~ %r{\[authentication username=foo password=seekrut\]}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "#parse_user" do
|
111
|
+
let(:scenario) { SippyCup::Scenario.new 'Test', source: '127.0.0.1:5061', destination: '127.0.0.1:5060' }
|
112
|
+
|
113
|
+
context "sip: prefix" do
|
114
|
+
|
115
|
+
it %q{should return user and domain for addresses in the sip:user@domain:port format} do
|
116
|
+
scenario.parse_user('sip:foo@example.com:1337').should == ['foo', 'example.com']
|
117
|
+
end
|
118
|
+
|
119
|
+
it %q{should return user and domain for addresses in the sip:user@domain format} do
|
120
|
+
scenario.parse_user('sip:foo@example.com').should == ['foo', 'example.com']
|
121
|
+
end
|
122
|
+
|
123
|
+
it %q{should return user and [remote_ip] for addresses in the sip:user format} do
|
124
|
+
scenario.parse_user('sip:foo').should == ['foo', '[remote_ip]']
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context "no prefix" do
|
129
|
+
it %q{should return user and domain for addresses in the user@domain:port format} do
|
130
|
+
scenario.parse_user('foo@example.com:1337').should == ['foo', 'example.com']
|
131
|
+
end
|
132
|
+
|
133
|
+
it %q{should return user and domain for addresses in the user@domain format} do
|
134
|
+
scenario.parse_user('foo@example.com').should == ['foo', 'example.com']
|
135
|
+
end
|
136
|
+
|
137
|
+
it %q{should return user and [remote_ip] for a standalone username} do
|
138
|
+
scenario.parse_user('sally').should == ['sally', '[remote_ip]']
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
72
142
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sippy_cup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Ben Klang
|
@@ -10,28 +9,25 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date: 2013-
|
12
|
+
date: 2013-09-06 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: packetfu
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
|
-
- -
|
18
|
+
- - '>='
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: '0'
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
|
-
- -
|
25
|
+
- - '>='
|
29
26
|
- !ruby/object:Gem::Version
|
30
27
|
version: '0'
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: nokogiri
|
33
30
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
31
|
requirements:
|
36
32
|
- - ~>
|
37
33
|
- !ruby/object:Gem::Version
|
@@ -39,7 +35,6 @@ dependencies:
|
|
39
35
|
type: :runtime
|
40
36
|
prerelease: false
|
41
37
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
38
|
requirements:
|
44
39
|
- - ~>
|
45
40
|
- !ruby/object:Gem::Version
|
@@ -47,39 +42,34 @@ dependencies:
|
|
47
42
|
- !ruby/object:Gem::Dependency
|
48
43
|
name: activesupport
|
49
44
|
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
45
|
requirements:
|
52
|
-
- -
|
46
|
+
- - '>'
|
53
47
|
- !ruby/object:Gem::Version
|
54
48
|
version: '3.0'
|
55
49
|
type: :runtime
|
56
50
|
prerelease: false
|
57
51
|
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
52
|
requirements:
|
60
|
-
- -
|
53
|
+
- - '>'
|
61
54
|
- !ruby/object:Gem::Version
|
62
55
|
version: '3.0'
|
63
56
|
- !ruby/object:Gem::Dependency
|
64
57
|
name: guard-rspec
|
65
58
|
requirement: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
59
|
requirements:
|
68
|
-
- -
|
60
|
+
- - '>='
|
69
61
|
- !ruby/object:Gem::Version
|
70
62
|
version: '0'
|
71
63
|
type: :development
|
72
64
|
prerelease: false
|
73
65
|
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
66
|
requirements:
|
76
|
-
- -
|
67
|
+
- - '>='
|
77
68
|
- !ruby/object:Gem::Version
|
78
69
|
version: '0'
|
79
70
|
- !ruby/object:Gem::Dependency
|
80
71
|
name: rspec
|
81
72
|
requirement: !ruby/object:Gem::Requirement
|
82
|
-
none: false
|
83
73
|
requirements:
|
84
74
|
- - ~>
|
85
75
|
- !ruby/object:Gem::Version
|
@@ -87,7 +77,6 @@ dependencies:
|
|
87
77
|
type: :development
|
88
78
|
prerelease: false
|
89
79
|
version_requirements: !ruby/object:Gem::Requirement
|
90
|
-
none: false
|
91
80
|
requirements:
|
92
81
|
- - ~>
|
93
82
|
- !ruby/object:Gem::Version
|
@@ -95,49 +84,43 @@ dependencies:
|
|
95
84
|
- !ruby/object:Gem::Dependency
|
96
85
|
name: simplecov
|
97
86
|
requirement: !ruby/object:Gem::Requirement
|
98
|
-
none: false
|
99
87
|
requirements:
|
100
|
-
- -
|
88
|
+
- - '>='
|
101
89
|
- !ruby/object:Gem::Version
|
102
90
|
version: '0'
|
103
91
|
type: :development
|
104
92
|
prerelease: false
|
105
93
|
version_requirements: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
94
|
requirements:
|
108
|
-
- -
|
95
|
+
- - '>='
|
109
96
|
- !ruby/object:Gem::Version
|
110
97
|
version: '0'
|
111
98
|
- !ruby/object:Gem::Dependency
|
112
99
|
name: simplecov-rcov
|
113
100
|
requirement: !ruby/object:Gem::Requirement
|
114
|
-
none: false
|
115
101
|
requirements:
|
116
|
-
- -
|
102
|
+
- - '>='
|
117
103
|
- !ruby/object:Gem::Version
|
118
104
|
version: '0'
|
119
105
|
type: :development
|
120
106
|
prerelease: false
|
121
107
|
version_requirements: !ruby/object:Gem::Requirement
|
122
|
-
none: false
|
123
108
|
requirements:
|
124
|
-
- -
|
109
|
+
- - '>='
|
125
110
|
- !ruby/object:Gem::Version
|
126
111
|
version: '0'
|
127
112
|
- !ruby/object:Gem::Dependency
|
128
113
|
name: ci_reporter
|
129
114
|
requirement: !ruby/object:Gem::Requirement
|
130
|
-
none: false
|
131
115
|
requirements:
|
132
|
-
- -
|
116
|
+
- - '>='
|
133
117
|
- !ruby/object:Gem::Version
|
134
118
|
version: '0'
|
135
119
|
type: :development
|
136
120
|
prerelease: false
|
137
121
|
version_requirements: !ruby/object:Gem::Requirement
|
138
|
-
none: false
|
139
122
|
requirements:
|
140
|
-
- -
|
123
|
+
- - '>='
|
141
124
|
- !ruby/object:Gem::Version
|
142
125
|
version: '0'
|
143
126
|
description: This tool makes it easier to generate SIPp load tests with DTMF interactions.
|
@@ -174,33 +157,33 @@ files:
|
|
174
157
|
- spec/sippy_cup/runner_spec.rb
|
175
158
|
- spec/sippy_cup/scenario_spec.rb
|
176
159
|
- spec/spec_helper.rb
|
160
|
+
- tmp/rspec_guard_result
|
177
161
|
homepage: https://github.com/bklang/sippy_cup
|
178
|
-
licenses:
|
162
|
+
licenses:
|
163
|
+
- MIT
|
164
|
+
metadata: {}
|
179
165
|
post_install_message:
|
180
166
|
rdoc_options: []
|
181
167
|
require_paths:
|
182
168
|
- lib
|
183
169
|
required_ruby_version: !ruby/object:Gem::Requirement
|
184
|
-
none: false
|
185
170
|
requirements:
|
186
|
-
- -
|
171
|
+
- - '>='
|
187
172
|
- !ruby/object:Gem::Version
|
188
173
|
version: '0'
|
189
174
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
-
none: false
|
191
175
|
requirements:
|
192
|
-
- -
|
176
|
+
- - '>='
|
193
177
|
- !ruby/object:Gem::Version
|
194
178
|
version: '0'
|
195
179
|
requirements: []
|
196
180
|
rubyforge_project:
|
197
|
-
rubygems_version:
|
181
|
+
rubygems_version: 2.0.3
|
198
182
|
signing_key:
|
199
|
-
specification_version:
|
183
|
+
specification_version: 4
|
200
184
|
summary: SIPp profile and RTP stream generator
|
201
185
|
test_files:
|
202
186
|
- spec/sippy_cup/media_spec.rb
|
203
187
|
- spec/sippy_cup/runner_spec.rb
|
204
188
|
- spec/sippy_cup/scenario_spec.rb
|
205
189
|
- spec/spec_helper.rb
|
206
|
-
has_rdoc:
|