tourbus 0.1.5 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -2
- data/bin/tourbus +9 -9
- data/bin/tourproxy +10 -0
- data/examples/contact_app/README.rdoc +1 -1
- data/examples/contact_app/tours/simple.rb +10 -9
- data/lib/common.rb +11 -2
- data/lib/file.rb +11 -0
- data/lib/runner.rb +24 -27
- data/lib/tour_bus.rb +10 -10
- data/lib/tour_proxy.rb +82 -0
- data/lib/tour_rat.rb +70 -0
- data/lib/tour_watch.rb +4 -4
- data/lib/tourist.rb +101 -0
- data/spec/lib/tourproxy_spec.rb +19 -0
- data/spec/spec_helper.rb +22 -0
- metadata +32 -29
- data/lib/tour.rb +0 -100
data/README.rdoc
CHANGED
@@ -45,7 +45,7 @@ better name, these are called Tours.
|
|
45
45
|
* Make a folder called tours and put a file in it called simple.rb. In
|
46
46
|
it write:
|
47
47
|
|
48
|
-
class Simple <
|
48
|
+
class Simple < Tourist
|
49
49
|
def test_homepage
|
50
50
|
visit "http://#{@host}/"
|
51
51
|
assert_contain "My Home Page"
|
@@ -53,7 +53,7 @@ better name, these are called Tours.
|
|
53
53
|
end
|
54
54
|
|
55
55
|
* Files in ./tours should have classes that match their names. E.g.
|
56
|
-
"class BigHairyTest <
|
56
|
+
"class BigHairyTest < Tourist" belongs in ./tours/big_hairy_test.rb
|
57
57
|
|
58
58
|
* Think Test::Unit. test_* methods will be found automagically.
|
59
59
|
setup() and teardown() methods will be executed at the appropriate
|
data/bin/tourbus
CHANGED
@@ -18,15 +18,15 @@ config[:rand] ||= nil
|
|
18
18
|
|
19
19
|
opts = Trollop.options do
|
20
20
|
opt :host, "Remote hostname to test", :default => config[:host]
|
21
|
-
opt :concurrency, "Number of simultaneous
|
22
|
-
opt :number, "Number of times to run the
|
23
|
-
opt :list, "List
|
21
|
+
opt :concurrency, "Number of simultaneous tourists to run", :type => :integer, :default => config[:concurrency]
|
22
|
+
opt :number, "Number of times to run the tourist (in each concurrent step, so -c 10 -n 10 will run the tourist 100 times)", :type => :integer, :default => config[:number]
|
23
|
+
opt :list, "List tourists and tours available. If tourists or tours are included, filters the list", :type => :boolean, :default => nil
|
24
24
|
opt :rand, "Random seed", :type => :integer, :default => config[:rand]
|
25
25
|
opt :tests, "Test name(s) filter. The name of the test to run (use --list to see the test names). Use commas, no spaces, for mulitple names", :type => :string, :default => nil
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
tourists = if ARGV.empty?
|
29
|
+
Tourist.tourists
|
30
30
|
else
|
31
31
|
ARGV
|
32
32
|
end
|
@@ -34,13 +34,13 @@ tours = if ARGV.empty?
|
|
34
34
|
srand opts[:rand] || Time.now.to_i
|
35
35
|
|
36
36
|
if opts[:list]
|
37
|
-
|
38
|
-
puts
|
39
|
-
puts
|
37
|
+
Tourist.tourists(ARGV).each do |tourist|
|
38
|
+
puts tourist
|
39
|
+
puts Tourist.tours(tourist).map {|test| " #{test}"}
|
40
40
|
end
|
41
41
|
else
|
42
42
|
opts[:tests] = opts[:tests].split(',') if opts[:tests]
|
43
43
|
|
44
|
-
TourBus.new(opts[:host], opts[:concurrency], opts[:number],
|
44
|
+
TourBus.new(opts[:host], opts[:concurrency], opts[:number], tourists, opts[:tests]).run
|
45
45
|
end
|
46
46
|
|
data/bin/tourproxy
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This proxy logger logs x-amf traffic, and is stolen with gratitude from http://altentee.com/2008/performance-testing-flex-remoting-amf-with-jmeter/
|
3
|
+
|
4
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'tour_proxy'))
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
|
8
|
+
proxy = TourProxy.new options
|
9
|
+
trap "INT" do proxy.shutdown end
|
10
|
+
proxy.start
|
@@ -1,19 +1,20 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require 'ruby-debug'
|
2
|
+
class Simple < Tourist
|
3
|
+
def tour_home
|
4
|
+
visit "#{@host}/"
|
4
5
|
assert_contain "If you click this"
|
5
6
|
|
6
7
|
click_link "Enter Contact"
|
7
|
-
assert_match /\/contacts/,
|
8
|
+
assert_match /\/contacts/, current_url
|
8
9
|
end
|
9
10
|
|
10
|
-
def
|
11
|
-
visit "/contacts"
|
12
|
-
|
11
|
+
def tour_contacts
|
12
|
+
visit "#{@host}/contacts"
|
13
|
+
|
13
14
|
fill_in "first_name", :with => "Joe"
|
14
15
|
fill_in "last_name", :with => "Tester"
|
15
|
-
click_button
|
16
|
-
|
16
|
+
click_button
|
17
|
+
|
17
18
|
assert_contain "Tester, Joe"
|
18
19
|
end
|
19
20
|
end
|
data/lib/common.rb
CHANGED
@@ -13,16 +13,25 @@ gem 'faker', '>= 0.3.1'
|
|
13
13
|
|
14
14
|
# TODO: I'd like to remove dependency on Rails. Need to see what all
|
15
15
|
# we're using (like classify) and remove each dependency individually.
|
16
|
-
|
16
|
+
begin
|
17
|
+
require 'activesupport'
|
18
|
+
rescue Exception
|
19
|
+
require 'active_support/all'
|
20
|
+
end
|
17
21
|
|
18
22
|
require 'monitor'
|
19
23
|
require 'faker'
|
20
24
|
require 'tour_bus'
|
21
25
|
require 'runner'
|
22
|
-
require '
|
26
|
+
require 'tourist'
|
23
27
|
|
28
|
+
# Our common base class for exceptions
|
24
29
|
class TourBusException < Exception; end
|
25
30
|
|
31
|
+
# The common base class for all exceptions raised by Webrat.
|
32
|
+
class WebratError < StandardError ; end
|
33
|
+
|
34
|
+
|
26
35
|
def require_all_files_in_folder(folder, extension = "*.rb")
|
27
36
|
for file in Dir[File.join('.', folder, "**/#{extension}")]
|
28
37
|
require file
|
data/lib/file.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
class File
|
2
|
+
# Hides all that lovely expand/join/dirname(__FILE__, path) crap
|
3
|
+
#
|
4
|
+
# E.g.: require File.here("../lib/pants")
|
5
|
+
#
|
6
|
+
# Splatty version for the OS Agnosts out there:
|
7
|
+
# require File.here(%w[.. lib pants])
|
8
|
+
def self.here(*args)
|
9
|
+
p = File.expand_path(File.join(File.dirname(caller.first.split(':')[0]), *args))
|
10
|
+
end
|
11
|
+
end
|
data/lib/runner.rb
CHANGED
@@ -1,40 +1,37 @@
|
|
1
1
|
require 'monitor'
|
2
2
|
require 'common'
|
3
3
|
|
4
|
-
# The common base class for all exceptions raised by Webrat.
|
5
|
-
class WebratError < StandardError ; end
|
6
|
-
|
7
4
|
class Runner
|
8
|
-
attr_reader :host, :
|
5
|
+
attr_reader :host, :tourists, :number, :runner_type, :runner_id
|
9
6
|
|
10
|
-
def initialize(host,
|
11
|
-
@host, @
|
7
|
+
def initialize(host, tourists, number, runner_id, tour_list)
|
8
|
+
@host, @tourists, @number, @runner_id, @tour_list = host, tourists, number, runner_id, tour_list
|
12
9
|
@runner_type = self.send(:class).to_s
|
13
10
|
log("Ready to run #{@runner_type}")
|
14
11
|
end
|
15
12
|
|
16
13
|
# Dispatches to subclass run method
|
17
|
-
def
|
18
|
-
log "Filtering on
|
19
|
-
tours,
|
14
|
+
def run_tourists
|
15
|
+
log "Filtering on tours #{@tour_list.join(', ')}" unless @tour_list.to_a.empty?
|
16
|
+
tourists,tours,passes,fails,errors = 0,0,0,0,0
|
20
17
|
1.upto(number) do |num|
|
21
18
|
log("Starting #{@runner_type} run #{num}/#{number}")
|
22
|
-
@
|
19
|
+
@tourists.each do |tourist_name|
|
23
20
|
|
24
|
-
log("Starting run #{num}/#{number} of
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
log("Starting run #{num}/#{number} of Tourist #{tourist_name}")
|
22
|
+
tourists += 1
|
23
|
+
tourist = Tourist.make_tourist(tourist_name,@host,@tourists,@number,@runner_id)
|
24
|
+
tourist.before_tours
|
28
25
|
|
29
|
-
|
26
|
+
tourist.tours.each do |tour|
|
30
27
|
times = Hash.new {|h,k| h[k] = {}}
|
31
28
|
|
32
|
-
next if
|
29
|
+
next if tour_limited_to(tour)
|
33
30
|
|
34
31
|
begin
|
35
|
-
|
36
|
-
times[
|
37
|
-
|
32
|
+
tours += 1
|
33
|
+
times[tour][:started] = Time.now
|
34
|
+
tourist.run_tour tour
|
38
35
|
passes += 1
|
39
36
|
rescue TourBusException, WebratError => e
|
40
37
|
log("********** FAILURE IN RUN! **********")
|
@@ -53,18 +50,18 @@ class Runner
|
|
53
50
|
end
|
54
51
|
errors += 1
|
55
52
|
ensure
|
56
|
-
times[
|
57
|
-
times[
|
53
|
+
times[tour][:finished] = Time.now
|
54
|
+
times[tour][:elapsed] = times[tour][:finished] - times[tour][:started]
|
58
55
|
end
|
59
|
-
log("Finished run #{num}/#{number} of
|
56
|
+
log("Finished run #{num}/#{number} of Tourist #{tourist_name}")
|
60
57
|
end
|
61
58
|
|
62
|
-
|
59
|
+
tourist.after_tours
|
63
60
|
end
|
64
61
|
log("Finished #{@runner_type} run #{num}/#{number}")
|
65
62
|
end
|
66
|
-
log("Finished all #{@runner_type}
|
67
|
-
[tours,
|
63
|
+
log("Finished all #{@runner_type} tourists.")
|
64
|
+
[tourists,tours,passes,fails,errors]
|
68
65
|
end
|
69
66
|
|
70
67
|
protected
|
@@ -73,8 +70,8 @@ class Runner
|
|
73
70
|
puts "#{Time.now.strftime('%F %H:%M:%S')} Runner ##{@runner_id}: #{message}"
|
74
71
|
end
|
75
72
|
|
76
|
-
def
|
77
|
-
@
|
73
|
+
def tour_limited_to(tour_name)
|
74
|
+
@tour_list && !@tour_list.empty? && !@tour_list.include?(tour_name.to_s)
|
78
75
|
end
|
79
76
|
end
|
80
77
|
|
data/lib/tour_bus.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'benchmark'
|
2
2
|
|
3
3
|
class TourBus < Monitor
|
4
|
-
attr_reader :host, :concurrency, :number, :
|
4
|
+
attr_reader :host, :concurrency, :number, :tourists, :runs, :tests, :passes, :fails, :errors, :benchmarks
|
5
5
|
|
6
|
-
def initialize(host="localhost", concurrency=1, number=1,
|
7
|
-
@host, @concurrency, @number, @
|
6
|
+
def initialize(host="localhost", concurrency=1, number=1, tourists=[], test_list=nil)
|
7
|
+
@host, @concurrency, @number, @tourists, @test_list = host, concurrency, number, tourists, test_list
|
8
8
|
@runner_id = 0
|
9
9
|
@runs, @tests, @passes, @fails, @errors = 0,0,0,0,0
|
10
10
|
super()
|
@@ -38,7 +38,7 @@ class TourBus < Monitor
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def total_runs
|
41
|
-
|
41
|
+
tourists.size * concurrency * number
|
42
42
|
end
|
43
43
|
|
44
44
|
def run
|
@@ -46,10 +46,10 @@ class TourBus < Monitor
|
|
46
46
|
threads_ready = 0
|
47
47
|
start_running = false
|
48
48
|
mutex = Mutex.new
|
49
|
-
|
49
|
+
tourist_name = "#{total_runs} runs: #{concurrency}x#{number} of #{tourists * ','}"
|
50
50
|
started = Time.now.to_f
|
51
51
|
concurrency.times do |conc|
|
52
|
-
log "Starting #{
|
52
|
+
log "Starting #{tourist_name}"
|
53
53
|
threads << Thread.new do
|
54
54
|
runner_id = next_runner_id
|
55
55
|
mutex.lock
|
@@ -62,8 +62,8 @@ class TourBus < Monitor
|
|
62
62
|
sleep 0.05 until start_running
|
63
63
|
runs,tests,passes,fails,errors,start = 0,0,0,0,0,Time.now.to_f
|
64
64
|
bm = Benchmark.measure do
|
65
|
-
runner = Runner.new(@host, @
|
66
|
-
runs,tests,passes,fails,errors = runner.
|
65
|
+
runner = Runner.new(@host, @tourists, @number, runner_id, @test_list)
|
66
|
+
runs,tests,passes,fails,errors = runner.run_tourists
|
67
67
|
update_stats runs, tests, passes, fails, errors
|
68
68
|
end
|
69
69
|
log "Runner Finished!"
|
@@ -76,9 +76,9 @@ class TourBus < Monitor
|
|
76
76
|
threads.each {|t| t.join }
|
77
77
|
finished = Time.now.to_f
|
78
78
|
log '-' * 80
|
79
|
-
log
|
79
|
+
log tourist_name
|
80
80
|
log "All Runners finished."
|
81
|
-
log "Total
|
81
|
+
log "Total Tourists: #{@runs}"
|
82
82
|
log "Total Tests: #{@tests}"
|
83
83
|
log "Total Passes: #{@passes}"
|
84
84
|
log "Total Fails: #{@fails}"
|
data/lib/tour_proxy.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'webrick/httpproxy'
|
2
|
+
require 'ruby-debug'
|
3
|
+
|
4
|
+
class TourProxy
|
5
|
+
# Initialize the proxy object.
|
6
|
+
#
|
7
|
+
# @param [Hash] options list of options to configure the proxy.
|
8
|
+
# @option options [Fixnum] :port Port number to listen on
|
9
|
+
# @option options [Hash] :hostnames hostnames by name => url
|
10
|
+
# @option options [IO] :output_buffer IO object to write output to
|
11
|
+
def initialize(options={})
|
12
|
+
@server = nil
|
13
|
+
@output_buffer = options[:output_buffer] || STDOUT
|
14
|
+
@server = WEBrick::HTTPProxyServer.new(
|
15
|
+
:Port => options[:port] || 8080,
|
16
|
+
:RequestCallback => Proc.new do |req,res|
|
17
|
+
log_request_as_webrat(req)
|
18
|
+
# dump_request(req)
|
19
|
+
# puts(("<" * 100) + " END CALLBACK")
|
20
|
+
end
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def log_request_as_webrat(request)
|
25
|
+
return unless @output_buffer
|
26
|
+
# puts "> log_request_as_webrat"
|
27
|
+
body = request.body
|
28
|
+
if body
|
29
|
+
items = body.split(/&/)
|
30
|
+
pairs = items.map{ |e| e.split(/=/,2)}
|
31
|
+
hash = Hash[pairs]
|
32
|
+
@output_buffer.puts "visit '#{request.request_uri}', :#{request.request_method.downcase}, #{hash.inspect}"
|
33
|
+
else
|
34
|
+
@output_buffer.puts "visit '#{request.request_uri}', :#{request.request_method.downcase}"
|
35
|
+
end
|
36
|
+
# puts "< log_request_as_webrat"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Dumps an HTTPRequest object
|
40
|
+
def dump_request(request)
|
41
|
+
return unless @output_buffer
|
42
|
+
puts "> dump_request"
|
43
|
+
terms = %w(request_uri request_line raw_header body)
|
44
|
+
longest = terms.map(&:size).max
|
45
|
+
|
46
|
+
@output_buffer.puts '-' * 80
|
47
|
+
@output_buffer.puts "Request:"
|
48
|
+
terms.each do |term|
|
49
|
+
@output_buffer.puts " %#{longest}s:" % [term] # , request.send(term).to_s.length]
|
50
|
+
end
|
51
|
+
@output_buffer.puts '-' * 80
|
52
|
+
@output_buffer.flush
|
53
|
+
puts "< dump_request"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Dumps an HTTPResponse object
|
57
|
+
def dump_response(response)
|
58
|
+
return unless @output_buffer
|
59
|
+
puts "> dump_response"
|
60
|
+
terms = %w()
|
61
|
+
longest = terms.map(&:size).max
|
62
|
+
|
63
|
+
@output_buffer.puts '-' * 80
|
64
|
+
@output_buffer.puts "Response:"
|
65
|
+
terms.each do |term|
|
66
|
+
@output_buffer.puts " %#{longest}s: %s" % [term, response.send(term).to_s]
|
67
|
+
end
|
68
|
+
@output_buffer.puts '-' * 80
|
69
|
+
@output_buffer.flush
|
70
|
+
puts "< dump_response"
|
71
|
+
end
|
72
|
+
|
73
|
+
def start
|
74
|
+
@server.start
|
75
|
+
end
|
76
|
+
|
77
|
+
def shutdown
|
78
|
+
@server.shutdown
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
data/lib/tour_rat.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'webrat'
|
3
|
+
|
4
|
+
module TourRat
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
module InstanceMethods
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(receiver)
|
14
|
+
receiver.extend ClassMethods
|
15
|
+
receiver.send :include, InstanceMethods
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Webrat::Scope
|
20
|
+
# 1. attach_file
|
21
|
+
# 2. check
|
22
|
+
# 3. choose
|
23
|
+
# 4. click_area
|
24
|
+
# 5. click_button
|
25
|
+
# 6. click_link
|
26
|
+
# 7. fill_in
|
27
|
+
# 8. select
|
28
|
+
# 9. select_date
|
29
|
+
# 10. select_datetime
|
30
|
+
# 11. select_time
|
31
|
+
# 12. set_hidden_field
|
32
|
+
# 13. submit_form
|
33
|
+
# 14. uncheck
|
34
|
+
#
|
35
|
+
#
|
36
|
+
# Webrat::Session
|
37
|
+
# 1. automate
|
38
|
+
# 2. basic_auth
|
39
|
+
# 3. check_for_infinite_redirects
|
40
|
+
# 4. click_link_within
|
41
|
+
# 5. dom
|
42
|
+
# 6. header
|
43
|
+
# 7. http_accept
|
44
|
+
# 8. infinite_redirect_limit_exceeded?
|
45
|
+
# 9. internal_redirect?
|
46
|
+
# 10. redirected_to
|
47
|
+
# 11. reload
|
48
|
+
# 12. simulate
|
49
|
+
# 13. visit
|
50
|
+
# 14. within
|
51
|
+
# 15. xml_content_type?
|
52
|
+
#
|
53
|
+
# Webrat::HaveTagMatcher
|
54
|
+
# 1. assert_have_no_tag
|
55
|
+
# 2. assert_have_tag
|
56
|
+
# 3. have_tag
|
57
|
+
# 4. match_tag
|
58
|
+
#
|
59
|
+
# Webrat::Matchers
|
60
|
+
# 1. assert_contain
|
61
|
+
# 2. assert_have_no_selector
|
62
|
+
# 3. assert_have_no_xpath
|
63
|
+
# 4. assert_have_selector
|
64
|
+
# 5. assert_have_xpath
|
65
|
+
# 6. assert_not_contain
|
66
|
+
# 7. contain
|
67
|
+
# 8. have_selector
|
68
|
+
# 9. have_xpath
|
69
|
+
# 10. match_selector
|
70
|
+
# 11. match_xpath
|
data/lib/tour_watch.rb
CHANGED
@@ -29,10 +29,10 @@ class TourWatch
|
|
29
29
|
@mac ? fields_mac(parts) : fields_linux(parts)
|
30
30
|
end
|
31
31
|
|
32
|
-
# Note: MacOSX is so
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
32
|
+
# Note: MacOSX is so laaaame. Top will report 0.0% cpu the first
|
33
|
+
# time you run top, every time. The only way to get actual CPU% here
|
34
|
+
# is to wait for it to send another page and then throw away the
|
35
|
+
# first page. Isn't that just awesome?!? I KNOW!!!
|
36
36
|
def top_mac
|
37
37
|
top = `top -l 1 | grep -E '(#{@processes})'`
|
38
38
|
end
|
data/lib/tourist.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'monitor'
|
3
|
+
require 'common'
|
4
|
+
require 'webrat'
|
5
|
+
require 'webrat/adapters/mechanize'
|
6
|
+
require 'test/unit/assertions'
|
7
|
+
|
8
|
+
# A tourist is essentially a test suite file. A Tourist subclass
|
9
|
+
# encapsulates a set of tours that can be done, and may contain helper
|
10
|
+
# and support methods for a given task. If you have a two or three
|
11
|
+
# paths through a specific area of your website, define a tourist for
|
12
|
+
# that area and create tour_ methods for each type of tour to be done.
|
13
|
+
|
14
|
+
Webrat.configure do |config|
|
15
|
+
config.mode = :mechanize
|
16
|
+
end
|
17
|
+
|
18
|
+
class Tourist
|
19
|
+
extend Forwardable
|
20
|
+
include Webrat::Methods
|
21
|
+
include Webrat::Matchers
|
22
|
+
include Webrat::SaveAndOpenPage
|
23
|
+
include Test::Unit::Assertions
|
24
|
+
|
25
|
+
attr_reader :host, :tours, :number, :tour_type, :tourist_id
|
26
|
+
|
27
|
+
def initialize(host, tours, number, tourist_id)
|
28
|
+
@host, @tours, @number, @tourist_id = host, tours, number, tourist_id
|
29
|
+
@tour_type = self.send(:class).to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
# before_tour runs once per tour, before any tours get run
|
33
|
+
def before_tours; end
|
34
|
+
|
35
|
+
# after_tour runs once per tour, after all the tours have run
|
36
|
+
def after_tours; end
|
37
|
+
|
38
|
+
def setup
|
39
|
+
end
|
40
|
+
|
41
|
+
def teardown
|
42
|
+
end
|
43
|
+
|
44
|
+
def wait(time)
|
45
|
+
sleep time.to_i
|
46
|
+
end
|
47
|
+
|
48
|
+
# Lists tourists in tours folder. If a string is given, filters the
|
49
|
+
# list by that string. If an array of filter strings is given,
|
50
|
+
# returns items that match ANY filter string in the array.
|
51
|
+
def self.tourists(filter=[])
|
52
|
+
filter = [filter].flatten
|
53
|
+
# All files in tours folder, stripped to basename, that match any item in filter
|
54
|
+
# I do loves me a long chain. This returns an array containing
|
55
|
+
# 1. All *.rb files in tour folder (recursive)
|
56
|
+
# 2. Each filename stripped to its basename
|
57
|
+
# 3. If you passed in any filters, these basenames are rejected unless they match at least one filter
|
58
|
+
# 4. The filenames remaining are then checked to see if they define a class of the same name that inherits from Tourist
|
59
|
+
Dir[File.join('.', 'tours', '**', '*.rb')].map {|fn| File.basename(fn, ".rb")}.select {|fn| filter.size.zero? || filter.any?{|f| fn =~ /#{f}/}}.select {|tour| Tourist.tourist? tour }
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.tours(tourist_name)
|
63
|
+
Tourist.make_tourist(tourist_name).tours
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns true if the given tourist name can be found in the tours folder, and defines a similarly-named subclass of Tourist
|
67
|
+
def self.tourist?(tourist_name)
|
68
|
+
Object.const_defined?(tourist_name.classify) && tourist_name.classify.constantize.ancestors.include?(Tourist)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Factory method, creates the named child class instance
|
72
|
+
def self.make_tourist(tourist_name,host="http://localhost:3000",tours=[],number=1,tourist_id=nil)
|
73
|
+
tourist_name.classify.constantize.new(host,tours,number,tourist_id)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns list of tours this tourist knows about. (Meant to be run on a subclass
|
77
|
+
# instance; returns the list of tours available).
|
78
|
+
def tours
|
79
|
+
methods.grep(/^tour_/).map {|m| m.sub(/^tour_/,'')}
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_tour(tour_name)
|
83
|
+
@current_tour = "tour_#{tour_name}"
|
84
|
+
raise TourBusException.new("run_tour couldn't run tour '#{tour_name}' because this tourist did not respond to :#{@current_tour}") unless respond_to? @current_tour
|
85
|
+
setup
|
86
|
+
send @current_tour
|
87
|
+
teardown
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def session
|
93
|
+
@session ||= Webrat::MechanizeSession.new
|
94
|
+
end
|
95
|
+
|
96
|
+
def log(message)
|
97
|
+
puts "#{Time.now.strftime('%F %H:%M:%S')} Tourist ##{@tourist_id}: (#{@current_tour}) #{message}"
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe TourProxy do
|
4
|
+
it "should replace known hostnames"
|
5
|
+
# given a list of known hostnames,
|
6
|
+
# when I process a request matching a hosts,
|
7
|
+
# then the request uri should be rewritten with the variable's expansion
|
8
|
+
|
9
|
+
it "should emit AMF blobs"
|
10
|
+
it "should emit get/post blobs for unrecognized types"
|
11
|
+
|
12
|
+
# when doing an html get
|
13
|
+
# with no params
|
14
|
+
# it should log "visit 'url'"
|
15
|
+
# and should not log "visit 'url', :get" etc
|
16
|
+
|
17
|
+
|
18
|
+
end
|
19
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# This file is copied to ~/spec when you run 'ruby script/generate rspec'
|
2
|
+
# from the project root directory.
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/file'))
|
4
|
+
require 'spec/autorun'
|
5
|
+
require 'ruby-debug'
|
6
|
+
# require File.here('../features/factories/fixjour_definitions')
|
7
|
+
# require File.here('../test/bdrb_test_helper')
|
8
|
+
|
9
|
+
# Requires all files in a folder relative to .. (project root).
|
10
|
+
def require_all_files_in_folder(folder, extension = '*.rb')
|
11
|
+
for file in Dir[File.join(File.expand_path(File.dirname(__FILE__)), '..', folder, "**/#{extension}")]
|
12
|
+
require file
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Uncomment the next line to use webrat's matchers
|
17
|
+
#require 'webrat/integrations/rspec-rails'
|
18
|
+
|
19
|
+
# Requires supporting files with custom matchers and macros, etc,
|
20
|
+
# in ./support/ and its subdirectories.
|
21
|
+
require_all_files_in_folder "spec/support"
|
22
|
+
require_all_files_in_folder "lib"
|
metadata
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tourbus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 1
|
8
|
-
- 5
|
9
|
-
version: 0.1.5
|
4
|
+
prerelease:
|
5
|
+
version: 2.0.1
|
10
6
|
platform: ruby
|
11
7
|
authors:
|
12
8
|
- David Brady
|
@@ -18,20 +14,17 @@ autorequire:
|
|
18
14
|
bindir: bin
|
19
15
|
cert_chain: []
|
20
16
|
|
21
|
-
date: 2010-
|
17
|
+
date: 2010-09-23 00:00:00 -06:00
|
22
18
|
default_executable:
|
23
19
|
dependencies:
|
24
20
|
- !ruby/object:Gem::Dependency
|
25
21
|
name: mechanize
|
26
22
|
prerelease: false
|
27
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
28
25
|
requirements:
|
29
26
|
- - ">="
|
30
27
|
- !ruby/object:Gem::Version
|
31
|
-
segments:
|
32
|
-
- 1
|
33
|
-
- 0
|
34
|
-
- 0
|
35
28
|
version: 1.0.0
|
36
29
|
type: :runtime
|
37
30
|
version_requirements: *id001
|
@@ -39,11 +32,10 @@ dependencies:
|
|
39
32
|
name: trollop
|
40
33
|
prerelease: false
|
41
34
|
requirement: &id002 !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
42
36
|
requirements:
|
43
37
|
- - ">="
|
44
38
|
- !ruby/object:Gem::Version
|
45
|
-
segments:
|
46
|
-
- 0
|
47
39
|
version: "0"
|
48
40
|
type: :runtime
|
49
41
|
version_requirements: *id002
|
@@ -51,11 +43,10 @@ dependencies:
|
|
51
43
|
name: faker
|
52
44
|
prerelease: false
|
53
45
|
requirement: &id003 !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
54
47
|
requirements:
|
55
48
|
- - ">="
|
56
49
|
- !ruby/object:Gem::Version
|
57
|
-
segments:
|
58
|
-
- 0
|
59
50
|
version: "0"
|
60
51
|
type: :runtime
|
61
52
|
version_requirements: *id003
|
@@ -63,11 +54,10 @@ dependencies:
|
|
63
54
|
name: hpricot
|
64
55
|
prerelease: false
|
65
56
|
requirement: &id004 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
66
58
|
requirements:
|
67
59
|
- - ">="
|
68
60
|
- !ruby/object:Gem::Version
|
69
|
-
segments:
|
70
|
-
- 0
|
71
61
|
version: "0"
|
72
62
|
type: :runtime
|
73
63
|
version_requirements: *id004
|
@@ -75,21 +65,30 @@ dependencies:
|
|
75
65
|
name: webrat
|
76
66
|
prerelease: false
|
77
67
|
requirement: &id005 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
78
69
|
requirements:
|
79
70
|
- - ">="
|
80
71
|
- !ruby/object:Gem::Version
|
81
|
-
segments:
|
82
|
-
- 0
|
83
|
-
- 7
|
84
|
-
- 0
|
85
72
|
version: 0.7.0
|
86
73
|
type: :runtime
|
87
74
|
version_requirements: *id005
|
88
|
-
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: activesupport
|
77
|
+
prerelease: false
|
78
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 3.0.0
|
84
|
+
type: :runtime
|
85
|
+
version_requirements: *id006
|
86
|
+
description: TourBus, a web load-testing tool that combines complex 'tour' definitions with scalable, concurrent testing
|
89
87
|
email: github@shinybit.com
|
90
88
|
executables:
|
91
89
|
- tourbus
|
92
90
|
- tourwatch
|
91
|
+
- tourproxy
|
93
92
|
extensions: []
|
94
93
|
|
95
94
|
extra_rdoc_files:
|
@@ -98,16 +97,22 @@ extra_rdoc_files:
|
|
98
97
|
- examples/contact_app/README.rdoc
|
99
98
|
files:
|
100
99
|
- bin/tourbus
|
100
|
+
- bin/tourproxy
|
101
101
|
- bin/tourwatch
|
102
102
|
- examples/contact_app/README.rdoc
|
103
103
|
- examples/contact_app/contact_app.rb
|
104
104
|
- examples/contact_app/tours/simple.rb
|
105
105
|
- examples/contact_app/tours/tourbus.yml
|
106
106
|
- lib/common.rb
|
107
|
+
- lib/file.rb
|
107
108
|
- lib/runner.rb
|
108
|
-
- lib/tour.rb
|
109
109
|
- lib/tour_bus.rb
|
110
|
+
- lib/tour_proxy.rb
|
111
|
+
- lib/tour_rat.rb
|
110
112
|
- lib/tour_watch.rb
|
113
|
+
- lib/tourist.rb
|
114
|
+
- spec/lib/tourproxy_spec.rb
|
115
|
+
- spec/spec_helper.rb
|
111
116
|
- README.rdoc
|
112
117
|
- MIT-LICENSE
|
113
118
|
has_rdoc: true
|
@@ -121,27 +126,25 @@ rdoc_options:
|
|
121
126
|
- --main
|
122
127
|
- README.rdoc
|
123
128
|
- --title
|
124
|
-
- Tourbus - Web
|
129
|
+
- Tourbus - Web Load Testing in Ruby
|
125
130
|
require_paths:
|
126
131
|
- lib
|
127
132
|
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
128
134
|
requirements:
|
129
135
|
- - ">="
|
130
136
|
- !ruby/object:Gem::Version
|
131
|
-
segments:
|
132
|
-
- 0
|
133
137
|
version: "0"
|
134
138
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
135
140
|
requirements:
|
136
141
|
- - ">="
|
137
142
|
- !ruby/object:Gem::Version
|
138
|
-
segments:
|
139
|
-
- 0
|
140
143
|
version: "0"
|
141
144
|
requirements: []
|
142
145
|
|
143
146
|
rubyforge_project:
|
144
|
-
rubygems_version: 1.
|
147
|
+
rubygems_version: 1.6.2
|
145
148
|
signing_key:
|
146
149
|
specification_version: 3
|
147
150
|
summary: TourBus web stress-testing tool
|
data/lib/tour.rb
DELETED
@@ -1,100 +0,0 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
require 'monitor'
|
3
|
-
require 'common'
|
4
|
-
require 'webrat'
|
5
|
-
require 'webrat/adapters/mechanize'
|
6
|
-
require 'test/unit/assertions'
|
7
|
-
|
8
|
-
# A tour is essentially a test suite file. A Tour subclass
|
9
|
-
# encapsulates a set of tests that can be done, and may contain helper
|
10
|
-
# and support methods for a given task. If you have a two or three
|
11
|
-
# paths through a specific area of your website, define a tour for
|
12
|
-
# that area and create test_ methods for each type of test to be done.
|
13
|
-
|
14
|
-
Webrat.configure do |config|
|
15
|
-
config.mode = :mechanize
|
16
|
-
end
|
17
|
-
|
18
|
-
class Tour
|
19
|
-
extend Forwardable
|
20
|
-
include Webrat::Methods
|
21
|
-
include Webrat::Matchers
|
22
|
-
include Webrat::SaveAndOpenPage
|
23
|
-
include Test::Unit::Assertions
|
24
|
-
|
25
|
-
attr_reader :host, :tours, :number, :tour_type, :tour_id
|
26
|
-
|
27
|
-
def initialize(host, tours, number, tour_id)
|
28
|
-
@host, @tours, @number, @tour_id = host, tours, number, tour_id
|
29
|
-
@tour_type = self.send(:class).to_s
|
30
|
-
end
|
31
|
-
|
32
|
-
# before_tour runs once per tour, before any tests get run
|
33
|
-
def before_tour; end
|
34
|
-
|
35
|
-
# after_tour runs once per tour, after all the tests have run
|
36
|
-
def after_tour; end
|
37
|
-
|
38
|
-
def setup
|
39
|
-
end
|
40
|
-
|
41
|
-
def teardown
|
42
|
-
end
|
43
|
-
|
44
|
-
def wait(time)
|
45
|
-
sleep time.to_i
|
46
|
-
end
|
47
|
-
|
48
|
-
# Lists tours in tours folder. If a string is given, filters the
|
49
|
-
# list by that string. If an array of filter strings is given,
|
50
|
-
# returns items that match ANY filter string in the array.
|
51
|
-
def self.tours(filter=[])
|
52
|
-
filter = [filter].flatten
|
53
|
-
# All files in tours folder, stripped to basename, that match any item in filter
|
54
|
-
# I do loves me a long chain. This returns an array containing
|
55
|
-
# 1. All *.rb files in tour folder (recursive)
|
56
|
-
# 2. Each filename stripped to its basename
|
57
|
-
# 3. If you passed in any filters, these basenames are rejected unless they match at least one filter
|
58
|
-
# 4. The filenames remaining are then checked to see if they define a class of the same name that inherits from Tour
|
59
|
-
Dir[File.join('.', 'tours', '**', '*.rb')].map {|fn| File.basename(fn, ".rb")}.select {|fn| filter.size.zero? || filter.any?{|f| fn =~ /#{f}/}}.select {|tour| Tour.tour? tour }
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.tests(tour_name)
|
63
|
-
Tour.make_tour(tour_name).tests
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.tour?(tour_name)
|
67
|
-
Object.const_defined?(tour_name.classify) && tour_name.classify.constantize.ancestors.include?(Tour)
|
68
|
-
end
|
69
|
-
|
70
|
-
# Factory method, creates the named child class instance
|
71
|
-
def self.make_tour(tour_name,host="http://localhost:3000",tours=[],number=1,tour_id=nil)
|
72
|
-
tour_name.classify.constantize.new(host,tours,number,tour_id)
|
73
|
-
end
|
74
|
-
|
75
|
-
# Returns list of tests in this tour. (Meant to be run on a subclass
|
76
|
-
# instance; returns the list of tests available).
|
77
|
-
def tests
|
78
|
-
methods.grep(/^test_/).map {|m| m.sub(/^test_/,'')}
|
79
|
-
end
|
80
|
-
|
81
|
-
def run_test(test_name)
|
82
|
-
@test = "test_#{test_name}"
|
83
|
-
raise TourBusException.new("run_test couldn't run test '#{test_name}' because this tour did not respond to :#{@test}") unless respond_to? @test
|
84
|
-
setup
|
85
|
-
send @test
|
86
|
-
teardown
|
87
|
-
end
|
88
|
-
|
89
|
-
protected
|
90
|
-
|
91
|
-
def session
|
92
|
-
@session ||= Webrat::MechanizeSession.new
|
93
|
-
end
|
94
|
-
|
95
|
-
def log(message)
|
96
|
-
puts "#{Time.now.strftime('%F %H:%M:%S')} Tour ##{@tour_id}: (#{@test}) #{message}"
|
97
|
-
end
|
98
|
-
|
99
|
-
end
|
100
|
-
|