blitz 0.1.20 → 0.1.21

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.
@@ -0,0 +1,226 @@
1
+ require 'blitz/utils'
2
+
3
+ class Blitz
4
+ class Curl
5
+ extend Blitz::Utils
6
+
7
+ RE_WS = /^\s+/.freeze
8
+ RE_NOT_WS = /^[^\s]+/.freeze
9
+ RE_DQ_STRING = /^"[^"\\\r\n]*(?:\\.[^"\\\r\n]*)*"/.freeze
10
+ RE_SQ_STRING = /^'[^'\\\r\n]*(?:\\.[^'\\\r\n]*)*'/.freeze
11
+
12
+ def self.parse arguments
13
+ argv = arguments.is_a?(Array) ? arguments : xargv(arguments)
14
+ args = parse_cli argv
15
+ raise "help" if args['help']
16
+ if not args['pattern']
17
+ Blitz::Curl::Sprint.new args
18
+ else
19
+ Blitz::Curl::Rush.new args
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def self.xargv text
26
+ argv = []
27
+ while not text.empty?
28
+ if text.match RE_WS
29
+ text = $'
30
+ elsif text.match RE_DQ_STRING or text.match RE_SQ_STRING or text.match RE_NOT_WS
31
+ text = $'
32
+ argv << strip_quotes($&)
33
+ end
34
+ end
35
+ argv
36
+ end
37
+
38
+ def self.strip_quotes text
39
+ text[1, (text.size - 2)]
40
+ end
41
+
42
+ def self.parse_cli argv
43
+ hash = { 'steps' => [] }
44
+
45
+ while not argv.empty?
46
+ hash['steps'] << Hash.new
47
+ step = hash['steps'].last
48
+
49
+ while not argv.empty?
50
+ break if argv.first[0,1] != '-'
51
+
52
+ k = argv.shift
53
+ if [ '-A', '--user-agent' ].member? k
54
+ step['user-agent'] = shift(k, argv)
55
+ next
56
+ end
57
+
58
+ if [ '-b', '--cookie' ].member? k
59
+ step['cookies'] ||= []
60
+ step['cookies'] << shift(k, argv)
61
+ next
62
+ end
63
+
64
+ if [ '-d', '--data' ].member? k
65
+ step['content'] ||= Hash.new
66
+ step['content']['data'] ||= []
67
+ v = shift(k, argv)
68
+ v = File.read v[1..-1] if v =~ /^@/
69
+ step['content']['data'] << v
70
+ next
71
+ end
72
+
73
+ if [ '-D', '--dump-header' ].member? k
74
+ hash['dump-header'] = shift(k, argv)
75
+ next
76
+ end
77
+
78
+ if [ '-e', '--referer'].member? k
79
+ step['referer'] = shift(k, argv)
80
+ next
81
+ end
82
+
83
+ if [ '-h', '--help' ].member? k
84
+ hash['help'] = true
85
+ next
86
+ end
87
+
88
+ if [ '-H', '--header' ].member? k
89
+ step['headers'] ||= []
90
+ step['headers'].push shift(k, argv)
91
+ next
92
+ end
93
+
94
+ if [ '-p', '--pattern' ].member? k
95
+ v = shift(k, argv)
96
+ v.split(',').each do |vt|
97
+ unless /^(\d+)-(\d+):(\d+)$/ =~ vt
98
+ raise Test::Unit::AssertionFailedError,
99
+ "invalid ramp pattern"
100
+ end
101
+ hash['pattern'] ||= { 'iterations' => 1, 'intervals' => [] }
102
+ hash['pattern']['intervals'] << {
103
+ 'iterations' => 1,
104
+ 'start' => $1.to_i,
105
+ 'end' => $2.to_i,
106
+ 'duration' => $3.to_i
107
+ }
108
+ end
109
+ next
110
+ end
111
+
112
+ if [ '-r', '--region' ].member? k
113
+ hash['region'] = shift(k, argv)
114
+ next
115
+ end
116
+
117
+ if [ '-s', '--status' ].member? k
118
+ step['status'] = shift(k, argv).to_i
119
+ next
120
+ end
121
+
122
+ if [ '-T', '--timeout' ].member? k
123
+ step['timeout'] = shift(k, argv).to_i
124
+ next
125
+ end
126
+
127
+ if [ '-u', '--user' ].member? k
128
+ step['user'] = shift(k, argv)
129
+ next
130
+ end
131
+
132
+ if [ '-X', '--request' ].member? k
133
+ step['request'] = shift(k, argv)
134
+ next
135
+ end
136
+
137
+ if /-x:c/ =~ k or /--xtract:cookie/ =~ k
138
+ xname = shift(k, argv)
139
+ assert_match /^[a-zA-Z_][a-zA-Z_0-9]*$/, xname,
140
+ "cookie name must be alphanumeric: #{xname}"
141
+
142
+ step['xtracts'] ||= Hash.new
143
+ xhash = step['xtracts'][xname] = { 'type' => 'cookie' }
144
+ next
145
+ end
146
+
147
+ if /-v:(\S+)/ =~ k or /--variable:(\S+)/ =~ k
148
+ vname = $1
149
+ vargs = shift(k, argv)
150
+
151
+ assert_match /^[a-zA-Z][a-zA-Z0-9]*$/, vname,
152
+ "variable name must be alphanumeric: #{vname}"
153
+
154
+ step['variables'] ||= Hash.new
155
+ vhash = step['variables'][vname] = Hash.new
156
+ if vargs.match /^(list)?\[([^\]]+)\]$/
157
+ vhash['type'] = 'list'
158
+ vhash['entries'] = $2.split(',')
159
+ elsif vargs.match /^(a|alpha)$/
160
+ vhash['type'] = 'alpha'
161
+ elsif vargs.match /^(a|alpha)\[(\d+),(\d+)(,(\d+))??\]$/
162
+ vhash['type'] = 'alpha'
163
+ vhash['min'] = $2.to_i
164
+ vhash['max'] = $3.to_i
165
+ vhash['count'] = $5 ? $5.to_i : 1000
166
+ elsif vargs.match /^(n|number)$/
167
+ vhash['type'] = 'number'
168
+ elsif vargs.match /^(n|number)\[(-?\d+),(-?\d+)(,(\d+))?\]$/
169
+ vhash['type'] = 'number'
170
+ vhash['min'] = $2.to_i
171
+ vhash['max'] = $3.to_i
172
+ vhash['count'] = $5 ? $5.to_i : 1000
173
+ elsif vargs.match /^(u|udid)$/
174
+ vhash['type'] = 'udid'
175
+ else
176
+ raise ArgumentError,
177
+ "Invalid variable args for #{vname}: #{vargs}"
178
+ end
179
+ next
180
+ end
181
+
182
+ if [ '-V', '--verbose' ].member? k
183
+ hash['verbose'] = true
184
+ next
185
+ end
186
+
187
+ if [ '-1', '--tlsv1' ].member? k
188
+ step['ssl'] = 'tlsv1'
189
+ next
190
+ end
191
+
192
+ if [ '-2', '--sslv2' ].member? k
193
+ step['ssl'] = 'sslv2'
194
+ next
195
+ end
196
+
197
+ if [ '-3', '--sslv3' ].member? k
198
+ step['ssl'] = 'sslv3'
199
+ next
200
+ end
201
+
202
+ raise ArgumentError, "Unknown option #{k}"
203
+ end
204
+
205
+ if step.member? 'content'
206
+ data_size = step['content']['data'].inject(0) { |m, v| m + v.size }
207
+ assert(data_size < 10*1024, "POST content must be < 10K")
208
+ end
209
+
210
+ break if hash['help']
211
+
212
+ url = argv.shift
213
+ raise ArgumentError, "no URL specified!" if not url
214
+ step['url'] = url
215
+ end
216
+
217
+ if not hash['help']
218
+ if hash['steps'].empty?
219
+ raise ArgumentError, "no URL specified!"
220
+ end
221
+ end
222
+
223
+ hash
224
+ end
225
+ end
226
+ end
@@ -1,5 +1,5 @@
1
1
  class Blitz
2
- module Curl
2
+ class Curl
3
3
  class Error < StandardError # :nodoc:
4
4
  def initialize json={}
5
5
  super json['reason'] || "Hmmm, something went wrong. Try again in a little bit?"
@@ -1,5 +1,5 @@
1
1
  class Blitz
2
- module Curl # :nodoc:
2
+ class Curl # :nodoc:
3
3
  # Use this to run a rush (a load test) against your app. The return values
4
4
  # include the entire timeline containing the average duration, the concurrency,
5
5
  # the bytes sent/received, etc.
@@ -99,41 +99,34 @@ class Rush
99
99
  # the pattern. If a block is given, it's invoked periodically with the
100
100
  # partial results of the run (to report progress, perhaps)
101
101
  #
102
- # args = {
103
- # :url => 'http://www.mudynamics.com',
104
- # :headers => [ 'X-API-Token: foo' ],
105
- # :region => 'california',
106
- # :pattern => {
107
- # :intervals => [{ :start => 1, :end => 10000, :duration => 60 }]
108
- # }
109
- # }
110
- #
111
- # result = Blitz::Curl::Sprint.execute args do |partial|
102
+ # result = Blitz::Curl.parse('-r california -p 10-50:30 www.example.com').execute do |partial|
112
103
  # pp [ partial.region, partial.timeline.last.hits ]
113
104
  # end
114
105
  #
115
106
  # You can easily export the result to JSON, XML or compute the various
116
107
  # rates, etc.
117
- def self.execute args, &block # |result|
118
- self.queue(args).result &block
108
+ def execute &block # |result|
109
+ queue
110
+ result &block
119
111
  end
120
112
 
121
- def self.queue args # :nodoc:
113
+ def queue # :nodoc:
122
114
  if not args.member? 'pattern' and not args.member? :pattern
123
115
  raise ArgumentError, 'missing pattern'
124
116
  end
125
117
 
126
118
  res = Command::API.client.curl_execute args
127
119
  raise Error.new(res) if res['error']
128
- return self.new res
120
+ @job_id = res['job_id']
121
+ @region = res['region']
129
122
  end
130
123
 
131
124
  attr_reader :job_id # :nodoc:
132
125
  attr_reader :region # :nodoc:
126
+ attr_reader :args # :nodoc:
133
127
 
134
- def initialize json # :nodoc:
135
- @job_id = json['job_id']
136
- @region = json['region']
128
+ def initialize args # :nodoc:
129
+ @args = args
137
130
  end
138
131
 
139
132
  def result &block # :nodoc:
@@ -180,7 +173,7 @@ class Rush
180
173
 
181
174
  def abort! # :nodoc:
182
175
  Command::API.client.abort_job job_id rescue nil
183
- end
176
+ end
184
177
  end
185
178
  end # Curl
186
179
  end # Blitz
@@ -1,5 +1,5 @@
1
1
  class Blitz
2
- module Curl # :nodoc:
2
+ class Curl # :nodoc:
3
3
  # Use this to run a sprint against your app. The return values include the response
4
4
  # time, the region from which the sprint was run along with the full request
5
5
  # and response headers and the response body.
@@ -104,38 +104,34 @@ class Sprint
104
104
  # The primary method to execute a sprint from region. This method supports
105
105
  # all of the arguments that the blitz bar supports. For example:
106
106
  #
107
- # args = {
108
- # :url => 'http://www.mudynamics.com',
109
- # :headers => [ 'X-API-Token: foo' ],
110
- # :region => 'california'
111
- # }
112
- #
113
- # result = Blitz::Curl::Sprint.execute args
114
- def self.execute args
115
- self.queue(args).result
107
+ # result = Blitz::Curl.parse('-r california www.example.com').execute
108
+ def execute
109
+ queue
110
+ result
116
111
  end
117
112
 
118
- def self.queue args # :nodoc:
113
+ def queue # :nodoc:
119
114
  args.delete 'pattern'
120
115
  args.delete :pattern
121
116
 
122
117
  res = Command::API.client.curl_execute args
123
118
  raise Error.new(res) if res['error']
124
- return self.new res
119
+ @job_id = res['job_id']
120
+ @region = res['region']
125
121
  end
126
122
 
127
123
  attr_reader :job_id # :nodoc:
128
124
  attr_reader :region # :nodoc:
125
+ attr_reader :args # :nodoc:
129
126
 
130
- def initialize json # :nodoc:
131
- @job_id = json['job_id']
132
- @region = json['region']
127
+ def initialize args # :nodoc:
128
+ @args = args
133
129
  end
134
130
 
135
131
  def result # :nodoc:
136
132
  while true
137
133
  sleep 2.0
138
-
134
+
139
135
  job = Command::API.client.job_status job_id
140
136
  if job['error']
141
137
  raise Error
@@ -170,7 +166,7 @@ class Sprint
170
166
 
171
167
  def abort # :nodoc:
172
168
  Command::API.client.abort_job job_id rescue nil
173
- end
169
+ end
174
170
  end
175
171
  end # Curl
176
172
  end # Blitz
@@ -0,0 +1,38 @@
1
+ require 'test/unit/assertions'
2
+
3
+ # The default template string contains what was sent and received. Strip
4
+ # these out since we don't need them
5
+ unless RUBY_VERSION =~ /^1.9/
6
+ module Test # :nodoc:
7
+ module Unit # :nodoc:
8
+ module Assertions # :nodoc:
9
+ class AssertionMessage # :nodoc:
10
+ alias :old_template :template
11
+
12
+ def template
13
+ @template_string = ''
14
+ @parameters = []
15
+ old_template
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ else
22
+ module ::Test::Unit # :nodoc:
23
+ AssertionFailedError = MiniTest::Assertion
24
+ end
25
+ end
26
+
27
+ class Blitz
28
+ module Utils
29
+ include Test::Unit::Assertions
30
+
31
+ def shift key, argv
32
+ val = argv.shift
33
+ assert_not_nil(val, "missing value for #{key}")
34
+ assert_no_match(/^-.*$/, val, "missing value for #{key}")
35
+ val
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blitz::Client do
4
+ before :each do
5
+ @resource = mock RestClient::Resource
6
+ RestClient::Resource.stub!(:new).and_return @resource
7
+ @client = Blitz::Client.new "test@example.com", '123456'
8
+ end
9
+
10
+ after :each do
11
+ RestClient::Resource.unstub!(:new)
12
+ end
13
+
14
+ context "#login" do
15
+ before :each do
16
+ @resource.should_receive(:[]).with('/login/api').and_return @resource
17
+ @resource.should_receive(:get).and_return "{\"api_key\":\"abc123\"}"
18
+ end
19
+
20
+ it "should return an api_key" do
21
+ result = @client.login
22
+ result.should_not be_nil
23
+ result['api_key'].should == 'abc123'
24
+ end
25
+ end
26
+
27
+ context "#account_about" do
28
+ before :each do
29
+ json = "{\"api_key\":\"abc123\", \"profile\":{\"email\":\"test@example.com\"}}"
30
+ @resource.should_receive(:[]).with('/api/1/account/about').and_return @resource
31
+ @resource.should_receive(:get).and_return json
32
+ end
33
+
34
+ it "should return a profile" do
35
+ result = @client.account_about
36
+ result.should_not be_nil
37
+ result['profile'].should_not be_nil
38
+ result['profile']['email'].should == "test@example.com"
39
+ end
40
+ end
41
+
42
+ context "#curl_execute" do
43
+ before :each do
44
+ json = "{\"ok\":true, \"job_id\":\"j123\", \"status\":\"queued\"}"
45
+ @resource.should_receive(:[]).with('/api/1/curl/execute').and_return @resource
46
+ @resource.should_receive(:post).and_return json
47
+ end
48
+
49
+ it "should return a profile" do
50
+ result = @client.curl_execute "{\"url\":\"wwwexample.com\"}"
51
+ result.should_not be_nil
52
+ result['ok'].should be_true
53
+ result['status'].should == "queued"
54
+ end
55
+
56
+ end
57
+ end