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 +22 -0
- data/README.rdoc +200 -0
- data/bin/tourbus +46 -0
- data/bin/tourwatch +52 -0
- data/examples/contact_app/README.rdoc +134 -0
- data/examples/contact_app/contact_app.rb +36 -0
- data/examples/contact_app/tours/simple.rb +20 -0
- data/examples/contact_app/tours/tourbus.yml +2 -0
- data/lib/common.rb +31 -0
- data/lib/runner.rb +72 -0
- data/lib/tour.rb +145 -0
- data/lib/tour_bus.rb +92 -0
- data/lib/tour_watch.rb +88 -0
- data/lib/web-sickle/init.rb +17 -0
- data/lib/web-sickle/lib/assertions.rb +51 -0
- data/lib/web-sickle/lib/hash_proxy.rb +9 -0
- data/lib/web-sickle/lib/helpers/asp_net.rb +16 -0
- data/lib/web-sickle/lib/helpers/table_reader.rb +39 -0
- data/lib/web-sickle/lib/make_nokigiri_output_useful.rb +15 -0
- data/lib/web-sickle/lib/web_sickle.rb +227 -0
- data/lib/web-sickle/spec/lib/helpers/table_reader_spec.rb +137 -0
- data/lib/web-sickle/spec/spec_helper.rb +7 -0
- data/lib/web-sickle/spec/spec_helpers/mechanize_mock_helper.rb +12 -0
- data/lib/web-sickle/spec/web_sickle_spec.rb +50 -0
- data/lib/web_sickle_webrat_adapter.rb +40 -0
- metadata +125 -0
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
|
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
|
+
|