solutious-stella 0.6.0 → 0.7.0.001
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +3 -15
- data/LICENSE.txt +1 -1
- data/README.rdoc +90 -60
- data/Rakefile +32 -42
- data/bin/stella +138 -0
- data/examples/basic/listing_ids.csv +7 -0
- data/examples/basic/plan.rb +71 -0
- data/lib/stella.rb +57 -104
- data/lib/stella/cli.rb +66 -0
- data/lib/stella/client.rb +197 -0
- data/lib/stella/config.rb +87 -0
- data/lib/stella/data.rb +85 -0
- data/lib/stella/data/http.rb +2 -257
- data/lib/stella/data/http/body.rb +15 -0
- data/lib/stella/data/http/request.rb +116 -0
- data/lib/stella/data/http/response.rb +92 -0
- data/lib/stella/dsl.rb +5 -0
- data/lib/stella/engine.rb +55 -0
- data/lib/stella/engine/functional.rb +39 -0
- data/lib/stella/engine/load.rb +106 -0
- data/lib/stella/exceptions.rb +15 -0
- data/lib/stella/guidelines.rb +18 -0
- data/lib/stella/mixins.rb +2 -0
- data/lib/stella/stats.rb +3 -7
- data/lib/stella/testplan.rb +95 -220
- data/lib/stella/testplan/stats.rb +26 -0
- data/lib/stella/testplan/usecase.rb +67 -0
- data/lib/stella/utils.rb +126 -0
- data/lib/{util → stella/utils}/httputil.rb +0 -0
- data/lib/stella/version.rb +15 -0
- data/lib/threadify.rb +0 -6
- data/stella.gemspec +43 -49
- data/support/example_webapp.rb +246 -0
- data/support/useragents.txt +75 -0
- metadata +66 -31
- data/bin/example_test.rb +0 -82
- data/bin/example_webapp.rb +0 -63
- data/lib/logger.rb +0 -79
- data/lib/stella/clients.rb +0 -161
- data/lib/stella/command/base.rb +0 -20
- data/lib/stella/command/form.rb +0 -36
- data/lib/stella/command/get.rb +0 -44
- data/lib/stella/common.rb +0 -53
- data/lib/stella/crypto.rb +0 -88
- data/lib/stella/data/domain.rb +0 -82
- data/lib/stella/environment.rb +0 -66
- data/lib/stella/functest.rb +0 -105
- data/lib/stella/loadtest.rb +0 -186
- data/lib/stella/testrunner.rb +0 -64
- data/lib/storable.rb +0 -280
- data/lib/timeunits.rb +0 -65
- data/tryouts/drb/drb_test.rb +0 -65
- data/tryouts/drb/open4.rb +0 -19
- data/tryouts/drb/slave.rb +0 -27
- data/tryouts/oo_tryout.rb +0 -30
@@ -0,0 +1,71 @@
|
|
1
|
+
# 1f5e852e934debd56aa98552c0a9227c93006f21
|
2
|
+
|
3
|
+
desc "Business Finder Testplan"
|
4
|
+
|
5
|
+
usecase 65, "Simple search" do
|
6
|
+
|
7
|
+
get "/", "Homepage" do
|
8
|
+
wait 1..5
|
9
|
+
response 200 do
|
10
|
+
status # => 200
|
11
|
+
headers['Content-Type'] # => ['text/html']
|
12
|
+
body # => <html>...
|
13
|
+
doc # => Nokigiri::HTML::Document
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
get "/search", "Search Results" do
|
18
|
+
wait 2..5
|
19
|
+
param :what => 'Big'
|
20
|
+
param :where => ''
|
21
|
+
response 200 do
|
22
|
+
listing = doc.css('div.listing').first
|
23
|
+
set :lid, listing['id'].match(/(\d+)/)[0]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
get "/listing/:lid" do # URIs can contain variables.
|
28
|
+
desc "Selected listing" # This one will be replaced by
|
29
|
+
wait 1..8 # the one stored in the previous
|
30
|
+
end # request.
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
usecase "YAML API" do
|
35
|
+
resource :preset_listing_ids, list('listing_ids.csv')
|
36
|
+
|
37
|
+
get "/listing/:lid.yaml", "Select listing" do
|
38
|
+
param :lid => random(:preset_listing_ids)
|
39
|
+
response 200 do
|
40
|
+
repeat 5
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
get '/listings.yaml', "View All" do
|
45
|
+
response 200 do
|
46
|
+
# doc contains the parsed YAML object
|
47
|
+
listings = doc.collect! { |l|; l[:id]; }
|
48
|
+
set :current_listing_ids, listings
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
get "/listing/:lid.yaml", "Select listing" do
|
53
|
+
param :lid => rsequential(:current_listing_ids)
|
54
|
+
response 200 do
|
55
|
+
repeat 7
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
usecase 10, "Advertiser self-serve" do
|
62
|
+
post "/listing/add" do
|
63
|
+
desc "Add a business"
|
64
|
+
wait 1..4
|
65
|
+
param :name => random(8)
|
66
|
+
param :city => "Vancouver"
|
67
|
+
response 302 do
|
68
|
+
repeat 3
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/stella.rb
CHANGED
@@ -1,119 +1,72 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
unless defined?(STELLA_LIB_HOME)
|
3
|
+
STELLA_LIB_HOME = File.expand_path File.dirname(__FILE__)
|
4
|
+
end
|
5
|
+
|
6
|
+
local_libs = %w{drydock storable sysinfo gibbler}
|
7
|
+
local_libs.each { |dir| $:.unshift File.join(STELLA_LIB_HOME, '..', '..', dir, 'lib') }
|
8
|
+
#require 'rubygems'
|
8
9
|
|
9
10
|
require 'storable'
|
10
|
-
require '
|
11
|
+
require 'sysinfo'
|
12
|
+
require 'gibbler'
|
13
|
+
require 'gibbler/aliases'
|
14
|
+
require 'ostruct'
|
11
15
|
require 'threadify'
|
12
|
-
require '
|
13
|
-
|
14
|
-
require 'stella/crypto'
|
15
|
-
|
16
|
-
require 'stella/common'
|
17
|
-
|
18
|
-
require 'stella/data/http'
|
19
|
-
require 'stella/data/domain'
|
20
|
-
|
21
|
-
require 'stella/environment'
|
22
|
-
require 'stella/clients'
|
23
|
-
require 'stella/testrunner'
|
24
|
-
require 'stella/testplan'
|
25
|
-
require 'stella/loadtest'
|
26
|
-
require 'stella/functest'
|
27
|
-
|
28
|
-
srand
|
29
|
-
|
30
|
-
# Common dependencies
|
31
|
-
STELLA_HOME = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
32
|
-
$: << File.join(STELLA_HOME, 'vendor', 'useragent', 'lib')
|
16
|
+
require 'drydock/screen'
|
33
17
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
|
53
|
-
def self.debug_level
|
54
|
-
Stella::LOGGER.debug_level
|
55
|
-
end
|
18
|
+
module Stella
|
19
|
+
extend self
|
20
|
+
require 'stella/version'
|
21
|
+
require 'stella/exceptions'
|
22
|
+
require 'stella/utils'
|
23
|
+
require 'stella/stats'
|
24
|
+
require 'stella/mixins'
|
25
|
+
require 'stella/dsl'
|
26
|
+
require 'stella/engine'
|
27
|
+
require 'stella/testplan'
|
28
|
+
|
29
|
+
autoload :Utils, STELLA_LIB_HOME + "/stella/utils"
|
30
|
+
autoload :Data, STELLA_LIB_HOME + "/stella/data"
|
31
|
+
autoload :Config, STELLA_LIB_HOME + "/stella/config"
|
32
|
+
autoload :Client, STELLA_LIB_HOME + "/stella/client"
|
56
33
|
|
57
|
-
|
58
|
-
|
59
|
-
|
34
|
+
@@sysinfo = SysInfo.new.freeze
|
35
|
+
|
36
|
+
@@logger = Drydock::Screen
|
37
|
+
@@loglev = 1
|
60
38
|
|
61
|
-
|
62
|
-
|
63
|
-
end
|
39
|
+
# Puts +msg+ to +@@logger+
|
40
|
+
def li(*msg); msg.each { |m| @@logger.puts m } if !quiet? end
|
41
|
+
def li1(*msg); li *msg if @@loglev >= 1 end
|
42
|
+
def li2(*msg); li *msg if @@loglev >= 2 end
|
43
|
+
def li3(*msg); li *msg if @@loglev >= 3 end
|
44
|
+
def li4(*msg); li *msg if @@loglev >= 4 end
|
64
45
|
|
65
|
-
|
66
|
-
|
46
|
+
# Puts +msg+ to +@@logger+ with "ERROR: " prepended
|
47
|
+
def le(*msg); @@logger.puts " " << msg.join("#{$/} ").color(:red); end
|
48
|
+
# Puts +msg+ to +@@logger+ if +Rudy.debug?+ returns true
|
49
|
+
def ld(*msg)
|
50
|
+
@@logger.puts "D: " << msg.join("#{$/}D: ") if debug?
|
67
51
|
end
|
68
52
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
end
|
53
|
+
def loglev; @@loglev; end
|
54
|
+
def loglev=(val); @@loglev = val; end
|
55
|
+
def sysinfo; @@sysinfo; end
|
73
56
|
|
74
|
-
def
|
75
|
-
|
76
|
-
end
|
77
|
-
end
|
57
|
+
def quiet?; @@loglev == 0; end
|
58
|
+
def enable_quiet; @@loglev = 0; end
|
59
|
+
def disable_quiet; @@loglev = 1; end
|
78
60
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
#
|
87
|
-
|
61
|
+
def debug?; @@loglev > 3; end
|
62
|
+
def enable_debug; @@loglev = 4; end
|
63
|
+
def disable_debug; @@loglev = 1; end
|
64
|
+
|
65
|
+
def rescue(&blk)
|
66
|
+
blk.call
|
67
|
+
rescue => ex
|
68
|
+
Stella.le "ERROR: #{ex.message}"
|
69
|
+
Stella.ld ex.backtrace
|
88
70
|
end
|
89
71
|
end
|
90
72
|
|
91
|
-
class Object #:nodoc: all
|
92
|
-
# The hidden singleton lurks behind everyone
|
93
|
-
def metaclass; class << self; self; end; end
|
94
|
-
def meta_eval &blk; metaclass.instance_eval &blk; end
|
95
|
-
|
96
|
-
# Adds methods to a metaclass
|
97
|
-
def meta_def name, &blk
|
98
|
-
meta_eval { define_method name, &blk }
|
99
|
-
end
|
100
|
-
|
101
|
-
# Defines an instance method within a class
|
102
|
-
def class_def name, &blk
|
103
|
-
class_eval { define_method name, &blk }
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
class Array
|
109
|
-
# create a hash from an array of [key,value] tuples
|
110
|
-
# you can set default or provide a block just as with Hash::new
|
111
|
-
# Note: if you use [key, value1, value2, value#], hash[key] will
|
112
|
-
# be [value1, value2, value#]
|
113
|
-
# From: http://www.ruby-forum.com/topic/138218#615260
|
114
|
-
def stella_to_hash(default=nil, &block)
|
115
|
-
hash = block_given? ? Hash.new(&block) : Hash.new(default)
|
116
|
-
each { |(key, *value)| hash[key]=*value }
|
117
|
-
hash
|
118
|
-
end
|
119
|
-
end
|
data/lib/stella/cli.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
class Stella::CLI < Drydock::Command
|
4
|
+
|
5
|
+
def init
|
6
|
+
@conf = Stella::Config.refresh
|
7
|
+
end
|
8
|
+
|
9
|
+
def verify_valid?
|
10
|
+
create_testplan
|
11
|
+
end
|
12
|
+
|
13
|
+
def verify
|
14
|
+
opts = {}
|
15
|
+
opts[:hosts] = @hosts
|
16
|
+
opts[:benchmark] = true if @option.benchmark
|
17
|
+
Stella::Engine::Functional.run @testplan, opts
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_valid?
|
21
|
+
create_testplan
|
22
|
+
end
|
23
|
+
|
24
|
+
def load
|
25
|
+
opts = {}
|
26
|
+
opts[:hosts] = @hosts
|
27
|
+
[:benchmark, :users, :repetitions, :delay, :time].each do |opt|
|
28
|
+
opts[opt] = @option.send(opt) unless @option.send(opt).nil?
|
29
|
+
end
|
30
|
+
Stella::Engine::Load.run @testplan, opts
|
31
|
+
end
|
32
|
+
|
33
|
+
def preview_valid?
|
34
|
+
create_testplan
|
35
|
+
end
|
36
|
+
|
37
|
+
def preview
|
38
|
+
Stella.li2 "file: #{@option.testplan} (#{@testplan.digest})"
|
39
|
+
Stella.li @testplan.pretty
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
private
|
44
|
+
def create_testplan
|
45
|
+
@hosts = @argv.collect { |uri|; URI.parse uri; }
|
46
|
+
if @option.testplan
|
47
|
+
@testplan = Stella::Testplan.load_file @option.testplan
|
48
|
+
else
|
49
|
+
@testplan = Stella::Testplan.new
|
50
|
+
usecase = Stella::Testplan::Usecase.new
|
51
|
+
@argv.each do |uri|
|
52
|
+
uri = URI.parse uri
|
53
|
+
uri.path = '/' if uri.path.empty?
|
54
|
+
req = usecase.add_request :get, uri.path
|
55
|
+
req.wait = @option.delay if @option.delay
|
56
|
+
end
|
57
|
+
@testplan.add_usecase usecase
|
58
|
+
end
|
59
|
+
@testplan.check! # raise errors, update usecase ratios
|
60
|
+
Stella.ld "PLANHASH: #{@testplan.digest}"
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require "observer"
|
2
|
+
require "tempfile"
|
3
|
+
require 'httpclient'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
module Stella
|
7
|
+
class Client
|
8
|
+
include Observable
|
9
|
+
attr_reader :client_id
|
10
|
+
attr_accessor :base_uri
|
11
|
+
attr_accessor :proxy
|
12
|
+
attr_reader :stats
|
13
|
+
def initialize(base_uri=nil, client_id=1)
|
14
|
+
@base_uri, @client_id = base_uri, client_id
|
15
|
+
@cookie_file = Tempfile.new('stella-cookie')
|
16
|
+
@stats = Stella::Stats.new("Client #{@client_id}")
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute(usecase)
|
20
|
+
http_client = generate_http_client
|
21
|
+
container = Container.new(usecase)
|
22
|
+
counter = 0
|
23
|
+
usecase.requests.each do |req|
|
24
|
+
counter += 1
|
25
|
+
uri_obj = URI.parse(req.uri)
|
26
|
+
params = prepare_params(usecase, req.params)
|
27
|
+
uri = build_request_uri uri_obj, params, container
|
28
|
+
raise NoHostDefined, uri_obj if uri.host.nil? || uri.host.empty?
|
29
|
+
|
30
|
+
meth = req.http_method.to_s.downcase
|
31
|
+
Stella.ld "#{meth}: " << "#{uri_obj.to_s} " << req.params.inspect
|
32
|
+
|
33
|
+
changed and notify_observers(:send_request, @client_id, usecase, meth, uri, req, params, counter)
|
34
|
+
begin
|
35
|
+
container.response = http_client.send(meth, uri, params) # booya!
|
36
|
+
changed and notify_observers(:receive_response, @client_id, usecase, meth, uri, req, params, container)
|
37
|
+
rescue => ex
|
38
|
+
changed and notify_observers(:request_error, @client_id, usecase, meth, uri, req, params, ex)
|
39
|
+
next
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
ret = execute_response_handler container, req
|
44
|
+
|
45
|
+
Drydock::Screen.flush
|
46
|
+
|
47
|
+
if ret.kind_of?(ResponseModifier)
|
48
|
+
case ret.class.to_s
|
49
|
+
when "Stella::Client::Repeat"
|
50
|
+
Stella.ld "REPETITION: #{counter} of #{ret.times+1}"
|
51
|
+
redo if counter <= ret.times
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
counter = 0 # reset
|
56
|
+
run_sleeper(req.wait) if req.wait && !benchmark?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def enable_benchmark_mode; @bm = true; end
|
61
|
+
def disable_benchmark_mode; @bm = false; end
|
62
|
+
def benchmark?; @bm == true; end
|
63
|
+
|
64
|
+
private
|
65
|
+
def run_sleeper(wait)
|
66
|
+
if wait.is_a?(Range)
|
67
|
+
ms = rand(wait.last * 1000).to_f
|
68
|
+
ms = wait.first if ms < wait.first
|
69
|
+
else
|
70
|
+
ms = wait * 1000
|
71
|
+
end
|
72
|
+
sleep ms / 1000
|
73
|
+
end
|
74
|
+
|
75
|
+
def generate_http_client
|
76
|
+
if @proxy
|
77
|
+
http_client = HTTPClient.new(@proxy.uri)
|
78
|
+
http_client.set_proxy_auth(@proxy.user, @proxy.pass) if @proxy.user
|
79
|
+
else
|
80
|
+
http_client = HTTPClient.new
|
81
|
+
end
|
82
|
+
http_client.set_cookie_store @cookie_file.to_s
|
83
|
+
http_client
|
84
|
+
end
|
85
|
+
|
86
|
+
def prepare_params(usecase, params)
|
87
|
+
newparams = {}
|
88
|
+
params.each_pair do |n,v|
|
89
|
+
v = usecase.instance_eval &v if v.is_a?(Proc)
|
90
|
+
newparams[n] = v
|
91
|
+
end
|
92
|
+
newparams
|
93
|
+
end
|
94
|
+
|
95
|
+
# Testplan URIs can be relative or absolute. Either one can
|
96
|
+
# contain variables in the form <tt>:varname</tt>, as in:
|
97
|
+
#
|
98
|
+
# http://example.com/product/:productid
|
99
|
+
#
|
100
|
+
# This method creates a new URI object using the @base_uri
|
101
|
+
# if necessary and replaces all variables with literal values.
|
102
|
+
# If no replacement value can be found, the variable is not touched.
|
103
|
+
def build_request_uri(requri, params, container)
|
104
|
+
uri = ""
|
105
|
+
request_uri = requri.to_s
|
106
|
+
if requri.host.nil?
|
107
|
+
uri = base_uri.to_s
|
108
|
+
uri.gsub! /\/$/, '' # Don't double up on the first slash
|
109
|
+
request_uri = '/' << request_uri unless request_uri.match(/^\//)
|
110
|
+
end
|
111
|
+
# We call req.uri again because we need
|
112
|
+
# to modify request_uri inside the loop.
|
113
|
+
requri.to_s.scan(/:([a-z_]+)/i) do |instances|
|
114
|
+
instances.each do |varname|
|
115
|
+
val = find_replacement_value(varname, params, container)
|
116
|
+
#Stella.ld "FOUND: #{val}"
|
117
|
+
request_uri.gsub! /:#{varname}/, val.to_s unless val.nil?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
uri << request_uri
|
121
|
+
URI.parse uri
|
122
|
+
end
|
123
|
+
|
124
|
+
# Testplan URIs can contain variables in the form <tt>:varname</tt>.
|
125
|
+
# This method looks at the request parameters and then at the
|
126
|
+
# usecase's resource hash for a replacement value.
|
127
|
+
# If not found, returns nil.
|
128
|
+
def find_replacement_value(name, params, container)
|
129
|
+
value = nil
|
130
|
+
#Stella.ld "REPLACE: #{name}"
|
131
|
+
#Stella.ld "PARAMS: #{params.inspect}"
|
132
|
+
#Stella.ld "IVARS: #{container.instance_variables}"
|
133
|
+
value = params[name.to_sym]
|
134
|
+
value = container.resource name.to_sym if value.nil?
|
135
|
+
value
|
136
|
+
end
|
137
|
+
|
138
|
+
# Find the appropriate response handler by executing the
|
139
|
+
# HTTP response status against the configured handlers.
|
140
|
+
# If several match, the first one is used.
|
141
|
+
def execute_response_handler(container, req)
|
142
|
+
handlers = req.response.select do |regex,handler|
|
143
|
+
regex = /#{regex}/ unless regex.is_a? Regexp
|
144
|
+
Stella.ld "HANDLER REGEX: #{regex} (#{container.status})"
|
145
|
+
container.status.to_s =~ regex
|
146
|
+
end
|
147
|
+
ret = nil
|
148
|
+
unless handlers.empty?
|
149
|
+
begin
|
150
|
+
changed
|
151
|
+
ret = container.instance_eval &handlers.values.first
|
152
|
+
notify_observers(:execute_response_handler, @client_id, req, container)
|
153
|
+
rescue => ex
|
154
|
+
notify_observers(:error_execute_response_handler, @client_id, ex, req, container)
|
155
|
+
Stella.ld ex.message, ex.backtrace
|
156
|
+
end
|
157
|
+
end
|
158
|
+
ret
|
159
|
+
end
|
160
|
+
|
161
|
+
class Container
|
162
|
+
attr_accessor :usecase
|
163
|
+
attr_accessor :response
|
164
|
+
def initialize(usecase)
|
165
|
+
@usecase = usecase
|
166
|
+
end
|
167
|
+
|
168
|
+
def doc
|
169
|
+
@container_doc and return @container_doc
|
170
|
+
@container_doc = case @response.header['Content-Type']
|
171
|
+
when ['text/html']
|
172
|
+
Nokogiri::HTML(body)
|
173
|
+
when ['text/yaml']
|
174
|
+
YAML.load(body)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def body; @response.body.content; end
|
179
|
+
def headers; @response.header; end
|
180
|
+
alias_method :header, :headers
|
181
|
+
def status; @response.status; end
|
182
|
+
def set(n, v); usecase.resource n, v; end
|
183
|
+
def resource(n); usecase.resource n; end
|
184
|
+
def wait(t); sleep t; end
|
185
|
+
|
186
|
+
def repeat(t=1); Repeat.new(t); end
|
187
|
+
end
|
188
|
+
|
189
|
+
class ResponseModifier; end
|
190
|
+
class Repeat < ResponseModifier;
|
191
|
+
attr_accessor :times
|
192
|
+
def initialize(times)
|
193
|
+
@times = times
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|