stella 0.7.0.006 → 0.7.0.012
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.
- data/CHANGES.txt +1 -0
- data/bin/stella +2 -2
- data/examples/cookies/plan.rb +18 -0
- data/examples/essentials/plan.rb +216 -29
- data/examples/example_webapp.rb +14 -4
- data/examples/example_webapp.ru +6 -0
- data/examples/exceptions/plan.rb +2 -8
- data/lib/stella/cli.rb +6 -3
- data/lib/stella/client/container.rb +49 -0
- data/lib/stella/client/modifiers.rb +19 -0
- data/lib/stella/client.rb +50 -78
- data/lib/stella/data/http/request.rb +2 -2
- data/lib/stella/engine/functional.rb +19 -24
- data/lib/stella/engine/load.rb +122 -52
- data/lib/stella/engine.rb +25 -4
- data/lib/stella/mixins/thread.rb +6 -0
- data/lib/stella/stats.rb +22 -36
- data/lib/stella/testplan/usecase.rb +2 -0
- data/lib/stella/testplan.rb +14 -5
- data/lib/stella/version.rb +1 -1
- data/lib/stella.rb +7 -0
- data/stella.gemspec +7 -4
- data/vendor/httpclient-2.1.5.2/httpclient/cookie.rb +71 -71
- data/vendor/httpclient-2.1.5.2/httpclient/http.rb +4 -1
- data/vendor/httpclient-2.1.5.2/httpclient/session.rb +3 -3
- data/vendor/httpclient-2.1.5.2/httpclient/ssl_config.rb +10 -6
- data/vendor/httpclient-2.1.5.2/httpclient.rb +3 -4
- metadata +8 -5
- data/vendor/httpclient-2.1.5.2/httpclient/stats.rb +0 -90
data/CHANGES.txt
CHANGED
data/bin/stella
CHANGED
@@ -59,7 +59,7 @@ class Stella::CLI::Definition
|
|
59
59
|
about "Run a functional test"
|
60
60
|
usage "stella verify http://stellaaahhhh.com/"
|
61
61
|
usage "stella verify -p path/2/testplan.rb http://stellaaahhhh.com/"
|
62
|
-
option :
|
62
|
+
option :w, :nowait, "Ignore wait times"
|
63
63
|
option :p, :testplan, String, "Path to testplan"
|
64
64
|
command :verify => Stella::CLI
|
65
65
|
|
@@ -68,7 +68,7 @@ class Stella::CLI::Definition
|
|
68
68
|
usage "stella load http://stellaaahhhh.com:3114/"
|
69
69
|
usage "stella load --clients=10 --repetitions=2 http://stellaaahhhh.com/"
|
70
70
|
usage "stella load -p path/2/testplan.rb -u 100 -r 5 http://stellaaahhhh.com/"
|
71
|
-
option :
|
71
|
+
option :w, :nowait, "Ignore wait times"
|
72
72
|
option :c, :clients, Integer, "Number of virtual clients"
|
73
73
|
option :r, :repetitions, Integer, "Number of times to repeat the testplan (per vclient)"
|
74
74
|
option :t, :time, String, "Max duration to run test"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
desc "Maintain Your Cookies"
|
2
|
+
|
3
|
+
usecase 65, "Simple search" do
|
4
|
+
|
5
|
+
get "/", "Homepage"
|
6
|
+
|
7
|
+
get "/search", "Search Results" do
|
8
|
+
param :what => random(['Big', 'Beads'])
|
9
|
+
param :where => 'Toronto'
|
10
|
+
end
|
11
|
+
|
12
|
+
get "/", "Homepage" do
|
13
|
+
response 200 do
|
14
|
+
puts doc.css('ul#history').first
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/examples/essentials/plan.rb
CHANGED
@@ -1,26 +1,165 @@
|
|
1
|
-
#
|
1
|
+
# Stella - Example Test Plan
|
2
|
+
#
|
3
|
+
#
|
4
|
+
# 1. INTRODUCTION
|
5
|
+
#
|
6
|
+
# A test plan is a group of one or more user
|
7
|
+
# scenarios. This allows you to simulate the
|
8
|
+
# kind of traffic your application is exposed
|
9
|
+
# to in the wild. Realistic tests are really
|
10
|
+
# important because they give you a much more
|
11
|
+
# accurate view of how your application is
|
12
|
+
# performing.
|
13
|
+
#
|
14
|
+
# A scenario (or "usecase") is a group a
|
15
|
+
# requests that represents a typical path
|
16
|
+
# that a real person would take through
|
17
|
+
# your site.
|
18
|
+
#
|
19
|
+
# This plan contains 3 scenarios:
|
20
|
+
#
|
21
|
+
# - Simple Search (60%)
|
22
|
+
# - YAML API (30%)
|
23
|
+
# - Self-serve API (10%)
|
24
|
+
#
|
25
|
+
# The percentages represent the relative amount
|
26
|
+
# of traffic to be generated for each scenario.
|
27
|
+
# In a test with 100 virtual users, 60 would
|
28
|
+
# follow the Simple Search usecase, 30 the YAML
|
29
|
+
# API, and 10 would following the Self-Serve API.
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# 2. THE CONFIGURATION LANGUAGE
|
33
|
+
#
|
34
|
+
# Test plans are defined in a streamlined version
|
35
|
+
# of the Ruby programming language. Using a "real"
|
36
|
+
# languages gives you a lot of power to specify
|
37
|
+
# complex operations.
|
38
|
+
#
|
39
|
+
#
|
40
|
+
# 3. START THE EXAMPLE APPLICATION
|
41
|
+
#
|
42
|
+
# You need to start the example web application before
|
43
|
+
# running this testplan. You can do this in one of the
|
44
|
+
# following ways:
|
45
|
+
#
|
46
|
+
# $ ruby examples/example_webapp.rb
|
47
|
+
#
|
48
|
+
# OR
|
49
|
+
#
|
50
|
+
# $ thin -R examples/example_webapp.ru start
|
51
|
+
#
|
52
|
+
# You can check that it's running by going to:
|
53
|
+
# http://127.0.0.1:3000/
|
54
|
+
#
|
55
|
+
#
|
56
|
+
# 4. RUNNING THE TEST PLAN
|
57
|
+
#
|
58
|
+
# You run this test plan from the command line.
|
59
|
+
# First you verify that the plan and application
|
60
|
+
# are running correctly:
|
61
|
+
#
|
62
|
+
# $ stella verify -p examples/essentials/plan.rb http://127.0.0.1:3114/
|
63
|
+
#
|
64
|
+
# The "verify" command executes the plan with a
|
65
|
+
# single user and provides more detailed output.
|
66
|
+
#
|
67
|
+
# "load" tests are run in a similar way:
|
68
|
+
#
|
69
|
+
# $ stella load -c 50 -r 10 -p examples/essentials/plan.rb http://127.0.0.1:3114/
|
70
|
+
#
|
71
|
+
# where "c" is the number of concurrent users and
|
72
|
+
# "r" is the number of times to repeat the plan.
|
73
|
+
#
|
74
|
+
#
|
75
|
+
# 5. WRITING A TEST PLAN
|
76
|
+
#
|
77
|
+
# The following is an example of a working test plan.
|
78
|
+
# I put a lot of effort into keeping the syntax simple
|
79
|
+
# but feel free to contact me if you have any questions
|
80
|
+
# or problems.
|
81
|
+
#
|
82
|
+
# Happy Testing!
|
83
|
+
#
|
84
|
+
# Delano (@solutious.com)
|
85
|
+
#
|
2
86
|
|
3
87
|
desc "Business Finder Testplan"
|
4
88
|
|
5
89
|
usecase 65, "Simple search" do
|
90
|
+
|
91
|
+
# An important factor in simulating traffic
|
92
|
+
# is using real, dynamic data. You can load
|
93
|
+
# data from a file using the resource method.
|
94
|
+
# Here is an example which reads a list of
|
95
|
+
# search terms ("restaurant", "hotel", ...)
|
96
|
+
# into an array called :search_terms. The
|
97
|
+
# colon is Ruby's way of defining a symbol.
|
98
|
+
#
|
6
99
|
resource :search_terms, list('search_terms.csv')
|
7
100
|
|
101
|
+
# Requests are defined with one of the
|
102
|
+
# following methods: get, post, head, delete.
|
103
|
+
# Here we define a simple get request for the
|
104
|
+
# homepage ("/").
|
105
|
+
#
|
8
106
|
get "/", "Homepage" do
|
107
|
+
# This tells Stella to wait between 1 and 5
|
108
|
+
# seconds before moving to the next request.
|
9
109
|
wait 1..5
|
10
|
-
response 200 do
|
11
|
-
status # => 200
|
12
|
-
headers['Content-Type'] # => ['text/html']
|
13
|
-
body # => <html>...
|
14
|
-
doc # => Nokigiri::HTML::Document
|
15
|
-
end
|
16
110
|
end
|
17
111
|
|
112
|
+
# In this request, the user has entered a simple
|
113
|
+
# what and where search. You'll notice that we
|
114
|
+
# aren't specifying a hostname or port number for
|
115
|
+
# these requests. We do that so you use the same
|
116
|
+
# test plan to run tests on machines with different
|
117
|
+
# hostnames. However, this is optional. You can
|
118
|
+
# also specify absolute URIs like this:
|
119
|
+
#
|
120
|
+
# get "http://example.com:8000/search"
|
121
|
+
#
|
18
122
|
get "/search", "Search Results" do
|
19
123
|
wait 2..5
|
124
|
+
|
125
|
+
# Two URI parameters will be included with this
|
126
|
+
# request. Notice that the values for the what
|
127
|
+
# and where parameters come from the resources
|
128
|
+
# we defined. We've specified for random values
|
129
|
+
# to be used, but we could also specify sequential
|
130
|
+
# or reverse sequential values (see XML API).
|
131
|
+
#
|
20
132
|
param :what => random(:search_terms)
|
21
|
-
param :where => random(['Toronto', 'Montreal'])
|
133
|
+
param :where => random(['Toronto', 'Vancouver', 'Montreal'])
|
134
|
+
|
135
|
+
# Each request can also include one or more
|
136
|
+
# optional response blocks. These blocks determine
|
137
|
+
# what should happen when the specified HTTP
|
138
|
+
# status code is returned by the web server.
|
139
|
+
#
|
140
|
+
# For successful responses, we want to parse out
|
141
|
+
# some data from the page. For 404 (Not Found)
|
142
|
+
# responses, we simply want the virtual user to
|
143
|
+
# quit the usecase altogether.
|
144
|
+
#
|
22
145
|
response 200 do
|
146
|
+
|
147
|
+
# If the response contains HTML, it will
|
148
|
+
# automatically be parsed using the Ruby
|
149
|
+
# library Nokogiri. See the following link
|
150
|
+
# for more information:
|
151
|
+
# http://nokogiri.rubyforge.org/nokogiri/
|
152
|
+
#
|
153
|
+
# The important thing to note is that you
|
154
|
+
# don't need to write complex regular
|
155
|
+
# expressions to grab data from the page.
|
156
|
+
#
|
23
157
|
listing = doc.css('div.listing').first
|
158
|
+
|
159
|
+
# Here we grab the first listing ID on the
|
160
|
+
# page and store it in a variable called
|
161
|
+
# :lid. This is similar to a resource.
|
162
|
+
#
|
24
163
|
set :lid, listing['id'].match(/(\d+)/)[0]
|
25
164
|
end
|
26
165
|
response 404 do
|
@@ -28,37 +167,66 @@ usecase 65, "Simple search" do
|
|
28
167
|
end
|
29
168
|
end
|
30
169
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
170
|
+
# Notice the special variable in this URI path.
|
171
|
+
# Since we have defined a variable with the same
|
172
|
+
# name in the previous request, the variable in
|
173
|
+
# the request will automatically be replaced with
|
174
|
+
# that value. This value is unique for each virtual
|
175
|
+
# user.
|
176
|
+
#
|
177
|
+
get "/listing/:lid" do
|
178
|
+
desc "Selected listing"
|
179
|
+
wait 1..8
|
180
|
+
end
|
35
181
|
|
36
182
|
end
|
37
183
|
|
38
|
-
|
39
|
-
post "/listing/add", "Add a listing" do
|
40
|
-
wait 1..4
|
41
|
-
param :name => random(8)
|
42
|
-
param :city => sequential("Montreal", "Toronto", "Vancouver")
|
43
|
-
param :logo => file('logo.png')
|
44
|
-
response 302 do
|
45
|
-
repeat 3
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
usecase "Listing API" do
|
184
|
+
usecase 25, "YAML API" do
|
51
185
|
|
52
186
|
get '/listings.yaml', "View All" do
|
53
187
|
response 200 do
|
54
|
-
|
188
|
+
|
189
|
+
# We showed above how HTML is parsed automatically.
|
190
|
+
# Stella can do the same with XML, YAML, and JSON.
|
191
|
+
#
|
192
|
+
# A variable called "doc" contains the parsed YAML.
|
193
|
+
# "collect" is a Ruby method that iterates through
|
194
|
+
# all items in an Array and returns a new Array
|
195
|
+
# containing the return values from each iteration.
|
196
|
+
# Each item item is available in the variable
|
197
|
+
# called 'l'.
|
198
|
+
#
|
199
|
+
# The variable called "listings" will contain all
|
200
|
+
# listing ids in the YAML document.
|
201
|
+
#
|
55
202
|
listings = doc.collect! { |l|; l[:id]; }
|
56
|
-
|
203
|
+
|
204
|
+
# And here we store that list of ids.
|
205
|
+
#
|
206
|
+
set :listing_ids, listings
|
57
207
|
end
|
58
208
|
end
|
59
209
|
|
60
|
-
|
61
|
-
|
210
|
+
# In the Simple Search usecase we stored a listing
|
211
|
+
# id in a variable called :lid. This time we have
|
212
|
+
# an Array of listing ids but we only want to use
|
213
|
+
# one for each request so we override the automatic
|
214
|
+
# variable replacement by using the param method.
|
215
|
+
#
|
216
|
+
get "/listing/:lid.yaml", "Select Listing" do
|
217
|
+
|
218
|
+
# Each request will use a new value from the
|
219
|
+
# Array and these will be selected sequentially.
|
220
|
+
# Note that this is _per virtual user_. Each
|
221
|
+
# vuser will have its own copy of the Array and
|
222
|
+
# iterate through it independently.
|
223
|
+
#
|
224
|
+
param :lid => rsequential(:listing_ids)
|
225
|
+
|
226
|
+
# We can use response blocks to affect behaviour
|
227
|
+
# the user. Here we specify that every virtual
|
228
|
+
# user should repeat this request 7 times.
|
229
|
+
#
|
62
230
|
response 200 do
|
63
231
|
repeat 7
|
64
232
|
end
|
@@ -66,3 +234,22 @@ usecase "Listing API" do
|
|
66
234
|
|
67
235
|
end
|
68
236
|
|
237
|
+
usecase 10, "Self-serve API" do
|
238
|
+
|
239
|
+
# Here is an example of using a POST request.
|
240
|
+
# Notice that the definition is otherwise
|
241
|
+
# identical to the ones you've seen above.
|
242
|
+
#
|
243
|
+
post "/listing/add", "Add a listing" do
|
244
|
+
wait 1..4
|
245
|
+
param :name => random(8)
|
246
|
+
param :city => random(['Toronto', 'Vancouver', 'Montreal'])
|
247
|
+
param :logo => file('logo.png')
|
248
|
+
response 302 do
|
249
|
+
repeat 3
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
# a5689bb64829d2dc1e9ab8901223cc90c975fe3a
|
data/examples/example_webapp.rb
CHANGED
@@ -73,7 +73,11 @@ post '/listing/add' do
|
|
73
73
|
else
|
74
74
|
@listings = options.listings
|
75
75
|
if find_name(params[:name], @listings).empty?
|
76
|
-
|
76
|
+
# Limit the number of in memory listings, but we want to keep
|
77
|
+
# the original listings so we use a slice instead of a shift.
|
78
|
+
if @listings.size >= options.max_listings
|
79
|
+
@listings.slice!(LISTINGS.size)
|
80
|
+
end
|
77
81
|
@listings << { :name => params[:name], :id => rand(100000), :city => params[:city] }
|
78
82
|
if params[:logo].is_a?(Hash) && params[:logo][:tempfile]
|
79
83
|
p "TODO: Fix uploads"
|
@@ -130,7 +134,8 @@ before do
|
|
130
134
|
@cookie[:history].unshift params[:what] unless blank?(params[:what])
|
131
135
|
@cookie[:history].pop if @cookie[:history].size > 5
|
132
136
|
@cookie[:location] = params[:where] unless blank?(params[:where])
|
133
|
-
response.set_cookie "bff-history",
|
137
|
+
response.set_cookie "bff-history",
|
138
|
+
:value => @cookie.to_yaml#, :expires => Time.parse('16/02/2012')
|
134
139
|
end
|
135
140
|
|
136
141
|
helpers do
|
@@ -177,11 +182,16 @@ __END__
|
|
177
182
|
@@layout
|
178
183
|
<html>
|
179
184
|
<head>
|
185
|
+
<!--
|
186
|
+
Param: __stella: <%= params['__stella'] %>
|
187
|
+
Header: X-Stella-ID: <%= env['HTTP_X_STELLA_ID'] %>
|
188
|
+
-->
|
180
189
|
<title><%= @title %></title>
|
181
190
|
<style>
|
182
191
|
.hilite { background-color: #FEE00B; font-weight: bold; }
|
183
192
|
.footer { color: #ccc; font-weight: lighter; font-size: 80%; margin-top: 30px; }
|
184
|
-
.footer a { color: #69c;}
|
193
|
+
.footer a { color: #69c; }
|
194
|
+
body { background: url('http://solutious.com/images/solutious-logo-large.png?1') no-repeat right;}
|
185
195
|
</style>
|
186
196
|
</head>
|
187
197
|
<body>
|
@@ -194,7 +204,7 @@ __END__
|
|
194
204
|
</em></p>
|
195
205
|
<%= yield %>
|
196
206
|
<div class="footer">
|
197
|
-
A <a href="http://solutious.com/
|
207
|
+
A <a href="http://solutious.com/">Solutious Inc</a> production.
|
198
208
|
</div>
|
199
209
|
</body>
|
200
210
|
</html>
|
data/examples/example_webapp.ru
CHANGED
data/examples/exceptions/plan.rb
CHANGED
@@ -3,16 +3,10 @@
|
|
3
3
|
usecase "Exception Handling" do
|
4
4
|
|
5
5
|
get "/search", "Search Results" do
|
6
|
-
|
7
|
-
param :what => random()
|
6
|
+
param :what => 'No Such Listing'
|
8
7
|
param :where => random('Toronto', 'Montreal', 'Vancouver')
|
9
|
-
response 200 do
|
10
|
-
listing = doc.css('div.listing').first
|
11
|
-
set :lid, listing['id'].match(/(\d+)/)[0]
|
12
|
-
raise NoListingResultFound if listing.nil?
|
13
|
-
end
|
14
8
|
response 404 do
|
15
|
-
raise NoSearchResults
|
9
|
+
raise NoSearchResults
|
16
10
|
end
|
17
11
|
end
|
18
12
|
|
data/lib/stella/cli.rb
CHANGED
@@ -15,7 +15,7 @@ class Stella::CLI < Drydock::Command
|
|
15
15
|
def verify
|
16
16
|
opts = {}
|
17
17
|
opts[:hosts] = @hosts
|
18
|
-
opts[:
|
18
|
+
opts[:nowait] = true if @option.nowait
|
19
19
|
ret = Stella::Engine::Functional.run @testplan, opts
|
20
20
|
@exit_code = (ret ? 0 : 1)
|
21
21
|
end
|
@@ -27,7 +27,7 @@ class Stella::CLI < Drydock::Command
|
|
27
27
|
def load
|
28
28
|
opts = {}
|
29
29
|
opts[:hosts] = @hosts
|
30
|
-
[:
|
30
|
+
[:nowait, :clients, :repetitions, :delay, :time].each do |opt|
|
31
31
|
opts[opt] = @option.send(opt) unless @option.send(opt).nil?
|
32
32
|
end
|
33
33
|
ret = Stella::Engine::Load.run @testplan, opts
|
@@ -39,7 +39,10 @@ class Stella::CLI < Drydock::Command
|
|
39
39
|
unless @option.testplan.nil? || File.exists?(@option.testplan)
|
40
40
|
raise Stella::InvalidOption, "Bad path: #{@option.testplan}"
|
41
41
|
end
|
42
|
-
@hosts = @argv.collect { |uri|;
|
42
|
+
@hosts = @argv.collect { |uri|;
|
43
|
+
uri = 'http://' << uri unless uri.match /^http:\/\//i
|
44
|
+
URI.parse uri;
|
45
|
+
}
|
43
46
|
if @option.testplan
|
44
47
|
@testplan = Stella::Testplan.load_file @option.testplan
|
45
48
|
else
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
class Stella::Client
|
4
|
+
|
5
|
+
class Container
|
6
|
+
|
7
|
+
attr_accessor :usecase
|
8
|
+
attr_accessor :response
|
9
|
+
attr_reader :resources
|
10
|
+
def initialize(usecase)
|
11
|
+
@usecase, @resources = usecase, {}
|
12
|
+
@base_path = usecase.base_path
|
13
|
+
end
|
14
|
+
|
15
|
+
# This is used to handle custom exception in usecases.
|
16
|
+
# See examples/exceptions/plan.rb
|
17
|
+
#
|
18
|
+
def self.const_missing(custom_error)
|
19
|
+
ResponseError.new custom_error
|
20
|
+
end
|
21
|
+
|
22
|
+
def doc
|
23
|
+
# NOTE: It's important to parse the document on every
|
24
|
+
# request because this container is available for the
|
25
|
+
# entire life of a usecase.
|
26
|
+
case @response.header['Content-Type']
|
27
|
+
when ['text/html']
|
28
|
+
Nokogiri::HTML(body)
|
29
|
+
when ['text/yaml']
|
30
|
+
YAML.load(body)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def resource(n)
|
35
|
+
return @usecase.resource(n) if @usecase.resources.has_key? n
|
36
|
+
return @resources[n] if @resources.has_key? n
|
37
|
+
end
|
38
|
+
|
39
|
+
def body; @response.body.content; end
|
40
|
+
def headers; @response.header; end
|
41
|
+
alias_method :header, :headers
|
42
|
+
def status; @response.status; end
|
43
|
+
def set(n, v); @resources[n] = v; end
|
44
|
+
def wait(t); sleep t; end
|
45
|
+
def quit(msg=nil); Quit.new(msg); end
|
46
|
+
def repeat(t=1); Repeat.new(t); end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
class Stella::Client
|
4
|
+
|
5
|
+
class ResponseModifier; end
|
6
|
+
class Repeat < ResponseModifier;
|
7
|
+
attr_accessor :times
|
8
|
+
def initialize(times)
|
9
|
+
@times = times
|
10
|
+
end
|
11
|
+
end
|
12
|
+
class Quit < ResponseModifier;
|
13
|
+
attr_accessor :message
|
14
|
+
def initialize(msg=nil)
|
15
|
+
@message = msg
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|