steam 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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