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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YTBhNThlZWZhZGVlZTgxOWU2YWJjZGFhMmU1MjkzZDc0OTNhNzY0OA==
4
+ ZTYzYWNiZmNmOTU4NjU5MzBhOTRhZGI4M2ZlZjg5YzBiOWEyZmIzNw==
5
5
  data.tar.gz: !binary |-
6
- NDJjY2Y0MjNkMjRlNGZmYWM5NzhmZWM3OGIyMzRjNWNlZDkwYzRiNg==
7
- !binary "U0hBNTEy":
6
+ NTU0NDIwOWUyZjc5MTRjMzkzOTQwZTNhZWZjNjhlNTMwZmM0N2NhYQ==
7
+ SHA512:
8
8
  metadata.gz: !binary |-
9
- MzQ5YzIyZTQ2MTE2YTg2ZTI1NTJmYjc4NWQxYjZiODJiZGE4MjhmY2NkMTA0
10
- OTA1OWViYTE2NzM0ZjQ1NTViMzM5ZDRjNTRlZTY1Mjk0YmUyMzgwYTk2OTI0
11
- ZDRiM2Y3MDE5YjkxZmQ4NzJhYTk2YWM3ODJjNTk1MGVmMDBhZmM=
9
+ ZWE1NDM0Nzg5OTU0NDAzMDA0MmI3Mjk0YjY1MWFkOTMzZDQ4YmU0ZTI0ZDk0
10
+ N2ViNjlhYWVlOGQ4YTQxOGUxYTQ5NzE0ZjY4Y2ZlNjVlMjBhMTczZmMxZmFh
11
+ YzAxMzU4MTk4YzYyZWZmN2EyZDRlYjMyYmZlNGYxMGY1NzdlYzg=
12
12
  data.tar.gz: !binary |-
13
- Njg5MjVkNjI3ZjZhN2NiYjVhN2ZlM2I3MjA3MTk1YTcyYmY3YTkxNDVjYzE2
14
- M2Y5YTkzNGE4MGJmYjFjMjE1ZDkzNmU4MGU5NmVjOGY2ZjRmMmY5ZDU5NzIz
15
- YTMzNDc2ZjhkOTQ2ODJiMGZiYTgyZDhkMzlkYzhmYTNmYzRjMTU=
13
+ MmRiYTBlMjc4MzUxMjAwZjdmZGJjYWY1MDNjNGM2MTU5ZTEyNjk2MWE2ZTgy
14
+ NWE1ZDM2YTQzNmYyZDg0NDUwNTk0NDEzZWZhYTg0OWIxNGY4NzIwMTQxNWM0
15
+ MDk0MTEyZDhhYzIzNjU1YjIwMDMyYjU4ODJlNTkxMDI5YjZmZDk=
data/.gitignore CHANGED
@@ -2,3 +2,6 @@
2
2
  .DS_Store
3
3
  *.gem
4
4
  /spec/reports/
5
+ Gemfile.lock
6
+ .ruby-gemset
7
+ tmp/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - jruby-19mode
6
+ - rbx-19mode
7
+ - ruby-head
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: 2.0.0
11
+ - rvm: jruby-19mode
12
+ - rvm: rbx-19mode
13
+ - rvm: ruby-head
14
+ notifications:
15
+ irc: "irc.freenode.org#adhearsion"
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # develop
2
+
3
+ # [0.3.0](https://github.com/bklang/sippy_cup/compare/v0.2.3...v0.3.0)
4
+ * Feature: A whole lot more documentation, test coverage and cleaner internals.
5
+ * Feature: Added a :transport_mode option that will add the -t switch to SIPp for setting TCP or other UDP modes.
6
+ * Feature: A YAML manifest may now reference a SIPp scenario (and media) on disk rather than providing steps.
7
+ * Feature: A media port may be specified as `:media_port` in the manifest or at runtime.
8
+ * Feature: API for validation of scenarios in manifests.
9
+ * Feature: Handle SIPp exit codes with clean exceptions.
10
+ * Feature: Allow passing arbitary headers in an INVITE
11
+ * Change: AVP is now AVPF in SDP.
12
+ * Change: Rake tasks for executing scenarios are removed.
13
+ * Change: Running and compiling scenarios are now separate concepts.
14
+ * `-c` on the CLI writes a YAML manifest to disk as SIPp XML and PCAP media. `-r` executes a YAML manifest and does not write to disk.
15
+ * XML scenarios may be referenced in a YAML manifest using the `scenario:` and `media:` keys, providing paths.
16
+ * `Runner` now takes a `Scenario` which it executes using SIPp via a temporary local-disk export. Most options passed to `Runner.new` are now properties of Scenario and can be specified in the YAML manifest. `Runner` no longer executes a scenario by path.
17
+ * Bugfix/Security: Don't symbolise untrusted data (YAML manifests).
18
+ * Bugfix: Allow the `sleep` step to take fractional seconds.
19
+ * Bugfix: Proxy full SIPp output to terminal by default.
20
+
1
21
  # [0.2.3](https://github.com/bklang/sippy_cup/compare/v0.2.2...v0.2.3)
2
22
  * Bugfix: Handle file extensions .yaml and .yml equally (#21)
3
23
  * Bugfix: Fix missing Logger constant (#20)
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ gem 'rake'
data/Guardfile CHANGED
@@ -1,10 +1,5 @@
1
- ENV['SKIP_RCOV'] = 'true'
2
-
3
- group 'rspec' do
4
- guard 'rspec', :cli => '--format documentation', :all_on_start => true, :all_after_pass => true do
5
- watch(%r{^spec/.+_spec\.rb$})
6
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
- watch('spec/spec_helper.rb') { "spec/" }
8
- end
1
+ guard 'rspec', :cli => '--format documentation', :all_on_start => true, :all_after_pass => true do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec/" }
9
5
  end
10
-
data/README.markdown CHANGED
@@ -1,3 +1,9 @@
1
+ [![Gem Version](https://badge.fury.io/rb/sippy_cup.png)](https://rubygems.org/gems/sippy_cup)
2
+ [![Build Status](https://secure.travis-ci.org/mojolingo/sippy_cup.png?branch=master)](http://travis-ci.org/mojolingo/sippy_cup)
3
+ [![Dependency Status](https://gemnasium.com/mojolingo/sippy_cup.png?travis)](https://gemnasium.com/mojolingo/sippy_cup)
4
+ [![Code Climate](https://codeclimate.com/github/mojolingo/sippy_cup.png)](https://codeclimate.com/github/mojolingo/sippy_cup)
5
+ [![Coverage Status](https://coveralls.io/repos/mojolingo/sippy_cup/badge.png?branch=master)](https://coveralls.io/r/mojolingo/sippy_cup)
6
+
1
7
  # Sippy Cup
2
8
 
3
9
  ## Overview
@@ -87,14 +93,16 @@ steps:
87
93
 
88
94
  Both `source` and `destination` above may be optionally supplied with a port number, eg. `192.0.2.200:5061`
89
95
 
90
- Next, compile and run the scenario:
96
+ Next, execute the scenario:
91
97
 
92
98
  ```Shell
93
- $ sippy_cup -cr my_test_scenario.yml
94
- Compiling media to /Users/bklang/src/sippy_cup/my_test_scenario.pcap...done.
95
- Compiling scenario to /Users/bklang/src/sippy_cup/my_test_scenario.xml...done.
96
- "Preparing to run SIPp command: sudo sipp -i 192.0.2.15 -p 8836 -sf /Users/bklang/src/sippy_cup/my_test_scenario.xml -l 10 -m 20 -r 5 -s 1 > /dev/null 2>&1"
97
- $
99
+ $ sippy_cup -r my_test_scenario.yml
100
+ I, [2013-09-30T14:48:08.388106 #9883] INFO -- : Preparing to run SIPp command: sudo sipp -i 192.0.2.15 -p 8836 -sf /var/folders/n4/dpzsp6_95tb3c4sp12xj5wdr0000gn/T/scenario20130930-9883-1crejcw -l 10 -m 20 -r 5 -s 1 192.0.2.200
101
+ Password:
102
+
103
+ ...snip...
104
+
105
+ I, [2013-09-30T14:48:16.728712 #9883] INFO -- : Test completed successfully.
98
106
  ```
99
107
 
100
108
  ### Example embedding SIPp in another Ruby process
@@ -121,20 +129,13 @@ end
121
129
  scenario.compile!
122
130
  ```
123
131
 
124
- The above code can either be executed as a standalone Ruby script and run with SIPp, or it can be compiled and run using rake tasks by inserting the following code into your Rakefile:
125
- ```Ruby
126
- require 'sippy_cup/tasks'
127
- ```
128
-
129
- Then running the rake task `rake sippy_cup:compile[sippy_cup.rb]`
130
-
131
- And finally running `rake sippy_cup:run[sippy_cup.yml]` to execute the scenario.
132
+ The above code can be executed as a standalone Ruby script and the resulting scenario file run with SIPp.
132
133
 
133
134
  ## Customize Your Scenarios
134
135
 
135
136
  ### Available Scenario Steps
136
137
 
137
- Each command below can take [SIPp attributes](http://sipp.sourceforge.net/doc/reference.html) as optional arguments.
138
+ Each command below can take [SIPp attributes](http://sipp.sourceforge.net/doc/reference.html) as optional arguments. For a full list of available steps, see the [API documentation](http://rubydoc.info/gems/sippy_cup/SippyCup/Scenario).
138
139
 
139
140
  * `sleep <seconds>` Wait a specified number of seconds
140
141
  * `invite` Send a SIP INVITE to the specified target
@@ -155,28 +156,27 @@ Each command below can take [SIPp attributes](http://sipp.sourceforge.net/doc/re
155
156
 
156
157
  Don't want your scenario to end up in the same directory as your script? Need the filename to be different than the scenario name? No problem!
157
158
 
158
- For the `sippy_cup` YAML specification, use `scenario`:
159
+ For the `sippy_cup` manifest, use `filename`:
159
160
 
160
161
  ```YAML
161
162
  ---
162
- scenario: /path/to/scenario.xml
163
+ filename: /path/to/somewhere
163
164
  ```
164
165
 
165
166
  Or, in Ruby:
166
167
 
167
168
  ```Ruby
168
- my_opts = { source: '192.168.5.5:10001', destination: '10.10.0.3:19995', filename: '/path/to/somewhere' }
169
- s = SippyCup::Scenario.new 'SippyCup', my_opts do
169
+ s = SippyCup::Scenario.new 'SippyCup', source: '192.168.5.5:10001', destination: '10.10.0.3:19995', filename: '/path/to/somewhere' do
170
170
  # scenario definitions here...
171
171
  end
172
+ s.compile!
172
173
  ```
173
174
 
174
- This will create the files `somewhere.xml`, `somewhere.pcap`, and `somewhere.yml` in the `/path/to/` directory.
175
+ This will create the files `somewhere.xml` and `somewhere.pcap` in the `/path/to/` directory.
175
176
 
176
177
  ### Customizing the Test Run
177
178
 
178
-
179
- Each parameter has an impact on the test, and may either be changed once the YAML file is generated or specified in the options hash for `SippyCup::Scenario.new`. In addition to the default parameters, some additional parameters can be set:
179
+ Each parameter has an impact on the test, and may either be changed once the XML file is generated or specified in the options hash for `SippyCup::Scenario.new`. In addition to the default parameters, some additional parameters can be set:
180
180
  <dl>
181
181
  <dt>stats_file</dt>
182
182
  <dd>Path to a file where call statistics will be stored in a CSV format, defaults to not storing stats</dd>
@@ -188,10 +188,10 @@ Each parameter has an impact on the test, and may either be changed once the YAM
188
188
  <dd>SIP username to use. Defaults to "1" (as in 1@127.0.0.1)</dd>
189
189
 
190
190
  <dt>full_sipp_output</dt>
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>
191
+ <dd>By default, SippyCup will show SIPp's command line output while running a scenario. Set this parameter to `false` to hide full command line output</dd>
192
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>
193
+ <dt>media_port</dt>
194
+ <dd>By default, SIPp assigns RTP ports dynamically. However, if there is a need for a static RTP port (say, for data collection purposes), it can be done by supplying a port number here.</dd>
195
195
 
196
196
  <dt>scenario_variables</dt>
197
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>
@@ -202,14 +202,14 @@ Each parameter has an impact on the test, and may either be changed once the YAM
202
202
  With Sippy Cup, you can add additional attributes to each step of the scenario:
203
203
 
204
204
  ```Ruby
205
- #This limits the amount of time the server has to reply to an invite (3 seconds)
205
+ # This limits the amount of time the server has to reply to an invite (3 seconds)
206
206
  s.receive_answer timeout: 3000
207
207
 
208
- #You can override the default 'optional' parameters
208
+ # You can override the default 'optional' parameters
209
209
  s.receive_ringing optional: false
210
210
  s.receive_answer optional: true
211
211
 
212
- #Let's combine multiple attributes...
212
+ # Let's combine multiple attributes...
213
213
  s.receive_answer timeout: 3000, crlf: true
214
214
  ```
215
215
 
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'rspec/core/rake_task'
2
- require 'ci/reporter/rake/rspec'
3
2
 
4
3
  RSpec::Core::RakeTask.new(:spec)
5
- task :spec => ['ci:setup:rspec']
6
- task :default => [:spec]
4
+
5
+ task :default => :spec
data/bin/sippy_cup CHANGED
@@ -6,9 +6,8 @@ require 'getoptlong'
6
6
  def usage
7
7
  puts "#{$0} [OPTS] </path/to/sippy_cup_definition.yml>"
8
8
  puts
9
- puts "--compile, -c Compile the given scenario YAML to XML and PCAP"
10
- puts "--run, -r Run the scenario. If used without -c, must supply a previously"
11
- puts " compiled SippyCup file"
9
+ puts "--compile, -c Compile the given scenario manifest to XML and PCAP"
10
+ puts "--run, -r Run the scenario"
12
11
  puts "--help, -h Print this usage information"
13
12
  puts "--version, -V Print SippyCup version"
14
13
  end
@@ -20,16 +19,14 @@ opts = GetoptLong.new(
20
19
  ['--version', '-V', GetoptLong::NO_ARGUMENT]
21
20
  )
22
21
 
23
- @compile = false
24
- @run = false
22
+ compile = false
23
+ run = false
25
24
  opts.each do |opt, arg|
26
25
  case opt
27
26
  when '--compile'
28
- @compile = true
29
- @definition_file = arg
27
+ compile = true
30
28
  when '--run'
31
- @run = true
32
- @compiled_file = arg
29
+ run = true
33
30
  when '--help'
34
31
  usage
35
32
  exit 0
@@ -41,38 +38,31 @@ opts.each do |opt, arg|
41
38
  end
42
39
 
43
40
  unless ARGV.count == 1
44
- puts "ERROR: Must supply the SippyCup definition file"
41
+ puts "ERROR: Must supply the SippyCup manifest file"
45
42
  puts
46
43
  usage
47
44
  exit 1
48
45
  end
49
46
 
50
- @definition_file = ARGV.shift
47
+ manifest_path = ARGV.shift
51
48
 
52
- unless File.readable? @definition_file
53
- puts "ERROR: Unable to read definition file"
49
+ unless File.readable? manifest_path
50
+ puts "ERROR: Unable to read manifest file"
54
51
  puts
55
52
  usage
56
53
  exit 1
57
54
  end
58
55
 
59
- unless @compile || @run
56
+ unless compile || run
60
57
  puts "No action (compile or run) specified. Exiting."
61
58
  usage
62
59
  exit 1
63
60
  end
64
61
 
65
- @definition = YAML.load File.read @definition_file
62
+ scenario = SippyCup::Scenario.from_manifest File.read(manifest_path), input_filename: manifest_path
63
+ scenario.compile! if compile
66
64
 
67
- unless @definition.has_key? :scenario
68
- @definition[:scenario] = @definition_file.gsub /\.ya?ml$/, ''
65
+ if run
66
+ runner = SippyCup::Runner.new scenario
67
+ runner.run
69
68
  end
70
- unless @definition.has_key? :name
71
- @definition[:name] = @definition[:scenario]
72
- end
73
-
74
-
75
- runner = SippyCup::Runner.new @definition
76
- runner.compile if @compile
77
- runner.run if @run
78
-
@@ -37,7 +37,7 @@ module SippyCup
37
37
 
38
38
  @sequence.each do |input|
39
39
  action, value = get_step input
40
-
40
+
41
41
  case action
42
42
  when 'silence'
43
43
  # value is the duration in milliseconds
@@ -85,7 +85,9 @@ module SippyCup
85
85
  end
86
86
  @pcap_file
87
87
  end
88
+
88
89
  private
90
+
89
91
  def get_step(input)
90
92
  action, value = input.split ':'
91
93
  raise "Invalid Sequence: #{input}" unless VALID_STEPS.include? action
@@ -1,75 +1,173 @@
1
- require 'yaml'
2
1
  require 'logger'
3
- require 'active_support/core_ext/hash'
4
2
 
3
+ #
4
+ # Service object to oversee the execution of a Scenario
5
+ #
5
6
  module SippyCup
6
7
  class Runner
7
8
  attr_accessor :sipp_pid
8
- attr_accessor :logger
9
9
 
10
- def initialize(opts = {})
11
- @options = ActiveSupport::HashWithIndifferentAccess.new opts
10
+ #
11
+ # Create a runner from a scenario
12
+ #
13
+ # @param [Scenario, XMLScenario] scenario The scenario to execute
14
+ # @param [Hash] opts Options to modify the runner
15
+ # @option opts [optional, true, false] :full_sipp_output Whether or not to copy SIPp's stdout/stderr to the parent process. Defaults to true.
16
+ # @option opts [optional, Logger] :logger A logger to use in place of the internal logger to STDOUT.
17
+ # @option opts [optional, String] :command The command to execute. This is mostly available for testing.
18
+ #
19
+ def initialize(scenario, opts = {})
20
+ @scenario = scenario
21
+ @scenario_options = @scenario.scenario_options
22
+
23
+ defaults = { full_sipp_output: true }
24
+ @options = defaults.merge(opts)
25
+
26
+ @command = @options[:command]
12
27
  @logger = @options[:logger] || Logger.new(STDOUT)
13
28
  end
14
29
 
15
- def compile
16
- raise ArgumentError, "Must provide scenario steps" unless @options[:steps]
17
-
18
- scenario_opts = {source: @options[:source], destination: @options[:destination]}
19
- scenario_opts[:filename] = @options[:filename] if @options[:filename]
20
- scenario = SippyCup::Scenario.new @options[:name].titleize, scenario_opts
21
- @options[:steps].each do |step|
22
- instruction, arg = step.split ' ', 2
23
- if arg && !arg.empty?
24
- # Strip leading/trailing quotes if present
25
- arg.gsub!(/^'|^"|'$|"$/, '')
26
- scenario.send instruction.to_sym, arg
27
- else
28
- scenario.send instruction
30
+ # Runs the loaded scenario using SIPp
31
+ #
32
+ # @raises Errno::ENOENT when the SIPp executable cannot be found
33
+ # @raises SippyCup::ExitOnInternalCommand when SIPp exits on an internal command. Calls may have been processed
34
+ # @raises SippyCup::NoCallsProcessed when SIPp exit normally, but has processed no calls
35
+ # @raises SippyCup::FatalError when SIPp encounters a fatal failure
36
+ # @raises SippyCup::FatalSocketBindingError when SIPp fails to bind to the specified socket
37
+ # @raises SippyCup::SippGenericError when SIPp encounters another type of error
38
+ #
39
+ # @return Boolean true if execution succeeded without any failed calls, false otherwise
40
+ #
41
+ def run
42
+ @input_files = @scenario.to_tmpfiles
43
+
44
+ @logger.info "Preparing to run SIPp command: #{command}"
45
+
46
+ exit_status, stderr_buffer = execute_with_redirected_streams
47
+
48
+ final_result = process_exit_status exit_status, stderr_buffer
49
+
50
+ if final_result
51
+ @logger.info "Test completed successfully!"
52
+ else
53
+ @logger.info "Test completed successfully but some calls failed."
54
+ end
55
+ @logger.info "Statistics logged at #{File.expand_path @scenario_options[:stats_file]}" if @scenario_options[:stats_file]
56
+
57
+ final_result
58
+ ensure
59
+ cleanup_input_files
60
+ end
61
+
62
+ #
63
+ # Tries to stop SIPp by killing the target PID
64
+ #
65
+ # @raises Errno::ESRCH when the PID does not correspond to a known process
66
+ # @raises Errno::EPERM when the process referenced by the PID cannot be killed
67
+ #
68
+ def stop
69
+ Process.kill "KILL", @sipp_pid if @sipp_pid
70
+ end
71
+
72
+ private
73
+
74
+ def command
75
+ @command ||= begin
76
+ command = "sudo sipp"
77
+ command_options.each_pair do |key, value|
78
+ command << (value ? " -#{key} #{value}" : " -#{key}")
29
79
  end
80
+ command << " #{@scenario_options[:destination]}"
30
81
  end
31
- scenario.compile!
32
82
  end
33
83
 
34
- def prepare_command
35
- [:scenario, :source, :destination, :max_concurrent, :calls_per_second, :number_of_calls].each do |arg|
36
- raise ArgumentError, "Must provide #{arg}!" unless @options[arg]
84
+ def command_options
85
+ options = {
86
+ i: @scenario_options[:source],
87
+ p: @scenario_options[:source_port] || '8836',
88
+ sf: @input_files[:scenario].path,
89
+ l: @scenario_options[:max_concurrent],
90
+ m: @scenario_options[:number_of_calls],
91
+ r: @scenario_options[:calls_per_second],
92
+ s: @scenario_options[:from_user] || '1'
93
+ }
94
+
95
+ options[:mp] = @scenario_options[:media_port] if @scenario_options[:media_port]
96
+
97
+ if @scenario_options[:stats_file]
98
+ options[:trace_stat] = nil
99
+ options[:stf] = @scenario_options[:stats_file]
100
+ options[:fd] = @scenario_options[:stats_interval] || 1
37
101
  end
38
- command = "sudo sipp"
39
- source_port = @options[:source_port] || '8836'
40
- sip_user = @options[:sip_user] || '1'
41
- command << " -i #{@options[:source]} -p #{source_port} -sf #{File.expand_path @options[:scenario]}.xml"
42
- command << " -l #{@options[:max_concurrent]} -m #{@options[:number_of_calls]} -r #{@options[:calls_per_second]}"
43
- command << " -s #{sip_user}"
44
- if @options[:stats_file]
45
- stats_interval = @options[:stats_interval] || 1
46
- command << " -trace_stat -stf #{@options[:stats_file]} -fd #{stats_interval}"
102
+
103
+ if @scenario_options[:transport_mode]
104
+ options[:t] = @scenario_options[:transport_mode]
105
+ end
106
+
107
+ if @scenario_options[:scenario_variables]
108
+ options[:inf] = @scenario_options[:scenario_variables]
47
109
  end
48
- command << " -inf #{@options[:scenario_variables]}" if @options[:scenario_variables]
49
- command << " #{@options[:destination]}"
50
- command << " > /dev/null 2>&1" unless @options[:full_sipp_output]
51
- command
110
+
111
+ options
52
112
  end
53
113
 
54
- def run
55
- command = prepare_command
56
- @logger.info "Preparing to run SIPp command: #{command}"
114
+ def execute_with_redirected_streams
115
+ rd, wr = IO.pipe
116
+ stdout_target = @options[:full_sipp_output] ? $stdout : '/dev/null'
117
+
118
+ @sipp_pid = spawn command, err: wr, out: stdout_target
57
119
 
58
- begin
59
- @sipp_pid = spawn command
60
- Process.wait @sipp_pid
61
- rescue Exception => e
62
- raise RuntimeError, "Command #{command} failed"
120
+ stderr_buffer = String.new
121
+
122
+ Thread.new do
123
+ wr.close
124
+ until rd.eof?
125
+ buffer = rd.readpartial(1024).strip
126
+ stderr_buffer += buffer
127
+ $stderr << buffer if @options[:full_sipp_output]
128
+ end
63
129
  end
64
130
 
65
- @logger.info "Test completed successfully!"
66
- @logger.info "Statistics logged at #{File.expand_path @options[:stats_file]}" if @options[:stats_file]
131
+ exit_status = Process.wait2 @sipp_pid.to_i
132
+
133
+ rd.close
134
+
135
+ [exit_status, stderr_buffer]
67
136
  end
68
137
 
69
- def stop
70
- Process.kill "KILL", @sipp_pid if @sipp_pid
71
- rescue Exception => e
72
- raise RuntimeError, "Killing #{@sipp_pid} failed"
138
+ def process_exit_status(process_status, error_message = nil)
139
+ exit_code = process_status[1].exitstatus
140
+ case exit_code
141
+ when 0
142
+ true
143
+ when 1
144
+ false
145
+ when 97
146
+ raise SippyCup::ExitOnInternalCommand, error_message
147
+ when 99
148
+ raise SippyCup::NoCallsProcessed, error_message
149
+ when 255
150
+ raise SippyCup::FatalError, error_message
151
+ when 254
152
+ raise SippyCup::FatalSocketBindingError, error_message
153
+ else
154
+ raise SippyCup::SippGenericError, error_message
155
+ end
156
+ end
157
+
158
+ def cleanup_input_files
159
+ @input_files.each_pair do |key, value|
160
+ value.close
161
+ value.unlink
162
+ end
73
163
  end
74
164
  end
165
+
166
+ # The corresponding SIPp error code is listed after the exception
167
+ class Error < StandardError; end
168
+ class ExitOnInternalCommand < Error; end # 97
169
+ class NoCallsProcessed < Error; end # 99
170
+ class FatalError < Error; end # -1
171
+ class FatalSocketBindingError < Error; end # -2
172
+ class SippGenericError < Error; end # 255 and undocumented errors
75
173
  end