jtzemp-tourbus 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tour.rb ADDED
@@ -0,0 +1,145 @@
1
+ require 'forwardable'
2
+ require 'monitor'
3
+ require 'common'
4
+ require 'webrat'
5
+ require 'webrat/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
+ class Tour
15
+ extend Forwardable
16
+ include Webrat::Matchers
17
+ include Webrat::SaveAndOpenPage
18
+ include Test::Unit::Assertions
19
+
20
+ attr_reader :host, :tours, :number, :tour_type, :tour_id, :webrat_session
21
+
22
+ # delegate goodness to webrat
23
+ [
24
+ :fill_in,
25
+ :fills_in,
26
+ :set_hidden_field,
27
+ :submit_form,
28
+ :check,
29
+ :checks,
30
+ :uncheck,
31
+ :unchecks,
32
+ :choose,
33
+ :chooses,
34
+ :select,
35
+ :selects,
36
+ :select_datetime,
37
+ :selects_datetime,
38
+ :select_date,
39
+ :selects_date,
40
+ :select_time,
41
+ :selects_time,
42
+ :attach_file,
43
+ :attaches_file,
44
+ :click_area,
45
+ :clicks_area,
46
+ :click_link,
47
+ :clicks_link,
48
+ :click_button,
49
+ :clicks_button,
50
+ :field_labeled,
51
+ :field_by_xpath,
52
+ :field_with_id,
53
+ :select_option,
54
+ :automate,
55
+ :basic_auth,
56
+ :check_for_infinite_redirects,
57
+ :click_link_within,
58
+ :dom,
59
+ :header,
60
+ :http_accept,
61
+ :infinite_redirect_limit_exceeded?,
62
+ :internal_redirect?,
63
+ :redirected_to,
64
+ :reload,
65
+ :response_body,
66
+ :simulate,
67
+ :visit,
68
+ :within,
69
+ :xml_content_type?].each {|m| def_delegators(:webrat_session, m) }
70
+
71
+ def initialize(host, tours, number, tour_id)
72
+ @host, @tours, @number, @tour_id = host, tours, number, tour_id
73
+ @tour_type = self.send(:class).to_s
74
+ @webrat_session = Webrat::MechanizeSession.new
75
+ end
76
+
77
+ # before_tour runs once per tour, before any tests get run
78
+ def before_tour; end
79
+
80
+ # after_tour runs once per tour, after all the tests have run
81
+ def after_tour; end
82
+
83
+ def setup
84
+ end
85
+
86
+ def teardown
87
+ end
88
+
89
+ def wait(time)
90
+ sleep time.to_i
91
+ end
92
+
93
+ # Lists tours in tours folder. If a string is given, filters the
94
+ # list by that string. If an array of filter strings is given,
95
+ # returns items that match ANY filter string in the array.
96
+ def self.tours(filter=[])
97
+ filter = [filter].flatten
98
+ # All files in tours folder, stripped to basename, that match any item in filter
99
+ # I do loves me a long chain. This returns an array containing
100
+ # 1. All *.rb files in tour folder (recursive)
101
+ # 2. Each filename stripped to its basename
102
+ # 3. If you passed in any filters, these basenames are rejected unless they match at least one filter
103
+ # 4. The filenames remaining are then checked to see if they define a class of the same name that inherits from Tour
104
+ 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 }
105
+ end
106
+
107
+ def self.tests(tour_name)
108
+ Tour.make_tour(tour_name).tests
109
+ end
110
+
111
+ def self.tour?(tour_name)
112
+ Object.const_defined?(tour_name.classify) && tour_name.classify.constantize.ancestors.include?(Tour)
113
+ end
114
+
115
+ # Factory method, creates the named child class instance
116
+ def self.make_tour(tour_name,host="localhost:3000",tours=[],number=1,tour_id=nil)
117
+ tour_name.classify.constantize.new(host,tours,number,tour_id)
118
+ end
119
+
120
+ # Returns list of tests in this tour. (Meant to be run on a subclass
121
+ # instance; returns the list of tests available).
122
+ def tests
123
+ methods.grep(/^test_/).map {|m| m.sub(/^test_/,'')}
124
+ end
125
+
126
+ def run_test(test_name)
127
+ @test = "test_#{test_name}"
128
+ raise TourBusException.new("run_test couldn't run test '#{test_name}' because this tour did not respond to :#{@test}") unless respond_to? @test
129
+ setup
130
+ send @test
131
+ teardown
132
+ end
133
+
134
+ protected
135
+
136
+ def session
137
+ @session ||= Webrat::MechanizeSession.new
138
+ end
139
+
140
+ def log(message)
141
+ puts "#{Time.now.strftime('%F %H:%M:%S')} Tour ##{@tour_id}: (#{@test}) #{message}"
142
+ end
143
+
144
+ end
145
+
data/lib/tour_bus.rb ADDED
@@ -0,0 +1,92 @@
1
+ require 'benchmark'
2
+
3
+ class TourBus < Monitor
4
+ attr_reader :host, :concurrency, :number, :tours, :runs, :tests, :passes, :fails, :errors, :benchmarks
5
+
6
+ def initialize(host="localhost", concurrency=1, number=1, tours=[], test_list=nil)
7
+ @host, @concurrency, @number, @tours, @test_list = host, concurrency, number, tours, test_list
8
+ @runner_id = 0
9
+ @runs, @tests, @passes, @fails, @errors = 0,0,0,0,0
10
+ super()
11
+ end
12
+
13
+ def next_runner_id
14
+ synchronize do
15
+ @runner_id += 1
16
+ end
17
+ end
18
+
19
+ def update_stats(runs,tests,passes,fails,errors)
20
+ synchronize do
21
+ @runs += runs
22
+ @tests += tests
23
+ @passes += passes
24
+ @fails += fails
25
+ @errors += errors
26
+ end
27
+ end
28
+
29
+ def update_benchmarks(bm)
30
+ synchronize do
31
+ @benchmarks = @benchmarks.zip(bm).map { |a,b| a+b}
32
+ end
33
+ end
34
+
35
+ def runners(filter=[])
36
+ # All files in tours folder, stripped to basename, that match any item in filter
37
+ Dir[File.join('.', 'tours', '**', '*.rb')].map {|fn| File.basename(fn, ".rb")}.select {|fn| filter.size.zero? || filter.any?{|f| fn =~ /#{f}/}}
38
+ end
39
+
40
+ def total_runs
41
+ tours.size * concurrency * number
42
+ end
43
+
44
+ def run
45
+ threads = []
46
+ tour_name = "#{total_runs} runs: #{concurrency}x#{number} of #{tours * ','}"
47
+ started = Time.now.to_f
48
+ concurrency.times do |conc|
49
+ log "Starting #{tour_name}"
50
+ threads << Thread.new do
51
+ runner_id = next_runner_id
52
+ runs,tests,passes,fails,errors,start = 0,0,0,0,0,Time.now.to_f
53
+ bm = Benchmark.measure do
54
+ runner = Runner.new(@host, @tours, @number, runner_id, @test_list)
55
+ runs,tests,passes,fails,errors = runner.run_tours
56
+ update_stats runs, tests, passes, fails, errors
57
+ end
58
+ log "Runner Finished!"
59
+ log "Runner finished in %0.3f seconds" % (Time.now.to_f - start)
60
+ log "Runner Finished! runs,passes,fails,errors: #{runs},#{passes},#{fails},#{errors}"
61
+ log "Benchmark for runner #{runner_id}: #{bm}"
62
+ end
63
+ end
64
+ log "All Runners started!"
65
+ threads.each {|t| t.join }
66
+ finished = Time.now.to_f
67
+ log '-' * 80
68
+ log tour_name
69
+ log "All Runners finished."
70
+ log "Total Tours: #{@runs}"
71
+ log "Total Tests: #{@tests}"
72
+ log "Total Passes: #{@passes}"
73
+ log "Total Fails: #{@fails}"
74
+ log "Total Errors: #{@errors}"
75
+ log "Elapsed Time: #{finished - started}"
76
+ log "Speed: %5.3f tours/sec" % (@runs / (finished-started))
77
+ log '-' * 80
78
+ if @fails > 0 || @errors > 0
79
+ log '********************************************************************************'
80
+ log '********************************************************************************'
81
+ log ' !! THERE WERE FAILURES !!'
82
+ log '********************************************************************************'
83
+ log '********************************************************************************'
84
+ end
85
+ end
86
+
87
+ def log(message)
88
+ puts "#{Time.now.strftime('%F %H:%M:%S')} TourBus: #{message}"
89
+ end
90
+
91
+ end
92
+
data/lib/tour_watch.rb ADDED
@@ -0,0 +1,88 @@
1
+ class TourWatch
2
+ attr_reader :processes
3
+
4
+ def initialize(options={})
5
+ @processes = if options[:processes]
6
+ options[:processes].split(/,/) * '|'
7
+ else
8
+ "ruby|mysql|apache|http|rails|mongrel"
9
+ end
10
+ @cores = options[:cores] || 4
11
+ @logfile = options[:outfile]
12
+ @mac = options[:mac]
13
+ end
14
+
15
+ def stats
16
+ top = @mac ? top_mac : top_linux
17
+ lines = []
18
+ @longest = Hash.new(0)
19
+ top.each_line do |line|
20
+ name,pid,cpu = fields(line.split(/\s+/))
21
+ lines << [name,pid,cpu]
22
+ @longest[:name] = name.size if name.size > @longest[:name]
23
+ @longest[:pid] = pid.to_s.size if pid.to_s.size > @longest[:pid]
24
+ end
25
+ lines
26
+ end
27
+
28
+ def fields(parts)
29
+ @mac ? fields_mac(parts) : fields_linux(parts)
30
+ end
31
+
32
+ # Note: MacOSX is so awesome I just cacked. Top will report 0.0% cpu
33
+ # the first time you run top, every time. The only way to get actual
34
+ # CPU% here is to wait for it to send another page and then throw
35
+ # away the first page. Isn't that just awesome?!? I KNOW!!!
36
+ def top_mac
37
+ top = `top -l 1 | grep -E '(#{@processes})'`
38
+ end
39
+
40
+ def fields_mac(fields)
41
+ name,pid,cpu = fields[1], fields[0].to_i, fields[2].to_f
42
+ end
43
+
44
+ def top_linux
45
+ top = `top -bn 1 | grep -E '(#{@processes})'`
46
+ end
47
+
48
+
49
+ def fields_linux(fields)
50
+ # linux top isn't much smarter. It spits out a blank field ahead
51
+ # of the pid if the pid is too short, which makes the indexes
52
+ # shift off by one.
53
+ a,b,c = if fields.size == 13
54
+ [-1,1,9]
55
+ else
56
+ [-1,0,8]
57
+ end
58
+ name,pid,cpu = fields[a], fields[b].to_i, fields[c].to_f
59
+ end
60
+
61
+
62
+ def run()
63
+ while(true)
64
+ now = Time.now.to_i
65
+ if @time != now
66
+ log '--'
67
+ lines = stats
68
+ lines.sort! {|a,b| a[1]==b[1] ? a[2]<=>b[2] : a[1]<=>b[1] }
69
+ lines.each do |vars|
70
+ vars << bargraph(vars[2], 100 * @cores)
71
+ log "%#{@longest[:name]}s %#{@longest[:pid]}d CPU: %6.2f%% [%-40s]" % vars
72
+ end
73
+ end
74
+ sleep 0.1
75
+ @time = now
76
+ end
77
+ end
78
+
79
+ def bargraph(value, max=100, length=40, on='#', off='.')
80
+ (on * (([[value, 0].max, max].min * length) / max).to_i).ljust(length, off)
81
+ end
82
+
83
+ def log(message)
84
+ msg = "#{Time.now.strftime('%F %H:%M:%S')} TourWatch: #{message}"
85
+ puts msg
86
+ File.open(@logfile, "a") {|f| f.puts msg } if @logfile
87
+ end
88
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ gem 'mechanize', ">= 0.7.6"
3
+ gem "hpricot", ">= 0.6"
4
+ $: << File.join(File.dirname(__FILE__), 'lib')
5
+
6
+ require 'hpricot'
7
+ require 'mechanize'
8
+
9
+ WWW::Mechanize.html_parser = Hpricot
10
+
11
+ require 'web_sickle'
12
+ require "assertions"
13
+ require "hash_proxy"
14
+ require "helpers/asp_net"
15
+ require "helpers/table_reader"
16
+
17
+ Hpricot.buffer_size = 524288
@@ -0,0 +1,51 @@
1
+ class WebSickleAssertionException < Exception; end
2
+
3
+ module WebSickle::Assertions
4
+ def assert_equals(expected, actual, message = nil)
5
+ unless(expected == actual)
6
+ report_error <<-EOF
7
+ Error: Expected
8
+ #{expected.inspect}, but got
9
+ #{actual.inspect}
10
+ #{message}
11
+ EOF
12
+ end
13
+ end
14
+
15
+ def assert_select(selector, message)
16
+ assert_select_in(@page, selector, message)
17
+ end
18
+
19
+ def assert_no_select(selector, message)
20
+ assert_no_select_in(@page, selector, message)
21
+ end
22
+
23
+ def assert_select_in(content, selector, message)
24
+ report_error("Error: Expected selector #{selector.inspect} to find a page element, but didn't. #{message}") if (content / selector).blank?
25
+ end
26
+
27
+ def assert_no_select_in(content, selector, message)
28
+ report_error("Error: Expected selector #{selector.inspect} to not find a page element, but did. #{message}") unless (content / selector).blank?
29
+ end
30
+
31
+ def assert_contains(left, right, message = nil)
32
+ (right.is_a?(Array) ? right : [right]).each do | item |
33
+ report_error("Error: Expected #{left.inspect} to contain #{right.inspect}, but didn't. #{message}") unless left.include?(item)
34
+ end
35
+ end
36
+
37
+ def assert(passes, message = nil)
38
+ report_error("Error: expected true, got false. #{message}") unless passes
39
+ end
40
+
41
+ def assert_link_text(link, text)
42
+ case text
43
+ when String
44
+ assert_equals(link.text, text)
45
+ when Regexp
46
+ assert(link.text.match(text))
47
+ else
48
+ raise ArgumentError, "Don't know how to assert an object like #{text.inspect} - expected: Regexp or String"
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,9 @@
1
+ class HashProxy
2
+ def initialize(options = {})
3
+ @set = options[:set]
4
+ @get = options[:get]
5
+ end
6
+
7
+ def [](key); @get && @get.call(key); end
8
+ def []=(key, value); @set && @set.call(key, value); end
9
+ end
@@ -0,0 +1,16 @@
1
+ module WebSickle::Helpers
2
+ module AspNet
3
+ def asp_net_do_postback(options)
4
+ target_element = case
5
+ when options[:button]
6
+ find_button(options[:button])
7
+ when options[:field]
8
+ find_field(options[:field])
9
+ else
10
+ nil
11
+ end
12
+ @form.fields << WWW::Mechanize::Form::Field.new("__EVENTTARGET", target_element ? target_element.name : "") if target_element
13
+ submit_form_button
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ module WebSickle::Helpers
2
+ class TableReader
3
+ attr_reader :headers, :options, :body_rows, :header_row, :extra_rows
4
+
5
+ def initialize(element, p_options = {})
6
+ @options = {
7
+ :row_selectors => [" > tr", "thead > tr", "tbody > tr"],
8
+ :header_selector => " > th",
9
+ :header_proc => lambda { |th| th.inner_text.gsub(/[\n\s]+/, ' ').strip },
10
+ :body_selector => " > td",
11
+ :body_proc => lambda { |header, td| td.inner_text.strip },
12
+ :header_offset => 0,
13
+ :body_offset => 1
14
+ }.merge(p_options)
15
+ @options[:body_range] ||= options[:body_offset]..-1
16
+ raw_rows = options[:row_selectors].map{|row_selector| element / row_selector}.compact.flatten
17
+
18
+ @header_row = raw_rows[options[:header_offset]]
19
+ @body_rows = raw_rows[options[:body_range]]
20
+ @extra_rows = (options[:body_range].last+1)==0 ? [] : raw_rows[(options[:body_range].last+1)..-1]
21
+
22
+ @headers = (@header_row / options[:header_selector]).map(&options[:header_proc])
23
+ end
24
+
25
+ def rows
26
+ @rows ||= @body_rows.map do |row|
27
+ hash = {}
28
+ data_array = (headers).zip(row / options[:body_selector]).each do |column_name, td|
29
+ hash[column_name] = options[:body_proc].call(column_name, td)
30
+ end
31
+ hash
32
+ end
33
+ end
34
+
35
+ def array_to_hash(data, column_names)
36
+ column_names.inject({}) {|h,column_name| h[column_name] = data[column_names.index(column_name)]; h }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ # Nokogiri::XML::Element.class_eval do
2
+ # def inspect(indent = "")
3
+ # breaker = "\n#{indent}"
4
+ # if children.length == 0
5
+ # %(#{indent}<#{name}#{breaker} #{attributes.map {|k,v| k + '=' + v.inspect} * "#{breaker} "}/>)
6
+ # else
7
+ # %(#{indent}<#{name} #{attributes.map {|k,v| k + '=' + v.inspect} * " "}>\n#{children.map {|c| c.inspect(indent + ' ') rescue c.class} * "\n"}#{breaker}</#{name}>)
8
+ # end
9
+ # end
10
+ # end
11
+ # Nokogiri::XML::Text.class_eval do
12
+ # def inspect(indent = "")
13
+ # "#{indent}#{text.inspect}"
14
+ # end
15
+ # end