blitz 0.1.20 → 0.1.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -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