tourbus 0.1.5 → 2.0.1
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.
- 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
|
-
|