jtzemp-tourbus 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2008-2009 David Brady github@shinybit.com
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,200 @@
1
+ = TourBus
2
+
3
+ Flexible and scalable website testing tool.
4
+
5
+ == Authors
6
+
7
+ * David Brady -- david.brady@leadmediapartners.com
8
+ * Tim Harper -- tim.harper@leadmediapartners.com
9
+ * James Britt -- james@neurogami.com
10
+ * JT Zemp -- jtzemp@gmail.com
11
+
12
+
13
+ == General Info
14
+
15
+ TourBus is an intelligent website load testing tool. Allows for
16
+ complicated testing scenarios including filling out forms, following
17
+ redirects, handling cookies, and following links--all of the things
18
+ you'd normally associate with a regression suite or integration
19
+ testing tool. The difference is that TourBus also scales concurrently,
20
+ and you can perform hundreds of complicated regression tests
21
+ simultaneously in order to thoroughly load test your website.
22
+
23
+ == Motivation
24
+
25
+ I started writing TourBus because I needed flexibility and scalability
26
+ in a website testing tool, and the extant tools all provided one but
27
+ not the other. Selenium is ultraflexible but limited to the number of
28
+ browsers you can have open at once, while Apache Bench is powerful and
29
+ fast but limited to simple tests.
30
+
31
+ TourBus lets you define complicated paths through your website, then
32
+ execute those paths concurrently for stress testing.
33
+
34
+ == Example
35
+
36
+ To see TourBus in action, you need to write scripts. For lack of a
37
+ better name, these are called Tours.
38
+
39
+ === Example Tour
40
+
41
+ * Make a folder called tours and put a file in it called simple.rb. In
42
+ it write:
43
+
44
+ class Simple < Tour
45
+ def test_homepage
46
+ visit "http://#{@host}/"
47
+ assert_contain "My Home Page"
48
+ end
49
+ end
50
+
51
+ * Files in ./tours should have classes that match their names. E.g.
52
+ "class BigHairyTest < Tour" belongs in ./tours/big_hairy_test.rb
53
+
54
+ * Think Test::Unit. test_* methods will be found automagically.
55
+ setup() and teardown() methods will be executed at the appropriate
56
+ times.
57
+
58
+ === Example TourBus Run
59
+
60
+ You want to invoke +tourbus+ from the parent directory of the @tours/@ folder.
61
+
62
+ For example, if you have this project tree ...
63
+
64
+ `-- contact_app
65
+ |-- README.rdoc
66
+ |-- contact_app.rb
67
+ `-- tours
68
+ |-- simple.rb
69
+ `-- tourbus.yml
70
+
71
+ ... then you execute +tourbus+ from the +contact_app/+ directory.
72
+
73
+ tourbus -c 2 -n 3 simple
74
+
75
+ That will run the +simple.rb+ tour file.
76
+
77
+ It will create 2 concurrent Tour runners, each of which will run all
78
+ of the methods in Simple three times.
79
+
80
+ * You can specify multiple tours.
81
+
82
+ tourbus -c 2 -n 3 simple1 simple2 simple3
83
+
84
+ * If you don't specify a tour, all tours in ./tours will be run.
85
+
86
+ * tourbus --help will give you more information.
87
+
88
+ * You can run tours and filter given tests.
89
+
90
+ tourbus -c 2 -n 3 simple -t test_login,test_logout
91
+
92
+ Note that if you specify multiple tours and filter tests, the filtered
93
+ tests will be run on all tours specified. If you do not specify a
94
+ tour, the filtered tests will be run on all tours found in the
95
+ +./tours+ folder.
96
+
97
+ === Example TourWatch Run
98
+
99
+ On the webserver, you can type
100
+
101
+ tourwatch -c 4
102
+
103
+ To begin running tourwatch. It's basically a stripped-down version of
104
+ top with cheesy text graphs. (TourWatch's development cycles were
105
+ included in the 2 days for TourBus.)
106
+
107
+ * The -c option is for the total number of cores on the server. The
108
+ top app will cheerfully report a process as taking 392% CPU if it is
109
+ using 98% of four cores. This option is only necessary for making
110
+ the little text graphs scale correctly.
111
+
112
+ * You can choose which processes to watch by passing a csv to -p:
113
+
114
+ tourwatch -p ruby,mongrel
115
+
116
+ Each process name is a partial regexp, so the above would match
117
+ mongrel AND mongrel_rails, etc.
118
+
119
+ * tourwatch --help will give you more information.
120
+
121
+ == History and Status
122
+
123
+ TourBus began life as a 2-day throwaway app. It is definitely an app
124
+ whose development provides many opportunities for open-source
125
+ contributors to make improvements. It is chock-full of brutal hacks,
126
+ duplications, oversights, and kludges.
127
+
128
+ == Hacks, Kludges, Known Issues, and Piles of Steaming Poo
129
+
130
+ * If you give a tour a name that is pluralized, it won't work. This is
131
+ probably a bug worth fixing. The reason for it is that we take file
132
+ names and "classify" them, and e.g. "ranking_reports" becomes
133
+ "RankingReport", not "RankingReports". This is an artifact of
134
+ borrowing from Rails' activesupport libs and should probably be
135
+ fixed.
136
+
137
+ * Mechanize 0.8 doesn't always play well together with TourBus. If you
138
+ get "connection refused" socket errors, try upgrading to Mechanize
139
+ 0.9.
140
+
141
+ * JRuby doesn't play well with Nokogiri. I have set the html_parser to
142
+ use hpricot, which should work around the issue for now.
143
+
144
+ * There are no specs. Yikes! This is to my eternal shame because I'm
145
+ sort of a testing freak. Because TourBus *WAS* a testing tool, I
146
+ didn't put tests on it. I haven't put tests on it yet because I'm
147
+ not sure how to go about it. Instead of exercising a web app with a
148
+ test browser, we need to exercise a test browser with... um... a web
149
+ app? (dbrady notes: Now that we have a contact_app, we could try
150
+ writing some specs and features to run tourbus against it.)
151
+
152
+ * Web-Sickle is another internal app, written by Tim Harper, that
153
+ works "well enough". Until I open-sourced this project, it was a
154
+ submodule in the app. We wanted to keep TourBus extensions separate
155
+ from WebSickle itself, so there's a lot of code in Runner that
156
+ really belongs in WebSickle.
157
+
158
+ * Documentation is <strike>horrible</strike> merely quite bad.
159
+
160
+ * There's not much in the way of examples, either. When I removed all
161
+ the LMP-specific code, all of the examples went with it. Sorry about
162
+ that. Coming soon.
163
+
164
+ == Feature Requests (How You Can Help!)
165
+
166
+ * I'd like to beef up the example contact app to show more of TourBus
167
+ than the simplest possible path. Adding in another page or two, and
168
+ then adding an additional tour or two would make it more apparent to
169
+ new users that you can do things like run multiple tours at once.
170
+ Also, having more than one test_ method in simple.rb would let us
171
+ demonstrate test filtering as well. (Be aware that at present
172
+ [2009-04-17], webrat still does not play well with Sinatra sessions
173
+ so there would be complications. dbrady's fork of webrat combines
174
+ jferris' fix with the latest webrat, and will be maintained until
175
+ main webrat includes the feature. That fork is at
176
+ http://github.com/dbrady/webrat)
177
+
178
+ * I'd like to remove WebSickle and replace it with Webrat. There is a
179
+ webrat branch on the main fork (http://github.com/dbrady/tourbus)
180
+ that is 90% complete. Once that's done we can start massaging the
181
+ API to be a little more friendly.
182
+
183
+ == Credits
184
+
185
+ * Tim Harper camped at my place for a day fixing bugs in WebSickle as
186
+ I exercised more and more new bits of it. Thanks, dude.
187
+
188
+ * Lead Media Partners paid me to write TourBus, then let me open
189
+ source it. How much do they rock? All the way to 11, that's how much
190
+ they rock.
191
+
192
+ * James Britt jumped on this and revived it as it was gathering dust.
193
+ Thanks!
194
+
195
+ * JT Zemp added before_tour, after_tour. Thanks!
196
+
197
+ == License
198
+
199
+ MIT. See the license file.
200
+
data/bin/tourbus ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'common'))
3
+ require 'trollop'
4
+ require_all_files_in_folder 'tours'
5
+
6
+ # load config file, we'll use these as defaults
7
+ config_file = ["./tourbus.yml", "./tours/tourbus.yml", "./config/tourbus.yml", "~/tourbus.yml"].map {|p| File.expand_path(p)}.find {|p| File.exists? p}
8
+ config = config_file ? YAML::load_file(config_file).symbolize_keys : {}
9
+
10
+ config_map = { :host => :to_s, :concurrency => :to_i, :number => :to_i, :rand => :to_i, :tests => :to_s }
11
+ config_map.each {|key,conv| config[key] = config[key].send(conv) if config.key? key }
12
+
13
+ # defaults
14
+ config[:host] ||= "localhost:3000"
15
+ config[:concurrency] ||= 1
16
+ config[:number] ||= 1
17
+ config[:rand] ||= nil
18
+
19
+ opts = Trollop.options do
20
+ opt :host, "Remote hostname to test", :default => config[:host]
21
+ opt :concurrency, "Number of simultaneous runs to perform", :type => :integer, :default => config[:concurrency]
22
+ opt :number, "Number of times to run the tour (in each concurrent step, so -c 10 -n 10 will run the tour 100 times)", :type => :integer, :default => config[:number]
23
+ opt :list, "List tours and runs available. If tours or runs are included, filters the list", :type => :boolean, :default => nil
24
+ opt :rand, "Random seed", :type => :integer, :default => config[:rand]
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
+ end
27
+
28
+ tours = if ARGV.empty?
29
+ Tour.tours
30
+ else
31
+ ARGV
32
+ end
33
+
34
+ srand opts[:rand] || Time.now.to_i
35
+
36
+ if opts[:list]
37
+ Tour.tours(ARGV).each do |tour|
38
+ puts tour
39
+ puts Tour.tests(tour).map {|test| " #{test}"}
40
+ end
41
+ else
42
+ opts[:tests] = opts[:tests].split(',') if opts[:tests]
43
+
44
+ TourBus.new(opts[:host], opts[:concurrency], opts[:number], tours, opts[:tests]).run
45
+ end
46
+
data/bin/tourwatch ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # tourwatch - cheap monitor program for tourbus
4
+ #
5
+ # Notes:
6
+ #
7
+ # tourwatch is a cheap logger program for tourbus. It runs on the
8
+ # targeted server and monitors cpu and memory usage of webserver
9
+ # processes. It's a moderately quick hack: I have a 2-hour budget to
10
+ # write and debug the whole thing and here I am wasting time by
11
+ # starting with documentation. This is because I figure the chance of
12
+ # this program needing maintenance in the next 6 months to be well
13
+ # over 100%, and the poor guy behind me (Hey, that's you! Hi.) will
14
+ # need to know why tourwatch is so barebones.
15
+ #
16
+ # So. TourWatch runs on the target server, collects top information
17
+ # every second, and logs it to file. End of story. "Automation" is
18
+ # handled by the meat cloud (Hey, that's you! Hi.) when the maintainer
19
+ # starts and stops the process manually. Report collection is handled
20
+ # by you reading the logfiles in a terminal. Report aggregation is
21
+ # handled by you aggregating the reports. Yes, there's a theme here.
22
+ #
23
+ # TODO:
24
+ #
25
+ # - Remote reporting? Send log events to main log server?
26
+ #
27
+ # - If we logged to a lightweight database like sqlite3, we could do
28
+ # some clever things like track individual pids and process groups.
29
+ # This would let us track, e.g., aggregate apache stress as well as
30
+ # rogue mongrels. I'm not doing this now because it will require
31
+ # writing something to read and parse the previous information. For
32
+ # now, we'll leave it up to the user (Hey, that's you! Hi.) to parse
33
+ # the logfiles.
34
+ #
35
+ # - Tweak output format. Currently it's crap. I don't think we need
36
+ # dynamic templating or anything, but it might be nice to improve
37
+ # the existing formats.
38
+
39
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'common'))
40
+ require 'trollop'
41
+ require 'tour_watch'
42
+
43
+ opts = Trollop.options do
44
+ opt :outfile, "Logfile name (default to STDOUT)", :type => :string, :default => nil
45
+ opt :processes, "csv of processes to monitor", :type => :string, :default => nil
46
+ opt :cores, "number of cores present (max CPU% is number of cores * 100)", :type => :integer, :default => 4
47
+ opt :mac, "Set if running on MacOSX. The Mac top command is different than linux top.", :type => :boolean, :default => false
48
+ end
49
+
50
+ TourWatch.new(opts).run
51
+
52
+
@@ -0,0 +1,134 @@
1
+ = Contact App
2
+
3
+ Silly little contact app to show you how to tour a website.
4
+
5
+ = Requirements
6
+
7
+ In addition to tourbus, you will need Sinatra to run
8
+ this app.
9
+
10
+ sudo gem install sinatra
11
+
12
+ = Contact App
13
+
14
+ == Start the app
15
+
16
+ Once that's working, start the app with "ruby contact_app.rb". Sinatra
17
+ should start up, and you can now point your browser at
18
+ http://localhost:4567 to see the app's homepage.
19
+
20
+ Pretty humble, I know; just the one link labeled Enter Contacts. Click
21
+ it to get to the Contact form. Here you can enter a first and last
22
+ name then click submit.
23
+
24
+ The app then shows you that name in last_name, first_name format.
25
+ That's the whole app. Don't everybody applaud all at once.
26
+
27
+ == First Tour
28
+
29
+ Still here? Okay, let's tour this website.
30
+
31
+ In the tours folder, you will find two files: simple.rb and
32
+ tourbus.yml. The YAML file just sets the default host to
33
+ localhost:4567. (Without it, tourbus will default to localhost:3000.
34
+ You could override this by running tourbus with "-h localhost:4567"
35
+ every time, but that gets tedious.
36
+
37
+ Before we go any farther, let's run tourbus. Leave Sinatra running and
38
+ open another terminal window. Go into the contact_app folder and just
39
+ type "tourbus". You should get a screenful of information ending with
40
+ a happy little banner something like this:
41
+
42
+ 2009-01-10 12:09:36 TourBus: --------------------------------------------------------------------------------
43
+ 2009-01-10 12:09:36 TourBus: 1 runs: 1x1 of simple
44
+ 2009-01-10 12:09:36 TourBus: All Runners finished.
45
+ 2009-01-10 12:09:36 TourBus: Total Runs: 1
46
+ 2009-01-10 12:09:36 TourBus: Total Passes: 1
47
+ 2009-01-10 12:09:36 TourBus: Total Fails: 0
48
+ 2009-01-10 12:09:36 TourBus: Total Errors: 0
49
+ 2009-01-10 12:09:36 TourBus: Elapsed Time: 0.0131220817565918
50
+ 2009-01-10 12:09:36 TourBus: Speed: 76.207 v/s
51
+ 2009-01-10 12:09:36 TourBus: --------------------------------------------------------------------------------
52
+
53
+ == Tourbus Defaults
54
+
55
+ Tourbus tries to be sensible; if you don't provide a number of runs or
56
+ concurrency, it sets them to 1. If you don't choose a tour to run, it
57
+ runs them all. It looks for tourbus.yml in the current folder,
58
+ ./tours, in ./config (a Rails convention), and in your home folder.
59
+ (It looks for them in that order, and stops as soon as it finds one.
60
+ It does not merge multiple yaml files together.)
61
+
62
+ == Simple Tour
63
+
64
+ Okay, now let's look at tours/simple.rb.
65
+
66
+ It defines a class named Simple that inherits from Tour. Tourbus won't
67
+ try to run a tour unless the file contains a Tour child class of the
68
+ same name as the file.
69
+
70
+ Inside the class, methods whose names begin with test_ will
71
+ automatically be run as part of the tour. They are not run in any
72
+ particular order.
73
+
74
+ === test_home
75
+
76
+ Right. Let's look test_home first, because it's simpler:
77
+
78
+ def test_home
79
+ open_site_page "/"
80
+ click_link :text => /Enter Contact/
81
+ assert_page_uri_matches "/contacts"
82
+ end
83
+
84
+ +open_site_page+ is defined in Tour.rb, it opens the given path on the
85
+ host that tourbus is testing.
86
+
87
+ +click_link+ does what you'd expect. It takes a hash that identifies
88
+ the link to click. In this case I chose to identify the link with a
89
+ regexp describing its text label. +click_link+ will raise an exception
90
+ if it cannot find the link to click.
91
+
92
+ +assert_page_uri_matches+ will raise an exception unless the uri
93
+ matches the given string or regexp. If I had passed in a regexp, it
94
+ would have passed if the regexp matched. *Note:* Strings only match at
95
+ the /end/ of the uri; simple containment is not enough. Passing
96
+ "/contacts" works the same as passing %r{/contacts$}.
97
+
98
+ Clear as mud? "/contacts" would match
99
+ http://localhost:4567/users/42/contacts but not
100
+ http://localhost:4567/contacts/42.
101
+
102
+
103
+ === test_contacts
104
+
105
+ Okay, let's actually submit a form.
106
+
107
+ def test_contacts
108
+ open_site_page "contacts"
109
+ submit_form(
110
+ :identified_by => { :action => %r{/contacts} },
111
+ :values => {
112
+ 'first_name' => "Joe",
113
+ 'last_name' => "Tester"
114
+ }
115
+ )
116
+ assert_page_uri_matches "/contacts"
117
+ assert_page_body_contains "Tester, Joe"
118
+ end
119
+
120
+ test_contacts starts by going directly to the contacts app. Note that
121
+ the leading "/" is optional.
122
+
123
+ +submit_form+ does what its name implies. It finds the correct form to
124
+ submit by matching the action to a regexp, then it sets the form
125
+ values and submits the form. *Note:* Like +click_link+, +submit_form+
126
+ contains some implicit assertions. It actually reads the form looking
127
+ for the named inputs and will raise an exception if any are missing.
128
+ This means you cannot use submit_form to do a blind post to a
129
+ webserver.
130
+
131
+ +assert_page_uri_matches+ we've already seen;
132
+ +assert_page_body_contains+ searches the page body for the given text
133
+ or regexp.
134
+
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ # Contact app. Example Sinatra application that you can use to test tourbus.
4
+ #
5
+ # Pretty simple applet. You go to / and enter your contact
6
+ # information. When you click submit, it shows you your name in all
7
+ # caps. Okay, "pretty simple" was an understatement. I get that. Shut up.
8
+ require 'rubygems'
9
+ require 'sinatra'
10
+
11
+ get '/' do
12
+ '<a href="/contacts">Enter Contact</a>'
13
+ end
14
+
15
+ get '/contacts' do
16
+ <<-eos
17
+ <html>
18
+ <head>
19
+ <title>Contact App</title>
20
+ </head>
21
+ <body>
22
+ <h1>Contact Info:</h1>
23
+ <form action="/contacts" method="POST">
24
+ <p><label for="first_name"><b>First Name:</b></label> <input name="first_name" size="30"></p>
25
+ <p><label for="last_name"><b>Last Name:</b></label> <input name="last_name" size="30"></p>
26
+ <input type="submit">
27
+ </form>
28
+ </body>
29
+ </html>
30
+ eos
31
+ end
32
+
33
+ post '/contacts' do
34
+ "<h1>#{params[:last_name]}, #{params[:first_name]}</h1>"
35
+ end
36
+
@@ -0,0 +1,20 @@
1
+ class Simple < Tour
2
+ def test_home
3
+ open_site_page "/"
4
+ click_link :text => /Enter Contact/
5
+ assert_page_uri_matches "/contacts"
6
+ end
7
+
8
+ def test_contacts
9
+ open_site_page "contacts"
10
+ submit_form(
11
+ :identified_by => { :action => %r{/contacts} },
12
+ :values => {
13
+ 'first_name' => "Joe",
14
+ 'last_name' => "Tester"
15
+ }
16
+ )
17
+ assert_page_uri_matches "/contacts"
18
+ assert_page_body_contains "Tester, Joe"
19
+ end
20
+ end
@@ -0,0 +1,2 @@
1
+ ---
2
+ host: localhost
data/lib/common.rb ADDED
@@ -0,0 +1,31 @@
1
+ # common.rb - Common settings, requires and helpers
2
+ unless defined? TOURBUS_LIB_PATH
3
+ TOURBUS_LIB_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ $:<< TOURBUS_LIB_PATH unless $:.include? TOURBUS_LIB_PATH
5
+ end
6
+
7
+ require 'rubygems'
8
+
9
+ gem 'mechanize', ">= 0.8.5"
10
+ gem 'trollop', ">= 1.10.0"
11
+ gem 'faker', '>= 0.3.1'
12
+
13
+ # TODO: I'd like to remove dependency on Rails. Need to see what all
14
+ # we're using (like classify) and remove each dependency individually.
15
+ require 'activesupport'
16
+
17
+ require 'monitor'
18
+ require 'faker'
19
+ require 'web-sickle/init'
20
+ require 'tour_bus'
21
+ require 'runner'
22
+ require 'tour'
23
+
24
+ class TourBusException < Exception; end
25
+
26
+ def require_all_files_in_folder(folder, extension = "*.rb")
27
+ for file in Dir[File.join('.', folder, "**/#{extension}")]
28
+ require file
29
+ end
30
+ end
31
+
data/lib/runner.rb ADDED
@@ -0,0 +1,72 @@
1
+ require 'monitor'
2
+ require 'common'
3
+
4
+ class Runner
5
+ attr_reader :host, :tours, :number, :runner_type, :runner_id
6
+
7
+ def initialize(host, tours, number, runner_id, test_list)
8
+ @host, @tours, @number, @runner_id, @test_list = host, tours, number, runner_id, test_list
9
+ @runner_type = self.send(:class).to_s
10
+ log("Ready to run #{@runner_type}")
11
+ end
12
+
13
+ # Dispatches to subclass run method
14
+ def run_tours
15
+ log "Filtering on tests #{@test_list.join(', ')}" unless @test_list.to_a.empty?
16
+ tours,tests,passes,fails,errors = 0,0,0,0,0
17
+ 1.upto(number) do |num|
18
+ log("Starting #{@runner_type} run #{num}/#{number}")
19
+ @tours.each do |tour_name|
20
+
21
+ log("Starting run #{number} of Tour #{tour_name}")
22
+ tours += 1
23
+ tour = Tour.make_tour(tour_name,@host,@tours,@number,@runner_id)
24
+ tour.before_tour
25
+
26
+ tour.tests.each do |test|
27
+
28
+ next if test_limited_to(test) # test_list && !test_list.empty? && !test_list.include?(test.to_s)
29
+
30
+ begin
31
+ tests += 1
32
+ tour.run_test test
33
+ passes += 1
34
+ rescue TourBusException, WebsickleException => e
35
+ log("********** FAILURE IN RUN! **********")
36
+ log e.message
37
+ e.backtrace.each do |trace|
38
+ log trace
39
+ end
40
+ fails += 1
41
+ rescue Exception => e
42
+ log("*************************************")
43
+ log("*********** ERROR IN RUN! ***********")
44
+ log("*************************************")
45
+ log e.message
46
+ e.backtrace.each do |trace|
47
+ log trace
48
+ end
49
+ errors += 1
50
+ end
51
+ log("Finished run #{number} of Tour #{tour_name}")
52
+ end
53
+
54
+ tour.after_tour
55
+ end
56
+ log("Finished #{@runner_type} run #{num}/#{number}")
57
+ end
58
+ log("Finished all #{@runner_type} tours.")
59
+ [tours,tests,passes,fails,errors]
60
+ end
61
+
62
+ protected
63
+
64
+ def log(message)
65
+ puts "#{Time.now.strftime('%F %H:%M:%S')} Runner ##{@runner_id}: #{message}"
66
+ end
67
+
68
+ def test_limited_to(test_name)
69
+ @test_list && !@test_list.empty? && !@test_list.include?(test_name.to_s)
70
+ end
71
+ end
72
+