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.
Files changed (55) hide show
  1. data/CHANGES.txt +3 -15
  2. data/LICENSE.txt +1 -1
  3. data/README.rdoc +90 -60
  4. data/Rakefile +32 -42
  5. data/bin/stella +138 -0
  6. data/examples/basic/listing_ids.csv +7 -0
  7. data/examples/basic/plan.rb +71 -0
  8. data/lib/stella.rb +57 -104
  9. data/lib/stella/cli.rb +66 -0
  10. data/lib/stella/client.rb +197 -0
  11. data/lib/stella/config.rb +87 -0
  12. data/lib/stella/data.rb +85 -0
  13. data/lib/stella/data/http.rb +2 -257
  14. data/lib/stella/data/http/body.rb +15 -0
  15. data/lib/stella/data/http/request.rb +116 -0
  16. data/lib/stella/data/http/response.rb +92 -0
  17. data/lib/stella/dsl.rb +5 -0
  18. data/lib/stella/engine.rb +55 -0
  19. data/lib/stella/engine/functional.rb +39 -0
  20. data/lib/stella/engine/load.rb +106 -0
  21. data/lib/stella/exceptions.rb +15 -0
  22. data/lib/stella/guidelines.rb +18 -0
  23. data/lib/stella/mixins.rb +2 -0
  24. data/lib/stella/stats.rb +3 -7
  25. data/lib/stella/testplan.rb +95 -220
  26. data/lib/stella/testplan/stats.rb +26 -0
  27. data/lib/stella/testplan/usecase.rb +67 -0
  28. data/lib/stella/utils.rb +126 -0
  29. data/lib/{util → stella/utils}/httputil.rb +0 -0
  30. data/lib/stella/version.rb +15 -0
  31. data/lib/threadify.rb +0 -6
  32. data/stella.gemspec +43 -49
  33. data/support/example_webapp.rb +246 -0
  34. data/support/useragents.txt +75 -0
  35. metadata +66 -31
  36. data/bin/example_test.rb +0 -82
  37. data/bin/example_webapp.rb +0 -63
  38. data/lib/logger.rb +0 -79
  39. data/lib/stella/clients.rb +0 -161
  40. data/lib/stella/command/base.rb +0 -20
  41. data/lib/stella/command/form.rb +0 -36
  42. data/lib/stella/command/get.rb +0 -44
  43. data/lib/stella/common.rb +0 -53
  44. data/lib/stella/crypto.rb +0 -88
  45. data/lib/stella/data/domain.rb +0 -82
  46. data/lib/stella/environment.rb +0 -66
  47. data/lib/stella/functest.rb +0 -105
  48. data/lib/stella/loadtest.rb +0 -186
  49. data/lib/stella/testrunner.rb +0 -64
  50. data/lib/storable.rb +0 -280
  51. data/lib/timeunits.rb +0 -65
  52. data/tryouts/drb/drb_test.rb +0 -65
  53. data/tryouts/drb/open4.rb +0 -19
  54. data/tryouts/drb/slave.rb +0 -27
  55. data/tryouts/oo_tryout.rb +0 -30
@@ -0,0 +1,7 @@
1
+ 1000
2
+ 1001
3
+ 1002
4
+ 1003
5
+ 1004
6
+ 1005
7
+ 1006
@@ -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
@@ -1,119 +1,72 @@
1
1
 
2
- require 'date'
3
- require 'time'
4
- require 'rubygems'
5
- require 'logger'
6
- require 'uri'
7
- require 'httpclient'
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 'stella/stats'
11
+ require 'sysinfo'
12
+ require 'gibbler'
13
+ require 'gibbler/aliases'
14
+ require 'ostruct'
11
15
  require 'threadify'
12
- require 'timeunits'
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
- # A friend in performance testing.
36
- module Stella
37
-
38
- LOGGER = Logger.new(:debug_level=>0) unless defined? LOGGER
39
-
40
- module VERSION #:nodoc:
41
- MAJOR = 0.freeze unless defined? MAJOR
42
- MINOR = 6.freeze unless defined? MINOR
43
- TINY = 0.freeze unless defined? TINY
44
- def self.to_s
45
- [MAJOR, MINOR, TINY].join('.')
46
- end
47
- def self.to_f
48
- self.to_s.to_f
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
- def self.debug_level=(level)
58
- Stella::LOGGER.debug_level = level
59
- end
34
+ @@sysinfo = SysInfo.new.freeze
35
+
36
+ @@logger = Drydock::Screen
37
+ @@loglev = 1
60
38
 
61
- def self.info(*args)
62
- LOGGER.info(*args)
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
- def self.error(*args)
66
- LOGGER.error(*args)
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 self.fatal(*args)
70
- LOGGER.error(*args)
71
- exit 1
72
- end
53
+ def loglev; @@loglev; end
54
+ def loglev=(val); @@loglev = val; end
55
+ def sysinfo; @@sysinfo; end
73
56
 
74
- def self.debug(*args)
75
- LOGGER.debug(*args)
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
- module Stella
80
- module DSL
81
- include Stella::DSL::TestPlan
82
- include Stella::DSL::FunctionalTest
83
- include Stella::DSL::LoadTest
84
- include Stella::DSL::Environment
85
- # For Modules
86
- #extend Stella::DSL::TestPlan
87
- #extend Stella::DSL::FunctionalTest
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
@@ -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