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,74 @@
1
+ # Just hides the ugly Java'ish details of instantiating, configuring and using
2
+ # a com.gargoylesoftware.htmlunit.WebClient (HtmlUnit's main browser object).
3
+
4
+ module Steam
5
+ module Browser
6
+ class HtmlUnit
7
+ class Client
8
+ Java.load_from(Steam.config[:html_unit][:java_path])
9
+
10
+ Java.import 'com.gargoylesoftware.htmlunit.Version'
11
+ Java.import 'com.gargoylesoftware.htmlunit.WebClient'
12
+ Java.import 'com.gargoylesoftware.htmlunit.BrowserVersion'
13
+ Java.import 'com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController'
14
+ Java.import 'com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException'
15
+
16
+ include Java::Com::Gargoylesoftware::Htmlunit
17
+
18
+ class SilencingListener
19
+ def notify(message, origin); end
20
+ end
21
+
22
+ def initialize(connection = nil, options = {})
23
+ options = Steam.config[:html_unit].merge(options)
24
+
25
+ @java = WebClient.new(BrowserVersion.send(options[:browser_version]))
26
+ @java.setCssEnabled(options[:css])
27
+ @java.setJavaScriptEnabled(options[:javascript])
28
+ @java.setPrintContentOnFailingStatusCode(options[:on_error_status] == :print)
29
+ @java.setThrowExceptionOnFailingStatusCode(options[:on_error_status] == :raise)
30
+ @java.setThrowExceptionOnScriptError(options[:on_script_error] == :raise)
31
+
32
+ self.log_level = options[:log_level]
33
+ unless options[:log_incorrectness]
34
+ listener = Rjb::bind(SilencingListener.new, 'com.gargoylesoftware.htmlunit.IncorrectnessListener')
35
+ @java.setIncorrectnessListener(listener)
36
+ end
37
+
38
+ if options[:resynchronize]
39
+ controller = NicelyResynchronizingAjaxController.new
40
+ @java.setAjaxController(controller)
41
+ end
42
+
43
+ if connection
44
+ connection = Rjb::bind(HtmlUnit::Connection.new(connection), 'com.gargoylesoftware.htmlunit.WebConnection')
45
+ @java.setWebConnection(connection)
46
+ end
47
+ end
48
+
49
+ def request(*args)
50
+ @java.getPage(*args) # TODO use WebRequestSettings
51
+ end
52
+
53
+ def wait_for_javascript(timeout)
54
+ waitForBackgroundJavaScript(timeout)
55
+ yield if block_given?
56
+ end
57
+
58
+ # FIXME setLevel throws "Fail: unknown method name `setLevel'". weird.
59
+ def log_level=(level)
60
+ [ 'com.gargoylesoftware.htmlunit',
61
+ 'com.gargoylesoftware.htmlunit.html',
62
+ 'com.gargoylesoftware.htmlunit.javascript',
63
+ 'org.apache.commons.httpclient'
64
+ ].each { |classifier|
65
+ Java.logger(classifier).setLevel(Java.log_level(level)) }
66
+ end
67
+
68
+ def method_missing(method, *args)
69
+ @java.send(method, *args)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,79 @@
1
+ # For internal use in Browser::HtmlUnit to hook into HtmlUnit's request process.
2
+ # Does not support the Rack interface because HtmlUnit doesn't. Not currently
3
+ # used by Steam, but potentially useful to get HtmlUnit and the app running in
4
+ # a single stack.
5
+ #
6
+ # Can be set to a com.gargoylesoftware.htmlunit.WebClient like this:
7
+ #
8
+ # mock = Steam::Connection::Mock.new
9
+ # mock.mock :get, 'http://localhost:3000/', 'body!'
10
+ # connection = Steam::Browser::HtmlUnit::Connection.new(mock)
11
+ #
12
+ # client = java::WebClient.new(Java::Com::Gargoylesoftware::Htmlunit::BrowserVersion.FIREFOX_3)
13
+ # client.setWebConnection(Rjb::bind(connection, 'com.gargoylesoftware.htmlunit.WebConnection'))
14
+
15
+ require 'thread'
16
+ require 'rack/utils'
17
+
18
+ module Steam
19
+ module Browser
20
+ class HtmlUnit
21
+ class Connection
22
+ @@lock = Mutex.new
23
+
24
+ Java.import 'com.gargoylesoftware.htmlunit.Version'
25
+
26
+ include Java::Com::Gargoylesoftware::Htmlunit
27
+
28
+ Java.import('com.gargoylesoftware.htmlunit.MockWebConnection')
29
+ classifier = Version.getProductVersion == '2.6' ?
30
+ 'org.apache.commons.httpclient.NameValuePair' : # HtmlUnit 2.6
31
+ 'com.gargoylesoftware.htmlunit.util.NameValuePair' # HtmlUnit 2.7
32
+ NameValuePair = Java.import(classifier, :NameValuePair)
33
+
34
+ attr_reader :connection, :java
35
+
36
+ def initialize(connection)
37
+ @connection = connection
38
+ @java = MockWebConnection.new
39
+ end
40
+
41
+ def getResponse(request)
42
+ # FIXME preserve original scheme, host + port
43
+ method = request.getHttpMethod.toString.dup
44
+ url = request.getUrl.toString.dup
45
+
46
+ body = request.getRequestBody
47
+ body = body.toString.dup if body
48
+
49
+ params = request.getRequestParameters
50
+ params = Hash[*params.toArray.map { |e| [e.name, e.value] }.flatten]
51
+
52
+ input = body ? body : Rack::Utils.build_nested_query(params)
53
+ env = Request.env_for(url, :method => method, :input => input)
54
+
55
+ status, headers, response = connection.call(env)
56
+ response.body.close if response.body.respond_to?(:close)
57
+
58
+ set_response(request.getUrl, response)
59
+ java.getResponse(request)
60
+ rescue Exception => e
61
+ puts "#{e.class.name}: #{e.message}"
62
+ e.backtrace.each { |line| puts line }
63
+ end
64
+
65
+ def set_response(url, response)
66
+ body = response.body.join
67
+ status = response.status
68
+ message = Rack::Utils::HTTP_STATUS_CODES[status.to_i]
69
+ charset = Steam.config[:charset]
70
+ headers = response.header.map { |key, value| NameValuePair.new(key, value) }
71
+ headers = Java::Util::Arrays.asList(headers)
72
+ content_type = response.content_type
73
+
74
+ java.setResponse(url, body, status, message, content_type, charset, headers)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,45 @@
1
+ require 'drb'
2
+
3
+ module Steam
4
+ module Browser
5
+ class HtmlUnit
6
+ class Drb
7
+ attr_reader :process, :connection, :options
8
+
9
+ def initialize(connection, options = {})
10
+ @connection, @options = connection, options
11
+ @process = Steam::Process.new
12
+
13
+ process.kill if options[:restart] && process.running?
14
+ options[:daemon] ? daemonize : start unless process.running?
15
+ end
16
+
17
+ def object
18
+ @object ||= DRbObject.new(nil, Steam.config[:drb_uri])
19
+ end
20
+
21
+ def daemonize
22
+ options[:keep_alive] = true unless options.key?(:keep_alive)
23
+ process.fork(options) { start }
24
+ sleep(1) # FIXME
25
+ end
26
+
27
+ def start
28
+ uri = Steam.config[:drb_uri]
29
+ DRb.start_service(uri, HtmlUnit.new(connection, options))
30
+ puts "HtmlUnit ready and listening at #{uri} [#{::Process.pid}]"
31
+ DRb.thread.join
32
+ end
33
+
34
+ def restart
35
+ process.kill if process.running?
36
+ daemonize
37
+ end
38
+
39
+ def method_missing(method, *args, &block)
40
+ object.send(method, *args, &block)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,57 @@
1
+ # module Steam
2
+ # module HtmlUnit
3
+ # module Matchers
4
+ # class HasContent #:nodoc:
5
+ # def initialize(content)
6
+ # @content = content
7
+ # end
8
+ #
9
+ # def matches?(target)
10
+ # @target = target
11
+ # !!@target.locate_element(@content)
12
+ # end
13
+ #
14
+ # def failure_message
15
+ # "expected the following element's content to #{content_message}:\n#{squeeze_space(@target.page.body)}"
16
+ # end
17
+ #
18
+ # def negative_failure_message
19
+ # "expected the following element's content to not #{content_message}:\n#{squeeze_space(@target.page.body)}"
20
+ # end
21
+ #
22
+ # def squeeze_space(inner_text)
23
+ # (inner_text || '').gsub(/^\s*$/, "").squeeze("\n")
24
+ # end
25
+ #
26
+ # def content_message
27
+ # case @content
28
+ # when String
29
+ # "include \"#{@content}\""
30
+ # when Regexp
31
+ # "match #{@content.inspect}"
32
+ # end
33
+ # end
34
+ # end
35
+ #
36
+ # # Matches the contents of an HTML document with
37
+ # # whatever string is supplied
38
+ # def contain(content)
39
+ # HasContent.new(content)
40
+ # end
41
+ #
42
+ # # Asserts that the body of the response contain
43
+ # # the supplied string or regexp
44
+ # def assert_contain(content)
45
+ # hc = HasContent.new(content)
46
+ # assert hc.matches?(response.body), hc.failure_message
47
+ # end
48
+ #
49
+ # # Asserts that the body of the response
50
+ # # does not contain the supplied string or regepx
51
+ # def assert_not_contain(content)
52
+ # hc = HasContent.new(content)
53
+ # assert !hc.matches?(response.body), hc.negative_failure_message
54
+ # end
55
+ # end
56
+ # end
57
+ # end
@@ -0,0 +1,51 @@
1
+ # Wraps a dom.gargoylesoftware.htmlunit.html.HtmlPage (which is returned by
2
+ # most browser actions such as click, drag, drop etc.) with a nicer interface.
3
+
4
+ module Steam
5
+ module Browser
6
+ class HtmlUnit
7
+ class Page
8
+ attr_reader
9
+
10
+ def initialize(page)
11
+ @page = page
12
+ end
13
+
14
+ def url
15
+ @page.getWebResponse.getRequestSettings.getUrl.toString
16
+ end
17
+
18
+ def status
19
+ @page.getWebResponse.getStatusCode
20
+ end
21
+
22
+ def headers
23
+ @page.getWebResponse.getResponseHeaders.toArray.inject({}) do |headers, pair|
24
+ headers[pair.name] = pair.value
25
+ headers
26
+ end
27
+ end
28
+
29
+ def body
30
+ @page.asXml
31
+ end
32
+
33
+ def execute(javascript)
34
+ @page.executeJavaScript(javascript)
35
+ end
36
+
37
+ def sourceCode
38
+ @page.asXml
39
+ end
40
+
41
+ def to_a
42
+ [body, status, headers]
43
+ end
44
+
45
+ def method_missing(method, *args)
46
+ return @page.send(method, *args)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,116 @@
1
+ # Mimicks the com.gargoylesoftware.htmlunit.WebRequestSettings api so it can be
2
+ # returned by an object that mimicks com.gargoylesoftware.htmlunit.WebConnection
3
+ # Not currently used in Steam, but useful for experimental stuff.
4
+
5
+ module Steam
6
+ module Browser
7
+ class HtmlUnit
8
+ class WebResponse
9
+ Java.import 'java.net.Url'
10
+ Java.import 'java.io.ByteArrayInputStream'
11
+ Java.import 'com.gargoylesoftware.htmlunit.WebRequestSettings'
12
+
13
+ include Java::Com::Gargoylesoftware::Htmlunit
14
+
15
+ classifier = Version.getProductVersion == '2.6' ?
16
+ 'org.apache.commons.httpclient.NameValuePair' : # HtmlUnit 2.6
17
+ 'com.gargoylesoftware.htmlunit.util.NameValuePair' # HtmlUnit 2.7
18
+ NameValuePair = Java.import(classifier, :NameValuePair)
19
+
20
+ attr_reader :request, :response
21
+
22
+ def initialize(request, response)
23
+ @request = request
24
+ @response = response
25
+ rescue Exception => e
26
+ puts e.message
27
+ e.backtrace.each { |line| puts line }
28
+ end
29
+
30
+ def getRequestSettings
31
+ @request_settings ||= WebRequestSettings.new(Java::Net::URL.new(request.url))
32
+ rescue Exception => e
33
+ puts e.message
34
+ e.backtrace.each { |line| puts line }
35
+ end
36
+
37
+ def getResponseHeaders
38
+ @headers ||= begin
39
+ headers = response.header.map { |key, value| NameValuePair.new(key, value) }
40
+ Java::Util::Arrays.asList(headers)
41
+ end
42
+ rescue Exception => e
43
+ puts e.message
44
+ e.backtrace.each { |line| puts line }
45
+ end
46
+
47
+ def getResponseHeaderValue(name)
48
+ response.header[name]
49
+ rescue Exception => e
50
+ puts e.message
51
+ e.backtrace.each { |line| puts line }
52
+ end
53
+
54
+ def getStatusCode
55
+ @status ||= response.status
56
+ rescue Exception => e
57
+ puts e.message
58
+ e.backtrace.each { |line| puts line }
59
+ end
60
+
61
+ def getStatusMessage
62
+ @message ||= Rack::Utils::HTTP_STATUS_CODES[getStatusCode.to_i]
63
+ rescue Exception => e
64
+ puts e.message
65
+ e.backtrace.each { |line| puts line }
66
+ end
67
+
68
+ def getContentType
69
+ @content_type ||= begin
70
+ content_type = response.content_type.split(';').first
71
+ content_type = 'application/x-javascript' if content_type == 'application/javascript'
72
+ content_type
73
+ end
74
+ rescue Exception => e
75
+ puts e.message
76
+ e.backtrace.each { |line| puts line }
77
+ end
78
+
79
+ def getContentCharset
80
+ @content_charset ||= Steam.config[:charset]
81
+ rescue Exception => e
82
+ puts e.message
83
+ e.backtrace.each { |line| puts line }
84
+ end
85
+
86
+ def getContentCharsetOrNull
87
+ @content_charset_or_null ||= Steam.config[:charset]
88
+ rescue Exception => e
89
+ puts e.message
90
+ e.backtrace.each { |line| puts line }
91
+ end
92
+
93
+ def getContentAsString
94
+ @content_as_string ||= response.body.join
95
+ rescue Exception => e
96
+ puts e.message
97
+ e.backtrace.each { |line| puts line }
98
+ end
99
+
100
+ def getContentAsStream
101
+ @content_as_stream ||= begin
102
+ bytes = getContentAsString.unpack('C*')
103
+ Java::Io::ByteArrayInputStream.new_with_sig('[B', bytes)
104
+ end
105
+ rescue Exception => e
106
+ puts e.message
107
+ e.backtrace.each { |line| puts line }
108
+ end
109
+
110
+ def method_missing(method, *args)
111
+ puts "Method missing in WebResponse: #{method} #{args.map{ |a| a.inspect }.join(', ')}"
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,9 @@
1
+ module Steam
2
+ module Connection
3
+ autoload :Mock, 'steam/connection/mock'
4
+ autoload :NetHttp, 'steam/connection/net_http'
5
+ autoload :OpenUri, 'steam/connection/open_uri'
6
+ autoload :Rails, 'steam/connection/rails'
7
+ autoload :Static, 'steam/connection/static'
8
+ end
9
+ end
@@ -0,0 +1,57 @@
1
+ require 'cgi'
2
+
3
+ module Steam
4
+ module Connection
5
+ class Mock
6
+ class << self
7
+ def default_content_types
8
+ {
9
+ :html => 'text/html',
10
+ :xml => 'application/xml',
11
+ :javascript => 'application/javascript',
12
+ :stylesheet => 'text/css'
13
+ }
14
+ end
15
+ end
16
+
17
+ def initialize
18
+ @responses = {}
19
+ end
20
+
21
+ def call(env)
22
+ request = Rack::Request.new(env)
23
+ response = response(request.request_method, request.url)
24
+ response = response.call if response.is_a?(Proc)
25
+ response.to_a
26
+ end
27
+
28
+ def response(method, url)
29
+ responses(method)[CGI::unescape(url)] || Rack::Response.new('', 404)
30
+ end
31
+
32
+ def mock(*args)
33
+ headers = args.last.is_a?(Hash) ? args.pop : {}
34
+ response, url, method = *args.reverse
35
+
36
+ responses(method || :get)[url] = case response
37
+ when Array
38
+ Rack::Response.new(*response)
39
+ when String
40
+ Rack::Response.new(response, 200, headers)
41
+ else
42
+ response
43
+ end
44
+ end
45
+
46
+ protected
47
+
48
+ def responses(method)
49
+ @responses[method.to_s.upcase] ||= {}
50
+ end
51
+
52
+ def content_type(type)
53
+ self.class.default_content_types[type]
54
+ end
55
+ end
56
+ end
57
+ end