stella 0.6.0 → 0.7.0.002

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 +7 -15
  2. data/LICENSE.txt +1 -1
  3. data/README.rdoc +93 -63
  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/cli.rb +66 -0
  9. data/lib/stella/client.rb +199 -0
  10. data/lib/stella/config.rb +87 -0
  11. data/lib/stella/data/http/body.rb +15 -0
  12. data/lib/stella/data/http/request.rb +116 -0
  13. data/lib/stella/data/http/response.rb +92 -0
  14. data/lib/stella/data/http.rb +2 -257
  15. data/lib/stella/data.rb +85 -0
  16. data/lib/stella/dsl.rb +5 -0
  17. data/lib/stella/engine/functional.rb +39 -0
  18. data/lib/stella/engine/load.rb +106 -0
  19. data/lib/stella/engine.rb +55 -0
  20. data/lib/stella/exceptions.rb +15 -0
  21. data/lib/stella/guidelines.rb +18 -0
  22. data/lib/stella/mixins.rb +2 -0
  23. data/lib/stella/stats.rb +3 -7
  24. data/lib/stella/testplan/stats.rb +26 -0
  25. data/lib/stella/testplan/usecase.rb +67 -0
  26. data/lib/stella/testplan.rb +95 -220
  27. data/lib/{util → stella/utils}/httputil.rb +0 -0
  28. data/lib/stella/utils.rb +126 -0
  29. data/lib/stella/version.rb +15 -0
  30. data/lib/stella.rb +58 -104
  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 +68 -32
  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,199 @@
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
+ # NOTE: It's important to parse the document on every
170
+ # request because this container is available for the
171
+ # entire life of a usecase.
172
+ case @response.header['Content-Type']
173
+ when ['text/html']
174
+ Nokogiri::HTML(body)
175
+ when ['text/yaml']
176
+ YAML.load(body)
177
+ end
178
+ end
179
+
180
+ def body; @response.body.content; end
181
+ def headers; @response.header; end
182
+ alias_method :header, :headers
183
+ def status; @response.status; end
184
+ def set(n, v); usecase.resource n, v; end
185
+ def resource(n); usecase.resource n; end
186
+ def wait(t); sleep t; end
187
+
188
+ def repeat(t=1); Repeat.new(t); end
189
+ end
190
+
191
+ class ResponseModifier; end
192
+ class Repeat < ResponseModifier;
193
+ attr_accessor :times
194
+ def initialize(times)
195
+ @times = times
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,87 @@
1
+
2
+
3
+ class Stella::Config < Storable
4
+ include Gibbler::Complex
5
+
6
+ field :source
7
+ field :apikey
8
+ field :secret
9
+
10
+ # Returns true when the current config matches the default config
11
+ def default?; to_hash.gibbler == DEFAULT_CONFIG_HASH; end
12
+
13
+ def self.each_path(&blk)
14
+ [PROJECT_PATH, USER_PATH].each do |path|
15
+ Stella.ld "Loading #{path}"
16
+ blk.call(path) if File.exists? path
17
+ end
18
+ end
19
+
20
+ def self.refresh
21
+ conf = {}
22
+ Stella::Config.each_path do |path|
23
+ tmp = YAML.load_file path
24
+ conf.merge! tmp if tmp
25
+ end
26
+ from_hash conf
27
+ end
28
+
29
+ def self.init
30
+ raise AlreadyInitialized, PROJECT_PATH if File.exists? PROJECT_PATH
31
+ dir = File.dirname USER_PATH
32
+ Dir.mkdir(dir, 0700) unless File.exists? dir
33
+ unless File.exists? USER_PATH
34
+ Stella.li "Creating #{USER_PATH} (Add your credentials here)"
35
+ Stella::Utils.write_to_file(USER_PATH, DEFAULT_CONFIG, 'w', 0600)
36
+ end
37
+
38
+ dir = File.dirname PROJECT_PATH
39
+ Dir.mkdir(dir, 0700) unless File.exists? dir
40
+
41
+ Stella.li "Creating #{PROJECT_PATH}"
42
+ Stella::Utils.write_to_file(PROJECT_PATH, 'target:', 'w', 0600)
43
+ end
44
+
45
+ def self.blast
46
+ if File.exists? USER_PATH
47
+ Stella.li "Blasting #{USER_PATH}"
48
+ FileUtils.rm_rf File.dirname(USER_PATH)
49
+ end
50
+ if File.exists? PROJECT_PATH
51
+ Stella.li "Blasting #{PROJECT_PATH}"
52
+ FileUtils.rm_rf File.dirname(PROJECT_PATH)
53
+ end
54
+ end
55
+
56
+
57
+ private
58
+
59
+ def self.find_project_config
60
+ dir = Dir.pwd.split File::SEPARATOR
61
+ path = nil
62
+ while !dir.empty?
63
+ tmp = File.join(dir.join(File::SEPARATOR), DIR_NAME, 'config')
64
+ Stella.ld " -> looking for #{tmp}"
65
+ path = tmp and break if File.exists? tmp
66
+ dir.pop
67
+ end
68
+ path ||= File.join(Dir.pwd, DIR_NAME, 'config')
69
+ path
70
+ end
71
+
72
+
73
+ unless defined?(DIR_NAME)
74
+ DIR_NAME = Stella.sysinfo.os == :windows ? 'Stella' : '.stella'
75
+ USER_PATH = File.join(Stella.sysinfo.home, DIR_NAME, 'config')
76
+ PROJECT_PATH = Stella::Config.find_project_config
77
+ DEFAULT_CONFIG = <<CONF
78
+ apikey: ''
79
+ secret: ''
80
+ remote: stella.solutious.com:443
81
+ CONF
82
+ DEFAULT_CONFIG_HASH = YAML.load(DEFAULT_CONFIG).gibbler
83
+ end
84
+
85
+ class AlreadyInitialized < Stella::Error; end
86
+ end
87
+
@@ -0,0 +1,15 @@
1
+ module Stella::Data::HTTP
2
+ class Body < Storable
3
+ include Gibbler::Complex
4
+
5
+ field :content_type
6
+ field :form_param
7
+ field :content
8
+
9
+ def has_content?
10
+ !@content.nil?
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,116 @@
1
+
2
+
3
+ module Stella::Data::HTTP
4
+ class Request < Storable
5
+ include Gibbler::Complex
6
+ include Stella::Data::Helpers
7
+
8
+ # A hash containing blocks to be executed depending on the HTTP response status.
9
+ # The hash keys are numeric HTTP Status Codes.
10
+ #
11
+ # 200 => { ... }
12
+ # 304 => { ... }
13
+ # 500 => { ... }
14
+ #
15
+ attr_accessor :response_handler
16
+
17
+ field :desc
18
+ field :header
19
+ field :uri
20
+ field :wait
21
+ field :params
22
+ field :body
23
+ field :http_method
24
+ field :http_version
25
+ field :content_type
26
+
27
+ def has_body?
28
+ !@body.nil? && !@body.empty?
29
+ end
30
+
31
+ def initialize (method, uri_str, version="1.1", &definition)
32
+ @uri = uri_str
33
+ @http_method, @http_version = method, version
34
+ @headers, @params, @response_handler = {}, {}, {}
35
+ @wait = 0
36
+ @desc = "Request"
37
+ @body = Stella::Data::HTTP::Body.new
38
+ instance_eval &definition unless definition.nil?
39
+ end
40
+
41
+ def desc(*args)
42
+ @desc = args.first unless args.empty?
43
+ @desc
44
+ end
45
+
46
+ def content_type(*args)
47
+ @content_type = args.first unless args.empty?
48
+ @content_type
49
+ end
50
+
51
+ def wait(*args)
52
+ @wait = args.first unless args.empty?
53
+ @wait
54
+ end
55
+ alias_method :sleep, :wait
56
+
57
+ def headers(*args)
58
+ @headers.merge! args.first unless args.empty?
59
+ @headers
60
+ end
61
+ alias_method :header, :headers
62
+
63
+ def params(*args)
64
+ @params.merge! args.first unless args.empty?
65
+ @params
66
+ end
67
+ alias_method :param, :params
68
+
69
+ def response(*args, &definition)
70
+ if definition.nil?
71
+ @response_handler
72
+ else
73
+ args << 200 if args.empty?
74
+ args.each do |status|
75
+ @response_handler[status] = definition
76
+ end
77
+ end
78
+ end
79
+
80
+ # +content+ can be literal content or a file path
81
+ def body(*args)
82
+ return @body if args.empty?
83
+ content, form_param, content_type = *args
84
+
85
+ @body.form_param = form_param if form_param
86
+ @body.content_type = content_type if content_type
87
+
88
+ if File.exists?(content)
89
+ @body.content = File.new(content)
90
+ @body.content_type ||= "application/x-www-form-urlencoded"
91
+ else
92
+ @body.content = content
93
+ end
94
+
95
+ end
96
+
97
+ def inspect
98
+ str = "%s %s HTTP/%s" % [http_method, uri.to_s, http_version]
99
+ #str << $/ + headers.join($/) unless headers.empty?
100
+ #str << $/ + $/ + body.to_s if body
101
+ str
102
+ end
103
+
104
+ def to_s
105
+ str = "%s %s HTTP/%s" % [http_method, uri.to_s, http_version]
106
+ str
107
+ end
108
+
109
+ def cookies
110
+ return [] if !header.is_a?(Hash) || header[:Cookie].empty?
111
+ header[:Cookie]
112
+ end
113
+
114
+ end
115
+
116
+ end
@@ -0,0 +1,92 @@
1
+
2
+
3
+ module Stella::Data::HTTP
4
+
5
+ class Response < Storable
6
+ include Gibbler::Complex
7
+
8
+ attr_reader :raw_data
9
+
10
+ field :time => DateTime
11
+ field :client_ip => String
12
+ field :server_ip => String
13
+ field :header => String
14
+ field :body => String
15
+ field :status => String
16
+ field :message => String
17
+ field :http_version => String
18
+
19
+ def initialize(raw_data=nil)
20
+ @raw_data = raw_data
21
+ parse(@raw_data)
22
+ end
23
+
24
+ def parse(raw)
25
+ return unless raw
26
+ @status, @http_version, @message, @header, @body = HTTPUtil::parse_http_response(raw)
27
+ end
28
+
29
+ def has_body?
30
+ !@body.nil? && !@body.empty?
31
+ end
32
+ def has_request?
33
+ false
34
+ end
35
+ def has_response?
36
+ false
37
+ end
38
+
39
+
40
+ def body
41
+ return nil unless @body
42
+ #TODO: Move to HTTPResponse::Body.to_s
43
+ if is_binary?
44
+ "[skipping binary content]"
45
+ elsif is_gzip?
46
+ #require 'zlib'
47
+ #Zlib::Inflate.inflate(@body)
48
+ "[skipping gzip content]"
49
+ else
50
+ @body
51
+ end
52
+ end
53
+
54
+ def headers
55
+ headers = []
56
+ header.each_pair do |n,v|
57
+ headers << [n.to_s.gsub('_', '-'), v[0]]
58
+ end
59
+ headers
60
+ end
61
+
62
+ def is_binary?
63
+ (!is_text?) == true
64
+ end
65
+
66
+ def is_text?
67
+ (!header[:Content_Type].nil? && (header[:Content_Type][0].is_a? String) && header[:Content_Type][0][/text/] != nil)
68
+ end
69
+
70
+ def is_gzip?
71
+ (!header[:Content_Encoding].nil? && (header[:Content_Encoding][0].is_a? String) && header[:Content_Encoding][0][/gzip/] != nil)
72
+ end
73
+
74
+ def inspect
75
+ str = "HTTP/%s %s (%s)" % [@http_version, @status, @message]
76
+ str << $/ + headers.join($/)
77
+ str << $/ + $/ + body if body
78
+ str
79
+ end
80
+
81
+ def to_s
82
+ str = "%s: HTTP/%s %s (%s)" % [time.strftime(NICE_TIME_FORMAT), @http_version, @status, @message]
83
+ str
84
+ end
85
+
86
+
87
+ def cookies
88
+ return [] unless header.is_a?(Array) && !header[:Set_Cookie].empty?
89
+ header[:Set_Cookie]
90
+ end
91
+ end
92
+ end