stella 0.6.0 → 0.7.0.002

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.
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,67 @@
1
+
2
+
3
+ module Stella
4
+ class Testplan
5
+
6
+ class Usecase
7
+ include Gibbler::Complex
8
+ attr_accessor :desc
9
+ attr_accessor :requests
10
+ attr_writer :ratio
11
+ attr_accessor :resources
12
+ attr_accessor :base_path
13
+
14
+ def initialize(&blk)
15
+ @requests, @resources = [], {}
16
+ instance_eval &blk unless blk.nil?
17
+ end
18
+
19
+ def desc(*args)
20
+ @desc = args.first unless args.empty?
21
+ @desc
22
+ end
23
+
24
+ def resource(name, value=nil)
25
+ @resources[name] = value unless value.nil?
26
+ @resources[name]
27
+ end
28
+
29
+ def ratio
30
+ r = (@ratio || 0).to_f
31
+ r = r/100 if r > 1
32
+ r
33
+ end
34
+
35
+ # Reads the contents of the file <tt>path</tt> (the current working
36
+ # directory is assumed to be the same directory containing the test plan).
37
+ def file(path)
38
+ path = File.join(@base_path, path) if @base_path
39
+ File.read(path)
40
+ end
41
+
42
+ def list(path)
43
+ file(path).split $/
44
+ end
45
+
46
+ def add_request(meth, *args, &blk)
47
+ req = Stella::Data::HTTP::Request.new meth.to_s.upcase, args[0], &blk
48
+ req.desc = args[1] if args.size > 1 # Description is optional
49
+ Stella.ld req
50
+ @requests << req
51
+ req
52
+ end
53
+ def get(*args, &blk); add_request :get, *args, &blk; end
54
+ def put(*args, &blk); add_request :put, *args, &blk; end
55
+ def post(*args, &blk); add_request :post, *args, &blk; end
56
+ def head(*args, &blk); add_request :head, *args, &blk; end
57
+ def delete(*args, &blk); add_request :delete, *args, &blk; end
58
+
59
+ def xget(*args, &blk); Stella.ld "Skipping get" end
60
+ def xput(*args, &blk); Stella.ld "Skipping put" end
61
+ def xpost(*args, &blk); Stella.ld "Skipping post" end
62
+ def xhead(*args, &blk); Stella.ld "Skipping head" end
63
+ def xdelete(*args, &blk); Stella.ld "Skipping delete" end
64
+
65
+ end
66
+ end
67
+ end
@@ -1,237 +1,112 @@
1
+ require 'stella/testplan/usecase'
2
+ require 'stella/testplan/stats'
1
3
 
2
4
  module Stella
3
- class TestPlan
4
- class ResponseHandler
5
- attr_accessor :action
6
- attr_accessor :times
7
- attr_accessor :wait
8
- def initialize(action, times=1, wait=1)
9
- @action = action
10
- @times = times
11
- @wait = wait
12
- end
13
- end
5
+ class Testplan
6
+ include Gibbler::Complex
7
+
8
+ class WackyRatio < Stella::Error
14
9
  end
15
- end
16
-
17
- module Stella
18
-
19
- class TestPlan
20
- # The name of the testplan.
21
- attr_accessor :name
22
- # A brief description of this testplan
23
- attr_accessor :description
24
- # Used as the default protocol for the testplan. One of: http, https
25
- attr_accessor :protocol
26
- # A Stella::TestPlan::Auth object
27
- attr_accessor :auth
28
- # An array of Stella::TestPlan::Request objects representing all "primary" requests
29
- # for the given test plan (an html page for example). Each primary Request object can have an array of "auxilliary"
30
- # requests which represent dependencies for that resource (javascript, images, callbacks, etc...).
31
- attr_accessor :requests
32
-
33
- def initialize(name=:anonymous)
34
- @name = name
35
- @requests = []
36
- @servers = []
37
- @protocol = "http"
38
- end
39
-
40
10
 
41
- def description
42
- @description
43
- end
44
- def description=(val)
45
- val = val.first if val.is_a? Array
46
- @description = val
11
+
12
+ attr_accessor :usecases
13
+ attr_accessor :base_path
14
+ attr_accessor :desc
15
+ attr_reader :stats
16
+
17
+ def initialize
18
+ @desc, @usecases = "Stella's plan", []
19
+ @testplan_current_ratio = 0
20
+ @stats = Stella::Testplan::Stats.new
21
+ end
22
+
23
+ def self.load_file(path)
24
+ conf = File.read path
25
+ plan = Stella::Testplan.new
26
+ plan.base_path = File.dirname path
27
+ # eval so the DSL code can be executed in this namespace.
28
+ plan.instance_eval conf
29
+ plan
30
+ end
31
+
32
+ def check!
33
+ # Adjust ratios if necessary
34
+ needy = @usecases.select { |u| u.ratio == -1 }
35
+ needy.each do |u|
36
+ u.ratio = (remaining_ratio / needy.size).to_f
47
37
  end
48
-
49
- alias :desc :description
50
- alias :desc= :description=
51
-
52
- # Append a Stella::TestPlan::Request object to +requests+.
53
- def add_request(req)
54
- raise "That is not an instance of Stella::Data::HTTPRequest" unless req.is_a? Stella::Data::HTTPRequest
55
- @requests << req
38
+ # Give usecases a name if necessary
39
+ @usecases.each_with_index { |uc,i| uc.desc ||= "Usecase ##{i+1}" }
40
+ if @testplan_current_ratio > 1.0
41
+ msg = "Usecase ratio cannot be higher than 1.0"
42
+ msg << " (#{@testplan_current_ratio})"
43
+ raise WackyRatio, msg
56
44
  end
45
+ end
57
46
 
58
-
59
- # Creates a Stella::TestPlan::Auth object and stores it to +@auth+
60
- def auth=(*args)
61
- type, user, pass = args.flatten
62
- @auth = Stella::Common::Auth.new(type, user, pass)
63
- end
47
+ def usecase(*args, &blk)
48
+ return @usecases if args.empty?
49
+ ratio, name = nil,nil
50
+ ratio, name = args[0], args[1] if args[0].is_a?(Numeric)
51
+ ratio, name = args[1], args[0] if args[0].is_a?(String)
52
+ uc = Stella::Testplan::Usecase.new
53
+ uc.base_path = @base_path
54
+ uc.instance_eval &blk
55
+ uc.ratio, uc.desc = (ratio || -1).to_f, name
56
+ @testplan_current_ratio += uc.ratio if uc.ratio > 0
57
+ add_usecase uc
58
+ end
59
+ def xusecase(*args, &blk); Stella.ld "Skipping usecase"; end
60
+
61
+ def add_usecase(uc)
62
+ Stella.ld "Usecase: #{uc.desc}"
63
+ @usecases << uc
64
+ uc
65
+ end
66
+
67
+ def desc(*args)
68
+ @desc = args.first unless args.empty?
69
+ @desc
70
+ end
64
71
 
65
- # A string to be parsed by URI#parsed or a URI object. The host and port are added to +@servers+
66
- # in the form "host:port". The protocol is stored in +@protocol+. NOTE: The
67
- # protocol is used as a default for the test and if it's already set, this
68
- # method will not try to overwrite it.
69
- def base_uri=(*args)
70
- uri_str = args.flatten.first
71
- begin
72
- uri = URI.parse uri_str
73
- host_str = uri.host
74
- host_str << ":#{uri.port}" if uri.port
75
- @servers << host_str
76
- @protocol = uri.scheme unless @protocol
77
- rescue => ex
78
- Stella.fatal(ex)
72
+ def pretty
73
+ str = []
74
+ str << " %-50s ".att(:reverse) % [@desc]
75
+ @usecases.each_with_index do |uc,i|
76
+ description = uc.desc || "Usecase ##{i+1}"
77
+ str << " %s (%s)".bright % [description, uc.ratio]
78
+ requests = uc.requests.each do |r|
79
+ str << " %-35s %s" % ["#{r.desc}:", r]
80
+ if Stella.loglev > 2
81
+ [:wait].each { |i| str << " %s: %s" % [i, r.send(i)] }
82
+ end
79
83
  end
80
84
  end
81
-
82
-
85
+ str.join($/)
86
+ end
87
+
88
+ private
89
+ def remaining_ratio
90
+ 1.0 - @testplan_current_ratio
83
91
  end
84
-
85
92
 
86
93
  end
94
+ end
87
95
 
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
- module Stella
97
- module DSL
98
- module TestPlan
99
- attr_accessor :current_plan
100
- attr_accessor :current_request
101
-
102
- def testplan(name, &define)
103
- @plans ||= {}
104
- @current_plan = @plans[name] = Stella::TestPlan.new(name)
105
- define.call if define
106
- end
107
-
108
- def plans
109
- @plans
110
- end
111
-
112
- def repeat(*args)
113
- raise "Repeat format does not look like a hash" unless args.first.is_a?(Hash)
114
- response_handler = Stella::TestPlan::ResponseHandler.new(:repeat)
115
- [:times, :wait].each do |att|
116
- response_handler.send("#{att}=", args.first[att])
117
- end
118
-
119
- response_handler
120
- end
121
-
122
- def body(*args)
123
-
124
- raise "current_plan is not a valid testplan: #{@current_plan}" unless @current_plan.is_a? Stella::TestPlan
125
-
126
- # NOTE: @current_request must be set in the calling namespace
127
- # before this method is called. See: make_request
128
- raise "current_request is not a valid request" unless @current_request.is_a? Stella::Data::HTTPRequest
129
-
130
- param, content_type, content = args if args.size == 3
131
- param, content = args if args.size == 2
132
- content = args.first if args.size == 1
133
-
134
- @current_request.add_body(content, param, content_type)
135
- end
136
-
137
- def name(*args)
138
- raise "current_plan is not a valid testplan: #{@current_plan}" unless @current_plan.is_a? Stella::TestPlan
139
-
140
- # NOTE: @current_request must be set in the calling namespace
141
- # before this method is called. See: make_request
142
- raise "current_request is not a valid request" unless @current_request.is_a? Stella::Data::HTTPRequest
143
- @current_request.name = args.first
144
- end
145
-
146
-
147
- def response(*args, &b)
148
- raise "current_plan is not a valid testplan" unless @current_plan.is_a? Stella::TestPlan
149
-
150
- # NOTE: @current_request must be set in the calling namespace
151
- # before this method is called. See: make_request
152
- raise "current_request is not a valid request" unless @current_request.is_a? Stella::Data::HTTPRequest
153
-
154
- @current_request.add_response_handler(*args, &b)
155
- end
156
- private :response
157
-
158
- # Stella::Data::HTTPRequest#add_ methods
159
- [:header, :param].each do |method_name|
160
- eval <<-RUBY, binding, '(Stella::DSL::TestPlan)', 1
161
- def #{method_name}(*args, &b)
162
- raise "current_plan is not a valid testplan" unless @current_plan.is_a? Stella::TestPlan
163
-
164
- # NOTE: @current_request must be set in the calling namespace
165
- # before this method is called. See: make_request
166
- raise "current_request is not a valid request" unless @current_request.is_a? Stella::Data::HTTPRequest
167
-
168
- @current_request.add_#{method_name}(*args, &b)
169
- end
170
- private :#{method_name}
171
- RUBY
172
- end
173
-
174
- # TestPlan#= methods
175
- [:proxy, :auth, :base_uri, :desc, :description].each do |method_name|
176
- eval <<-RUBY, binding, '(Stella::DSL::TestPlan)', 1
177
- def #{method_name}(*args)
178
- return unless @current_plan.is_a? Stella::TestPlan
179
- @current_plan.#{method_name}=(args)
180
- end
181
- private :#{method_name}
182
- RUBY
183
- end
184
-
185
- # = methods
186
- [:protocol].each do |method_name|
187
- eval <<-RUBY, binding, '(Stella::DSL::TestPlan)', 1
188
- def #{method_name}(val)
189
- return unless @current_plan.is_a? Stella::TestPlan
190
- @current_plan.#{method_name}=(val.to_s)
191
- end
192
- private :#{method_name}
193
- RUBY
194
- end
195
-
196
- def post(uri, &define)
197
- make_request(:POST, uri, &define)
198
- end
199
- def xpost(*args); end;
200
- def xget(*args); end;
201
-
202
- def get(uri, &define)
203
- make_request(:GET, uri, &define)
204
- end
205
-
206
- private
207
-
208
- def make_request(method, uri, &define)
209
- return unless @current_plan.is_a? Stella::TestPlan
210
- req = Stella::Data::HTTPRequest.new(uri, method.to_s.upcase)
211
- @current_plan.add_request req
212
- index = @current_plan.requests.size
213
- method_name = :"#{index} #{req.http_method} #{req.uri}"
214
-
215
- req_method = Proc.new {
216
- # These instance variables are very important. The bring in the context
217
- # when the request method is called in the testrunner class. We know what
218
- # the current plan is while we're executing the DSL blocks to define the
219
- # request. The response block however, is called only after a require request
220
- # is made. We set these instance variables so that the response block will
221
- # know what request it's associated too.
222
- instance_variable_set('@current_plan', @current_plan)
223
- instance_variable_set('@current_request', req)
224
- define.call if define
225
- req
226
- }
227
- metaclass.instance_eval do
228
- define_method(method_name, &req_method)
229
- end
230
-
231
- end
232
-
96
+ __END__
97
+ # instance_exec for Ruby 1.8 written by Mauricio Fernandez
98
+ # http://eigenclass.org/hiki/instance_exec
99
+ if RUBY_VERSION =~ /1.8/
100
+ module InstanceExecHelper; end
101
+ include InstanceExecHelper
102
+ def instance_exec(*args, &block) # !> method redefined; discarding old instance_exec
103
+ mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
104
+ InstanceExecHelper.module_eval{ define_method(mname, &block) }
105
+ begin
106
+ ret = send(mname, *args)
107
+ ensure
108
+ InstanceExecHelper.module_eval{ undef_method(mname) } rescue nil
233
109
  end
110
+ ret
234
111
  end
235
112
  end
236
-
237
-
File without changes
@@ -0,0 +1,126 @@
1
+
2
+ require 'socket'
3
+ require 'open-uri'
4
+ require 'date'
5
+
6
+ require 'timeout'
7
+
8
+ module Stella
9
+
10
+ # A motley collection of methods that Stella loves to call!
11
+ module Utils
12
+ extend self
13
+ include Socket::Constants
14
+
15
+ # Return the external IP address (the one seen by the internet)
16
+ def external_ip_address
17
+ ip = nil
18
+ begin
19
+ %w{solutious.heroku.com/ip}.each do |sponge|
20
+ ipstr = Net::HTTP.get(URI.parse("http://#{sponge}")) || ''
21
+ ip = /([0-9]{1,3}\.){3}[0-9]{1,3}/.match(ipstr).to_s
22
+ break if ip && !ip.empty?
23
+ end
24
+ rescue SocketError, Errno::ETIMEDOUT => ex
25
+ Stella.le "Connection Error. Check your internets!"
26
+ end
27
+ ip
28
+ end
29
+
30
+ # Return the local IP address which receives external traffic
31
+ # from: http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
32
+ # NOTE: This <em>does not</em> open a connection to the IP address.
33
+ def internal_ip_address
34
+ # turn off reverse DNS resolution temporarily
35
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
36
+ ip = UDPSocket.open {|s| s.connect('75.101.137.7', 1); s.addr.last } # Solutious IP
37
+ ip
38
+ ensure
39
+ Socket.do_not_reverse_lookup = orig
40
+ end
41
+
42
+
43
+ # <tt>require</tt> a glob of files.
44
+ # * +path+ is a list of path elements which is sent to File.join
45
+ # and then to Dir.glob. The list of files found are sent to require.
46
+ # Nothing is returned but LoadError exceptions are caught. The message
47
+ # is printed to STDERR and the program exits with 7.
48
+ def require_glob(*path)
49
+ begin
50
+ Dir.glob(File.join(*path.flatten)).each do |path|
51
+ require path
52
+ end
53
+ rescue LoadError => ex
54
+ puts "Error: #{ex.message}"
55
+ exit 7
56
+ end
57
+ end
58
+
59
+ # Checks whether something is listening to a socket.
60
+ # * +host+ A hostname
61
+ # * +port+ The port to check
62
+ # * +wait+ The number of seconds to wait for before timing out.
63
+ #
64
+ # Returns true if +host+ allows a socket connection on +port+.
65
+ # Returns false if one of the following exceptions is raised:
66
+ # Errno::EAFNOSUPPORT, Errno::ECONNREFUSED, SocketError, Timeout::Error
67
+ #
68
+ def service_available?(host, port, wait=3)
69
+ if Stella.sysinfo.vm == :java
70
+ begin
71
+ iadd = Java::InetSocketAddress.new host, port
72
+ socket = Java::Socket.new
73
+ socket.connect iadd, wait * 1000 # milliseconds
74
+ success = !socket.isClosed && socket.isConnected
75
+ rescue NativeException => ex
76
+ puts ex.message, ex.backtrace if Stella.debug?
77
+ false
78
+ end
79
+ else
80
+ begin
81
+ status = Timeout::timeout(wait) do
82
+ socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
83
+ sockaddr = Socket.pack_sockaddr_in( port, host )
84
+ socket.connect( sockaddr )
85
+ end
86
+ true
87
+ rescue Errno::EAFNOSUPPORT, Errno::ECONNREFUSED, SocketError, Timeout::Error => ex
88
+ puts ex.class, ex.message, ex.backtrace if Stella.debug?
89
+ false
90
+ end
91
+ end
92
+ end
93
+
94
+ # A basic file writer
95
+ def write_to_file(filename, content, mode, chmod=600)
96
+ mode = (mode == :append) ? 'a' : 'w'
97
+ f = File.open(filename,mode)
98
+ f.puts content
99
+ f.close
100
+ return unless Stella.sysinfo.os == :unix
101
+ raise "Provided chmod is not a Fixnum (#{chmod})" unless chmod.is_a?(Fixnum)
102
+ File.chmod(chmod, filename)
103
+ end
104
+
105
+ #
106
+ # Generates a string of random alphanumeric characters.
107
+ # * +len+ is the length, an Integer. Default: 8
108
+ # * +safe+ in safe-mode, ambiguous characters are removed (default: true):
109
+ # i l o 1 0
110
+ def strand( len=8, safe=true )
111
+ chars = ("a".."z").to_a + ("0".."9").to_a
112
+ chars.delete_if { |v| %w(i l o 1 0).member?(v) } if safe
113
+ str = ""
114
+ 1.upto(len) { |i| str << chars[rand(chars.size-1)] }
115
+ str
116
+ end
117
+
118
+ # Returns +str+ with the leading indentation removed.
119
+ # Stolen from http://github.com/mynyml/unindent/ because it was better.
120
+ def noindent(str)
121
+ indent = str.split($/).each {|line| !line.strip.empty? }.map {|line| line.index(/[^\s]/) }.compact.min
122
+ str.gsub(/^[[:blank:]]{#{indent}}/, '')
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,15 @@
1
+
2
+
3
+ module Stella
4
+ module Version
5
+ unless defined?(MAJOR)
6
+ MAJOR = 0.freeze
7
+ MINOR = 7.freeze
8
+ TINY = 0.freeze
9
+ PATCH = '001'.freeze
10
+ end
11
+ def self.to_s; [MAJOR, MINOR, TINY].join('.'); end
12
+ def self.to_f; self.to_s.to_f; end
13
+ def self.patch; PATCH; end
14
+ end
15
+ end