sippy_cup 0.2.1 → 0.2.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.
- 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:
|