steam 0.0.2

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.
Files changed (62) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/README.textile +91 -0
  3. data/Rakefile +23 -0
  4. data/TODO +7 -0
  5. data/lib/core_ext/ruby/array/flatten_once.rb +9 -0
  6. data/lib/core_ext/ruby/hash/except.rb +11 -0
  7. data/lib/core_ext/ruby/hash/slice.rb +14 -0
  8. data/lib/core_ext/ruby/kernel/silence_warnings.rb +8 -0
  9. data/lib/core_ext/ruby/process/daemon.rb +23 -0
  10. data/lib/core_ext/ruby/string/camelize.rb +5 -0
  11. data/lib/core_ext/ruby/string/underscore.rb +5 -0
  12. data/lib/steam.rb +43 -0
  13. data/lib/steam/browser.rb +24 -0
  14. data/lib/steam/browser/html_unit.rb +87 -0
  15. data/lib/steam/browser/html_unit/actions.rb +176 -0
  16. data/lib/steam/browser/html_unit/client.rb +74 -0
  17. data/lib/steam/browser/html_unit/connection.rb +79 -0
  18. data/lib/steam/browser/html_unit/drb.rb +45 -0
  19. data/lib/steam/browser/html_unit/matchers.rb +57 -0
  20. data/lib/steam/browser/html_unit/page.rb +51 -0
  21. data/lib/steam/browser/html_unit/web_response.rb +116 -0
  22. data/lib/steam/connection.rb +9 -0
  23. data/lib/steam/connection/mock.rb +57 -0
  24. data/lib/steam/connection/net_http.rb +42 -0
  25. data/lib/steam/connection/open_uri.rb +24 -0
  26. data/lib/steam/connection/rails.rb +20 -0
  27. data/lib/steam/connection/static.rb +33 -0
  28. data/lib/steam/java.rb +74 -0
  29. data/lib/steam/process.rb +53 -0
  30. data/lib/steam/request.rb +49 -0
  31. data/lib/steam/response.rb +13 -0
  32. data/lib/steam/session.rb +30 -0
  33. data/lib/steam/session/rails.rb +33 -0
  34. data/lib/steam/version.rb +3 -0
  35. data/test/all.rb +3 -0
  36. data/test/browser/html_unit/actions_test.rb +183 -0
  37. data/test/browser/html_unit/javascript_test.rb +60 -0
  38. data/test/browser/html_unit/rails_actions_test.rb +151 -0
  39. data/test/browser/html_unit_test.rb +97 -0
  40. data/test/connection/cascade_test.rb +42 -0
  41. data/test/connection/mock_test.rb +58 -0
  42. data/test/connection/rails_test.rb +16 -0
  43. data/test/connection/static_test.rb +14 -0
  44. data/test/fixtures/html_fakes.rb +191 -0
  45. data/test/java_test.rb +29 -0
  46. data/test/playground/connection.rb +19 -0
  47. data/test/playground/dragdrop_behavior.rb +60 -0
  48. data/test/playground/drb.rb +55 -0
  49. data/test/playground/java_signature.rb +22 -0
  50. data/test/playground/nokogiri.rb +15 -0
  51. data/test/playground/put_headers.rb +83 -0
  52. data/test/playground/rack.rb +11 -0
  53. data/test/playground/rjb_bind.rb +42 -0
  54. data/test/playground/stack_level_problem.rb +129 -0
  55. data/test/playground/thread_problem.rb +57 -0
  56. data/test/playground/web_response_data.rb +21 -0
  57. data/test/playground/webrat.rb +48 -0
  58. data/test/playground/xhr_accept_headers.rb +61 -0
  59. data/test/process_test.rb +55 -0
  60. data/test/session_test.rb +15 -0
  61. data/test/test_helper.rb +56 -0
  62. metadata +135 -0
@@ -0,0 +1,42 @@
1
+ # This connection proxies requests using open-uri.
2
+ #
3
+
4
+ require 'net/http'
5
+ require 'uri'
6
+
7
+ Net::HTTPResponse.class_eval do
8
+ attr_reader :code # crap
9
+ end
10
+
11
+ module Steam
12
+ module Connection
13
+ class NetHttp
14
+ def call(env)
15
+ request = Rack::Request.new(env)
16
+ response = send(request.request_method.downcase, request)
17
+ response = follow_redirect(response) if response.status.to_s[0, 1] == '3'
18
+ response.to_a
19
+ end
20
+
21
+ def get(request)
22
+ url = URI.parse(request.url)
23
+ response = Net::HTTP.start(url.host, url.port) { |http| http.get(url.path) }
24
+ Rack::Response.new(response.body, response.code, response.to_hash)
25
+ end
26
+
27
+ def post(request)
28
+ url = URI.parse(request.url)
29
+ params = Rack::Utils.parse_query(Rack::Utils.build_nested_query(request.params))
30
+ response = Net::HTTP.post_form(url, params)
31
+ Rack::Response.new(response.body, response.code, response.to_hash)
32
+ end
33
+
34
+ def follow_redirect(response)
35
+ location = response.headers['location'].join
36
+ env = Steam::Request.env_for(location)
37
+ request = Rack::Request.new(env)
38
+ get(request)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ # This connection proxies requests using open-uri.
2
+ #
3
+
4
+ require 'open-uri'
5
+
6
+ module Steam
7
+ module Connection
8
+ class OpenUri
9
+ def call(env)
10
+ request = Rack::Request.new(env)
11
+ Rack::Response.new(*open(request.url)).to_a
12
+ end
13
+
14
+ def open(uri)
15
+ headers = {}
16
+ body = Kernel.open(uri) { |f| headers = f.meta; f.read }
17
+ [body, 200, headers]
18
+ rescue OpenURI::HTTPError => e
19
+ status = e.message.split(' ').first
20
+ ['', status, headers]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module Steam
2
+ module Connection
3
+ class Rails
4
+ def initialize
5
+ @app = ActionController::Dispatcher.new
6
+ end
7
+
8
+ def call(env)
9
+ status, headers, response = @app.call(env)
10
+ Rack::Response.new(response_body(response), status, headers).to_a
11
+ end
12
+
13
+ def response_body(response)
14
+ body = ''
15
+ response.each { |part| body << part } # yuck
16
+ body
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ require 'rack/response'
2
+
3
+ module Steam
4
+ module Connection
5
+ class Static < Rack::Static
6
+ def initialize(*args)
7
+ options = args.last.is_a?(Hash) ? args.pop : {}
8
+ options[:urls] ||= %w(/images /javascripts /stylesheets)
9
+ app = args.pop
10
+ super(app || lambda { response_404 }, options)
11
+ end
12
+
13
+ def call(env)
14
+ status, headers, response = super
15
+
16
+ response = case response
17
+ when Rack::File
18
+ Rack::Response.new(::File.read(response.path), status, headers).to_a
19
+ when Array
20
+ Rack::Response.new(response.first, status, headers).to_a
21
+ else
22
+ [status, headers, response]
23
+ end
24
+
25
+ response
26
+ end
27
+
28
+ def response_404
29
+ Rack::Response.new('', 404).to_a
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,74 @@
1
+ require 'rjb'
2
+ require 'core_ext/ruby/string/camelize'
3
+ require 'core_ext/ruby/string/underscore'
4
+
5
+ module Steam
6
+ module Java
7
+
8
+ # TODO replace this with http://github.com/sklemm/jrequire/blob/master/lib/java.rb
9
+
10
+ module AutoDefine
11
+ def const_set_nested(full_name, const)
12
+ name = pop_name(full_name)
13
+ if full_name.empty? && !const_defined?(name)
14
+ const_set(name, const)
15
+ else
16
+ const_set(name, Module.new { extend AutoDefine }) unless const_defined?(name)
17
+ const_get(name).const_set_nested(full_name, const) unless full_name.empty?
18
+ end
19
+ end
20
+
21
+ def pop_name(string)
22
+ name, *rest = string.split('::')
23
+ string.replace(rest.join('::'))
24
+ name
25
+ end
26
+ end
27
+ extend AutoDefine
28
+
29
+ class << self
30
+ def const_missing(name)
31
+ return init && const_get(name) unless @initialized
32
+ super
33
+ end
34
+
35
+ def import(signature, name = nil)
36
+ init unless @initialized
37
+ name = path_to_const_name(signature)
38
+ const_set_nested(name, Rjb::import(signature))
39
+ end
40
+
41
+ def path_to_const_name(path)
42
+ path.split('.').map { |token| token.underscore.camelize }.join('::').gsub('Java::', '')
43
+ end
44
+
45
+ def init
46
+ @initialized = true
47
+
48
+ import('java.net.URL')
49
+ import('java.lang.System')
50
+ import('java.util.Arrays')
51
+ import('java.util.ArrayList')
52
+ import('java.util.logging.Logger')
53
+ import('java.util.logging.Level')
54
+ end
55
+
56
+ def load_from(path)
57
+ paths = Dir["#{Steam.config[:html_unit][:java_path]}/*.jar"]
58
+ load(paths.join(':')) unless paths.empty?
59
+ end
60
+
61
+ def load(paths)
62
+ Rjb::load(paths, Steam.config[:java_load_params].to_a)
63
+ end
64
+
65
+ def logger(classifier)
66
+ Java::Util::Logging::Logger.getLogger(classifier)
67
+ end
68
+
69
+ def log_level(name)
70
+ Java::Util::Logging::Level.send(name.to_s.upcase)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,53 @@
1
+ module Steam
2
+ class Process
3
+ attr_reader :pid, :path
4
+
5
+ def initialize(path = '/tmp')
6
+ @path = path
7
+ recall_pid
8
+ end
9
+
10
+ def pid?
11
+ !!pid
12
+ end
13
+
14
+ def running?
15
+ pid? && !!::Process.getpgid(pid)
16
+ rescue Errno::ESRCH
17
+ false
18
+ end
19
+
20
+ def fork(options)
21
+ @pid = Kernel.fork { yield }
22
+ write_pid
23
+ # Process.detach(pid) ?
24
+ at_exit { kill } unless options[:keep_alive]
25
+ end
26
+
27
+ def kill(signal = 'TERM')
28
+ ::Process.kill(Signal.list[signal], pid)
29
+ delete_pid
30
+ true
31
+ end
32
+
33
+ protected
34
+
35
+ def recall_pid
36
+ @pid = ::File.read(filename).to_i rescue nil
37
+ delete_pid if pid? && !running?
38
+ end
39
+
40
+ def write_pid
41
+ File.open(filename, 'w') { |f| f.write(pid) }
42
+ end
43
+
44
+ def delete_pid
45
+ File.delete(filename) rescue Errno::ENOENT
46
+ @pid = nil
47
+ end
48
+
49
+ def filename
50
+ "#{path}/steam.pid"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,49 @@
1
+ # Extends a Rack::Request with convenience methods
2
+
3
+ require 'uri'
4
+
5
+ module Steam
6
+ class Request < Rack::Request
7
+ attr_accessor :method, :headers
8
+
9
+ DEFAULT_ENV = {
10
+ "rack.version" => [0, 1],
11
+ "rack.input" => StringIO.new,
12
+ "rack.errors" => StringIO.new,
13
+ "rack.multithread" => true,
14
+ "rack.multiprocess" => true,
15
+ "rack.run_once" => false,
16
+ }
17
+
18
+ class << self
19
+ def env_for(uri = '', opts = {})
20
+ env = DEFAULT_ENV.dup
21
+ uri = URI.parse(uri)
22
+ input = opts[:input] || ''
23
+
24
+ env.merge!(
25
+ 'REQUEST_METHOD' => opts[:method] || 'GET',
26
+ 'SERVER_NAME' => Steam.config[:server_name],
27
+ 'SERVER_PORT' => Steam.config[:server_port],
28
+ 'QUERY_STRING' => uri.query.to_s,
29
+ 'PATH_INFO' => (!uri.path || uri.path.empty?) ? '/' : uri.path,
30
+ 'rack.url_scheme' => Steam.config[:url_scheme],
31
+ 'SCRIPT_NAME' => opts[:script_name] || '',
32
+ 'rack.errors' => StringIO.new,
33
+ 'rack.input' => input.is_a?(String) ? StringIO.new(input) : input,
34
+ 'CONTENT_LENGTH' => env['rack.input'].length.to_s,
35
+ 'rack.test.scheme' => uri.scheme || 'http',
36
+ 'rack.test.host' => uri.host || 'www.example.com',
37
+ 'rack.test.port' => uri.port,
38
+ 'rack.test.cache_classes' => true
39
+ )
40
+ env
41
+ end
42
+ end
43
+
44
+ def initialize(method, uri, opts = {})
45
+ env = self.class.env_for(uri, opts.merge(:method => method))
46
+ super(env)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ # Not sure about this. The only reason why this class is needed is that
2
+ # Rack::Response#body returns an Array of body parts while in Rails integration
3
+ # tests response.body returns a String.
4
+
5
+ require 'uri'
6
+
7
+ module Steam
8
+ class Response < Rack::Response
9
+ def body
10
+ super.join
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ # Conceptually a session is something different than a browser. E.g. a (test-)
2
+ # session can be started (setting up test data etc.) and stopped (cleaning
3
+ # stuff up etc.). Webrat and Capybara don't separate these concepts. So let's
4
+ # keep them separate though from the beginning even though we don't implement
5
+ # any such behavior so far.
6
+
7
+ module Steam
8
+ class Session
9
+ autoload :Rails, 'steam/session/rails'
10
+
11
+ attr_accessor :browser
12
+
13
+ def initialize(browser = nil)
14
+ @browser = browser
15
+ end
16
+
17
+ def respond_to?(method)
18
+ browser.respond_to?(method) ? true : super
19
+ end
20
+
21
+ def method_missing(method, *args, &block)
22
+ return browser.send(method, *args, &block) # if browser.respond_to?(method)
23
+ super
24
+ end
25
+
26
+ def select(*args, &block) # because Class implements #select
27
+ browser.select(*args, &block)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module Steam
2
+ class Session
3
+ class Rails < Session
4
+ include ActionController
5
+
6
+ def initialize(browser = nil)
7
+ super
8
+
9
+ # install the named routes in this session instance.
10
+ klass = class << self; self; end
11
+ ActionController::Routing::Routes.install_helpers(klass)
12
+ klass.module_eval { public *Routing::Routes.named_routes.helpers }
13
+ end
14
+
15
+ # FIXME we only have access to the controller when Steam can run HtmlUnit
16
+ # and the app in one stack
17
+ def controller
18
+ end
19
+
20
+ def url_for(options)
21
+ controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
22
+ end
23
+
24
+ protected
25
+
26
+ # FIXME remove ActionController::Request dependency
27
+ def generic_url_rewriter
28
+ env = Request.env_for(Steam.config[:request_url])
29
+ UrlRewriter.new(ActionController::Request.new(env), {})
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module Steam
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,3 @@
1
+ Dir[File.dirname(__FILE__) + '/**/*_test.rb'].each do |filename|
2
+ require filename unless filename.include?('_locator')
3
+ end
@@ -0,0 +1,183 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+ require 'fixtures/html_fakes'
3
+
4
+ class HtmlUnitActionsTest < Test::Unit::TestCase
5
+ include Steam, HtmlFakes
6
+
7
+ def setup
8
+ @app = Steam::Connection::Mock.new
9
+ static = Steam::Connection::Static.new(:root => FIXTURES_PATH)
10
+ @browser = Steam::Browser::HtmlUnit.new(Rack::Cascade.new([static, @app]))
11
+ end
12
+
13
+ test "click_on clicks on an element" do
14
+ perform :get, 'http://localhost:3000/', html
15
+
16
+ assert_response_contains('LINK') do
17
+ @app.mock :get, 'http://localhost:3000/link', 'LINK'
18
+ @browser.click_on('link')
19
+ end
20
+ end
21
+
22
+ test "click_on clicks a button" do
23
+ perform :get, 'http://localhost:3000/', html(:fields => :text)
24
+
25
+ assert_response_contains('FORM') do
26
+ @app.mock :get, 'http://localhost:3000/form?field=', 'FORM'
27
+ @browser.click_on(:button, 'button')
28
+ end
29
+ end
30
+
31
+ test "click_link clicks a link" do
32
+ perform :get, 'http://localhost:3000/', html
33
+
34
+ assert_response_contains('LINK') do
35
+ @app.mock :get, 'http://localhost:3000/link', 'LINK'
36
+ @browser.click_link('link')
37
+ end
38
+ end
39
+
40
+ test "click_button clicks a button" do
41
+ perform :get, 'http://localhost:3000/', html(:fields => :text)
42
+
43
+ assert_response_contains('FORM') do
44
+ @app.mock :get, 'http://localhost:3000/form?field=', 'FORM'
45
+ @browser.click_button('button')
46
+ end
47
+ end
48
+
49
+ test "fill_in fills in a text input" do
50
+ perform :get, 'http://localhost:3000/', html(:fields => :text)
51
+
52
+ assert_response_contains('FIELD') do
53
+ @app.mock :get, 'http://localhost:3000/form?field=text', 'FIELD'
54
+ @browser.fill_in('field', :with => 'text')
55
+ @browser.click_button('button')
56
+ end
57
+ end
58
+
59
+ test "fill_in fills in a textarea" do
60
+ perform :get, 'http://localhost:3000/', html(:fields => :textarea)
61
+
62
+ assert_response_contains('TEXTAREA') do
63
+ @app.mock :get, 'http://localhost:3000/form?textarea=text', 'TEXTAREA'
64
+ @browser.fill_in('textarea', :with => 'text')
65
+ @browser.click_button('button')
66
+ end
67
+ end
68
+
69
+ test "check checks a checkbox" do
70
+ perform :get, 'http://localhost:3000/', html(:fields => :checkbox)
71
+
72
+ assert_response_contains('CHECKED') do
73
+ @app.mock :get, 'http://localhost:3000/form?checkbox=1', 'CHECKED'
74
+ @browser.check('checkbox')
75
+ @browser.click_button('button')
76
+ end
77
+ end
78
+
79
+ test "uncheck unchecks a checkbox" do
80
+ perform :get, 'http://localhost:3000/', html(:fields => :checkbox)
81
+
82
+ assert_response_contains('FORM') do
83
+ @app.mock :get, 'http://localhost:3000/form', 'FORM'
84
+ @browser.check('checkbox')
85
+ @browser.uncheck('checkbox')
86
+ @browser.click_button('button')
87
+ end
88
+ end
89
+
90
+ test "choose activates a radio button" do
91
+ perform :get, 'http://localhost:3000/', html(:fields => :radio)
92
+
93
+ assert_response_contains('RADIO') do
94
+ @app.mock :get, 'http://localhost:3000/form?radio=radio', 'RADIO'
95
+ @browser.choose('radio')
96
+ @browser.click_button('button')
97
+ end
98
+ end
99
+
100
+ test "select selects an option from a select box" do
101
+ perform :get, 'http://localhost:3000/', html(:fields => :select)
102
+
103
+ assert_response_contains('SELECT') do
104
+ @app.mock :get, 'http://localhost:3000/form?select=foo', 'SELECT'
105
+ @browser.select('foo', :from => 'select')
106
+ @browser.click_button('button')
107
+ end
108
+ end
109
+
110
+ test "set_hidden_field sets a value to a hidden field" do
111
+ perform :get, 'http://localhost:3000/', html(:fields => :hidden)
112
+
113
+ assert_response_contains('SELECT') do
114
+ @app.mock :get, 'http://localhost:3000/form?hidden=foo', 'SELECT'
115
+ @browser.set_hidden_field('hidden', :to => 'foo')
116
+ @browser.click_button('button')
117
+ end
118
+ end
119
+
120
+ test "attach_file sets a filename to a file field" do
121
+ perform :get, 'http://localhost:3000/', html(:fields => :file)
122
+
123
+ assert_response_contains('FILE') do
124
+ @app.mock :get, 'http://localhost:3000/form?file=rails.png', 'FILE'
125
+ @browser.attach_file('file', "#{TEST_ROOT}/fixtures/rails.png")
126
+ @browser.click_button('button')
127
+ end
128
+ end
129
+
130
+ test "submit_form submits a form" do
131
+ perform :get, 'http://localhost:3000/', html(:fields => :text)
132
+
133
+ assert_response_contains('FORM') do
134
+ @app.mock :get, 'http://localhost:3000/form?field=', 'FORM'
135
+ @browser.submit_form('form')
136
+ end
137
+ end
138
+
139
+ test "drag drags an draggable and drop drops the draggable onto a droppable" do
140
+ perform :get, 'http://localhost:3000/', html(:scripts => [:jquery, :jquery_ui, :drag])
141
+
142
+ @browser.drag('link')
143
+ @browser.drop('form')
144
+ assert_equal 'DROPPED!', @browser.page.getTitleText
145
+ end
146
+
147
+ test "drag_and_drop drags a draggable and drops it onto a droppable" do
148
+ perform :get, 'http://localhost:3000/', html(:scripts => [:jquery, :jquery_ui, :drag])
149
+
150
+ @browser.drag_and_drop('link', :to => 'form')
151
+ assert_equal 'DROPPED!', @browser.page.getTitleText
152
+ end
153
+
154
+ test "hover triggers a mouseOver event on an element" do
155
+ perform :get, 'http://localhost:3000/', html(:scripts => [:jquery, :jquery_ui, :hover])
156
+
157
+ @browser.hover('paragraph')
158
+ assert_equal 'HOVERED!', @browser.page.getTitleText
159
+ end
160
+
161
+ test "focus triggers a focus event on an element" do
162
+ perform :get, 'http://localhost:3000/', html(:fields => :text, :scripts => [:jquery, :jquery_ui, :focus])
163
+
164
+ @browser.focus('field')
165
+ assert_equal 'FOCUSED!', @browser.page.getTitleText
166
+ end
167
+
168
+ test "blur triggers a blur event on an element" do
169
+ perform :get, 'http://localhost:3000/', html(:fields => :text, :scripts => [:jquery, :jquery_ui, :blur])
170
+
171
+ @browser.focus('field')
172
+ @browser.blur('field')
173
+ assert_equal 'BLURRED!', @browser.page.getTitleText
174
+ end
175
+
176
+ test "double_click doubleclicks an element" do
177
+ perform :get, 'http://localhost:3000/', html(:scripts => [:jquery, :jquery_ui, :double_click])
178
+
179
+ @browser.double_click('paragraph')
180
+ assert_equal 'DOUBLE CLICKED!', @browser.page.getTitleText
181
+ end
182
+
183
+ end