js_test_core 0.1.1 → 0.2.0

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 (48) hide show
  1. data/CHANGES +11 -0
  2. data/Rakefile +1 -1
  3. data/lib/js_test_core.rb +10 -2
  4. data/lib/js_test_core/client.rb +85 -18
  5. data/lib/js_test_core/extensions.rb +3 -0
  6. data/lib/js_test_core/extensions/time.rb +6 -0
  7. data/lib/js_test_core/resources.rb +4 -4
  8. data/lib/js_test_core/resources/dir.rb +22 -7
  9. data/lib/js_test_core/resources/file.rb +24 -16
  10. data/lib/js_test_core/resources/file_not_found.rb +11 -0
  11. data/lib/js_test_core/resources/runner.rb +107 -0
  12. data/lib/js_test_core/resources/session.rb +44 -0
  13. data/lib/js_test_core/resources/session_finish.rb +17 -0
  14. data/lib/js_test_core/resources/specs/spec_dir.rb +10 -14
  15. data/lib/js_test_core/resources/specs/spec_file.rb +1 -1
  16. data/lib/js_test_core/resources/web_root.rb +51 -39
  17. data/lib/js_test_core/selenium_server_configuration.rb +48 -0
  18. data/lib/js_test_core/server.rb +3 -64
  19. data/lib/js_test_core/thin/js_test_core_connection.rb +4 -38
  20. data/spec/unit/js_test_core/client_spec.rb +167 -0
  21. data/spec/unit/{js_spec → js_test_core}/rails_server_spec.rb +0 -0
  22. data/spec/unit/js_test_core/resources/dir_spec.rb +52 -0
  23. data/spec/unit/js_test_core/resources/file_not_found_spec.rb +16 -0
  24. data/spec/unit/js_test_core/resources/file_spec.rb +90 -0
  25. data/spec/unit/js_test_core/resources/runners/runner_spec.rb +303 -0
  26. data/spec/unit/js_test_core/resources/session_finish_spec.rb +79 -0
  27. data/spec/unit/js_test_core/resources/session_spec.rb +82 -0
  28. data/spec/unit/js_test_core/resources/specs/spec_dir_spec.rb +105 -0
  29. data/spec/unit/{js_spec → js_test_core}/resources/specs/spec_file_spec.rb +5 -5
  30. data/spec/unit/js_test_core/resources/web_root_spec.rb +32 -0
  31. data/spec/unit/js_test_core/selenium_server_configuration_spec.rb +49 -0
  32. data/spec/unit/{js_spec → js_test_core}/server_spec.rb +18 -32
  33. data/spec/unit/thin/js_test_core_connection_spec.rb +0 -86
  34. data/spec/unit/unit_spec_helper.rb +58 -22
  35. metadata +39 -33
  36. data/lib/js_test_core/resources/runners.rb +0 -15
  37. data/lib/js_test_core/resources/runners/firefox_runner.rb +0 -73
  38. data/lib/js_test_core/resources/suite.rb +0 -24
  39. data/lib/js_test_core/resources/suite_finish.rb +0 -19
  40. data/spec/unit/js_spec/client_spec.rb +0 -137
  41. data/spec/unit/js_spec/resources/dir_spec.rb +0 -42
  42. data/spec/unit/js_spec/resources/file_spec.rb +0 -88
  43. data/spec/unit/js_spec/resources/runner_spec.rb +0 -24
  44. data/spec/unit/js_spec/resources/runners/firefox_runner_spec.rb +0 -161
  45. data/spec/unit/js_spec/resources/specs/spec_dir_spec.rb +0 -79
  46. data/spec/unit/js_spec/resources/suite_finish_spec.rb +0 -94
  47. data/spec/unit/js_spec/resources/suite_spec.rb +0 -44
  48. data/spec/unit/js_spec/resources/web_root_spec.rb +0 -67
data/CHANGES CHANGED
@@ -1,3 +1,14 @@
1
+ 0.2.0
2
+ - Renamed Suite to Session to follow Selenium conventions
3
+ - Renamed SuiteFinish to SessionFinish to follow Selenium conventions
4
+ - Added /session, which uses the session_id cookie to derive the current session.
5
+ - Added /session/finished to be used to simplify client/server interactions.
6
+ - Remove File caching because it doesn't cause a noticable performance benefit over a local network and because it sometimes doesn't write over stale files.
7
+ - Client accepts selenium_browser_start_command parameter, which allows the user to parameterize the selenium_browser_start_command
8
+ - Added support for running specs in Internet Explorer via POST /runners/iexplore
9
+ - Resource file download performance improvements via caching and chunked sending
10
+ - Fixed false positive bug when Client connection times out
11
+
1
12
  0.1.1
2
13
  - SuiteFinish#post immediately closes the connection
3
14
 
data/Rakefile CHANGED
@@ -26,7 +26,7 @@ def run_suite
26
26
  end
27
27
 
28
28
  PKG_NAME = "js_test_core"
29
- PKG_VERSION = "0.1.1"
29
+ PKG_VERSION = "0.2.0"
30
30
  PKG_FILES = FileList[
31
31
  '[A-Z]*',
32
32
  '*.rb',
@@ -1,7 +1,14 @@
1
1
  require "rubygems"
2
2
  gem "thin", ">=0.8.0"
3
3
 
4
- require "thin"
4
+ dir = File.dirname(__FILE__)
5
+ $LOAD_PATH.unshift File.expand_path("#{dir}/../vendor/thin-rest/lib")
6
+ require "thin_rest"
7
+
8
+ # This causes errors to be printed to STDOUT.
9
+ Thin::Logging.silent = false
10
+ Thin::Logging.debug = true
11
+
5
12
  require "fileutils"
6
13
  require "tmpdir"
7
14
  require "timeout"
@@ -10,13 +17,14 @@ require "net/http"
10
17
  require "selenium"
11
18
  require "optparse"
12
19
 
13
- dir = File.dirname(__FILE__)
20
+ require "#{dir}/js_test_core/extensions"
14
21
  require "#{dir}/js_test_core/thin"
15
22
  require "#{dir}/js_test_core/rack"
16
23
  require "#{dir}/js_test_core/resources"
17
24
  require "#{dir}/js_test_core/selenium"
18
25
 
19
26
  require "#{dir}/js_test_core/client"
27
+ require "#{dir}/js_test_core/selenium_server_configuration"
20
28
  require "#{dir}/js_test_core/server"
21
29
  require "#{dir}/js_test_core/rails_server"
22
30
 
@@ -1,24 +1,17 @@
1
1
  module JsTestCore
2
2
  class Client
3
- class << self
4
- def run(params={})
5
- data = []
6
- data << "selenium_host=#{CGI.escape(params[:selenium_host] || 'localhost')}"
7
- data << "selenium_port=#{CGI.escape((params[:selenium_port] || 4444).to_s)}"
8
- data << "spec_url=#{CGI.escape(params[:spec_url])}" if params[:spec_url]
9
- response = Net::HTTP.start(DEFAULT_HOST, DEFAULT_PORT) do |http|
10
- http.post('/runners/firefox', data.join("&"))
11
- end
3
+ class ClientException < Exception
4
+ end
12
5
 
13
- body = response.body
14
- if body.empty?
15
- puts "SUCCESS"
16
- return true
17
- else
18
- puts "FAILURE"
19
- puts body
20
- return false
21
- end
6
+ class InvalidStatusResponse < ClientException
7
+ end
8
+
9
+ class SessionNotFound < ClientException
10
+ end
11
+
12
+ class << self
13
+ def run(parameters={})
14
+ new(parameters).run
22
15
  end
23
16
 
24
17
  def run_argv(argv)
@@ -28,6 +21,10 @@ module JsTestCore
28
21
  o.banner << "\nUsage: #{$0} [options] [-- untouched arguments]"
29
22
 
30
23
  o.on
24
+ o.on('-s', '--selenium_browser_start_command=selenium_browser_start_command', "The Selenium server command to start the browser. See http://selenium-rc.openqa.org/") do |selenium_browser_start_command|
25
+ params[:selenium_browser_start_command] = selenium_browser_start_command
26
+ end
27
+
31
28
  o.on('-h', '--selenium_host=SELENIUM_HOST', "The host name of the Selenium Server relative to where this file is executed") do |host|
32
29
  params[:selenium_host] = host
33
30
  end
@@ -46,5 +43,75 @@ module JsTestCore
46
43
  run params
47
44
  end
48
45
  end
46
+
47
+ attr_reader :parameters, :query_string, :http, :session_start_response, :last_poll_status, :last_poll_reason, :last_poll
48
+ def initialize(parameters)
49
+ @parameters = parameters
50
+ @query_string = SeleniumServerConfiguration.query_string_from(parameters)
51
+ end
52
+
53
+ def run
54
+ Net::HTTP.start(DEFAULT_HOST, DEFAULT_PORT) do |@http|
55
+ start_runner
56
+ wait_for_session_to_finish
57
+ end
58
+ report_result
59
+ end
60
+
61
+ def parts_from_query(query)
62
+ query.split('&').inject({}) do |acc, key_value_pair|
63
+ key, value = key_value_pair.split('=')
64
+ acc[key] = value
65
+ acc
66
+ end
67
+ end
68
+
69
+ protected
70
+ def start_runner
71
+ @session_start_response = http.post('/runners', query_string)
72
+ end
73
+
74
+ def wait_for_session_to_finish
75
+ while session_not_completed?
76
+ poll
77
+ sleep 0.25
78
+ end
79
+ end
80
+
81
+ def report_result
82
+ case last_poll_status
83
+ when Resources::Session::SUCCESSFUL_COMPLETION
84
+ STDOUT.puts "SUCCESS"
85
+ true
86
+ when Resources::Session::FAILURE_COMPLETION
87
+ STDOUT.puts "FAILURE"
88
+ STDOUT.puts last_poll_reason
89
+ false
90
+ else
91
+ raise InvalidStatusResponse, "Invalid Status: #{last_poll_status}"
92
+ end
93
+ end
94
+
95
+ def session_not_completed?
96
+ last_poll_status.nil? || last_poll_status == Resources::Session::RUNNING
97
+ end
98
+
99
+ def poll
100
+ @last_poll = http.get("/sessions/#{session_id}")
101
+ ensure_session_exists!
102
+ parts = parts_from_query(last_poll.body)
103
+ @last_poll_status = parts['status']
104
+ @last_poll_reason = parts['reason']
105
+ end
106
+
107
+ def ensure_session_exists!
108
+ if (400..499).include?(Integer(last_poll.code))
109
+ raise SessionNotFound, "Could not find session with id #{session_id}"
110
+ end
111
+ end
112
+
113
+ def session_id
114
+ @session_id ||= parts_from_query(session_start_response.body)['session_id']
115
+ end
49
116
  end
50
117
  end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/extensions/*.rb"].each do |file|
2
+ require file
3
+ end
@@ -0,0 +1,6 @@
1
+ require "time"
2
+ class Time
3
+ def rfc822
4
+ strftime("%a, %d %b %Y %k:%M:%S %Z")
5
+ end
6
+ end
@@ -1,10 +1,10 @@
1
1
  dir = File.dirname(__FILE__)
2
- require "#{dir}/resources/runners"
3
- require "#{dir}/resources/runners/firefox_runner"
2
+ require "#{dir}/resources/runner"
4
3
  require "#{dir}/resources/file"
5
4
  require "#{dir}/resources/dir"
5
+ require "#{dir}/resources/file_not_found"
6
6
  require "#{dir}/resources/specs/spec_file"
7
7
  require "#{dir}/resources/specs/spec_dir"
8
8
  require "#{dir}/resources/web_root"
9
- require "#{dir}/resources/suite"
10
- require "#{dir}/resources/suite_finish"
9
+ require "#{dir}/resources/session"
10
+ require "#{dir}/resources/session_finish"
@@ -1,25 +1,32 @@
1
1
  module JsTestCore
2
2
  module Resources
3
3
  class Dir < File
4
- def locate(name)
4
+ route ANY do |env, name|
5
5
  if file = file(name)
6
6
  file
7
7
  elsif subdir = subdir(name)
8
8
  subdir
9
9
  else
10
- raise "No file or directory found at #{relative_path}/#{name}."
10
+ FileNotFound.new(env.merge(:name => name))
11
11
  end
12
12
  end
13
13
 
14
- def get(request, response)
15
-
14
+ def get
15
+ connection.send_head
16
+ connection.send_body(::Dir.glob("#{absolute_path}/*").inject("") do |html, file|
17
+ file_basename = ::File.basename(file)
18
+ html << %Q|<a href="#{file_basename}">#{file_basename}</a>\n|
19
+ end)
16
20
  end
17
21
 
18
22
  def glob(pattern)
19
23
  expanded_pattern = absolute_path + pattern
20
24
  ::Dir.glob(expanded_pattern).map do |absolute_globbed_path|
21
25
  relative_globbed_path = absolute_globbed_path.gsub(absolute_path, relative_path)
22
- File.new(absolute_globbed_path, relative_globbed_path)
26
+ File.new(env.merge(
27
+ :absolute_path => absolute_globbed_path,
28
+ :relative_path => relative_globbed_path
29
+ ))
23
30
  end
24
31
  end
25
32
 
@@ -31,18 +38,26 @@ module JsTestCore
31
38
  end
32
39
 
33
40
  def file(name)
41
+ # N.B. Absolute_path and relative_path are methods. Do not shadow.
34
42
  absolute_file_path, relative_file_path = determine_child_paths(name)
35
43
  if ::File.exists?(absolute_file_path) && !::File.directory?(absolute_file_path)
36
- Resources::File.new(absolute_file_path, relative_file_path)
44
+ Resources::File.new(env.merge(
45
+ :absolute_path => absolute_file_path,
46
+ :relative_path => relative_file_path
47
+ ))
37
48
  else
38
49
  nil
39
50
  end
40
51
  end
41
52
 
42
53
  def subdir(name)
54
+ # N.B. Absolute_path and relative_path are methods. Do not shadow.
43
55
  absolute_dir_path, relative_dir_path = determine_child_paths(name)
44
56
  if ::File.directory?(absolute_dir_path)
45
- Resources::Dir.new(absolute_dir_path, relative_dir_path)
57
+ Resources::Dir.new(env.merge(
58
+ :absolute_path => absolute_dir_path,
59
+ :relative_path => relative_dir_path
60
+ ))
46
61
  else
47
62
  nil
48
63
  end
@@ -1,6 +1,6 @@
1
1
  module JsTestCore
2
2
  module Resources
3
- class File
3
+ class File < ThinRest::Resource
4
4
  MIME_TYPES = {
5
5
  '.js' => 'text/javascript',
6
6
  '.css' => 'text/css',
@@ -8,25 +8,33 @@ module JsTestCore
8
8
  '.jpg' => 'image/jpeg',
9
9
  '.jpeg' => 'image/jpeg',
10
10
  '.gif' => 'image/gif',
11
- }
11
+ }
12
12
 
13
- attr_reader :absolute_path, :relative_path
13
+ property :absolute_path, :relative_path
14
14
 
15
- def initialize(absolute_path, relative_path)
16
- @absolute_path = absolute_path
17
- @relative_path = relative_path
18
- end
19
-
20
- def get(request, response)
15
+ def get
21
16
  extension = ::File.extname(absolute_path)
22
- response.headers['Content-Type'] = MIME_TYPES[extension] || 'text/html'
23
- response.body = ::File.read(absolute_path)
24
- end
17
+ content_type = MIME_TYPES[extension] || 'text/html'
18
+
19
+ connection.terminate_after_sending do
20
+ connection.send_head(
21
+ 200,
22
+ 'Content-Type' => content_type,
23
+ 'Last-Modified' => ::File.mtime(absolute_path).rfc822,
24
+ 'Content-Length' => ::File.size(absolute_path)
25
+ )
26
+ ::File.open(absolute_path) do |file|
27
+ while !file.eof?
28
+ connection.send_data(file.read(1024))
29
+ end
30
+ end
31
+ end
25
32
 
26
- def ==(other)
27
- return false unless other.class == self.class
28
- absolute_path == other.absolute_path && relative_path == other.relative_path
33
+ def ==(other)
34
+ return false unless other.class == self.class
35
+ absolute_path == other.absolute_path && relative_path == other.relative_path
36
+ end
29
37
  end
30
38
  end
31
39
  end
32
- end
40
+ end
@@ -0,0 +1,11 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class FileNotFound < ThinRest::Resource
4
+ property :name
5
+ def get
6
+ connection.send_head(404)
7
+ connection.send_body("")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,107 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class Runner < ThinRest::Resource
4
+ class Collection < ThinRest::Resource
5
+ property :selenium_browser_start_command
6
+ route 'firefox' do |env, name|
7
+ self.selenium_browser_start_command = "*firefox"
8
+ self
9
+ end
10
+ route 'iexplore' do |env, name|
11
+ self.selenium_browser_start_command = "*iexplore"
12
+ self
13
+ end
14
+
15
+ def after_initialize
16
+ super
17
+ self.selenium_browser_start_command = rack_request['selenium_browser_start_command']
18
+ end
19
+
20
+ def post
21
+ spec_url = rack_request['spec_url'].to_s == "" ? full_spec_suite_url : rack_request['spec_url']
22
+ parsed_spec_url = URI.parse(spec_url)
23
+ selenium_host = rack_request['selenium_host'].to_s == "" ? 'localhost' : rack_request['selenium_host'].to_s
24
+ selenium_port = rack_request['selenium_port'].to_s == "" ? 4444 : Integer(rack_request['selenium_port'])
25
+ http_address = "#{parsed_spec_url.scheme}://#{parsed_spec_url.host}:#{parsed_spec_url.port}"
26
+ driver = Selenium::SeleniumDriver.new(
27
+ selenium_host,
28
+ selenium_port,
29
+ selenium_browser_start_command,
30
+ http_address
31
+ )
32
+ begin
33
+ driver.start
34
+ rescue Errno::ECONNREFUSED => e
35
+ raise Errno::ECONNREFUSED, "Cannot connect to Selenium Server at #{http_address}. To start the selenium server, run `selenium`."
36
+ end
37
+ runner = Runner.new(:driver => driver)
38
+ Runner.register(runner)
39
+ Thread.start do
40
+ driver.open("/")
41
+ driver.create_cookie("session_id=#{runner.session_id}")
42
+ driver.open(parsed_spec_url.path)
43
+ end
44
+ connection.send_head
45
+ connection.send_body("session_id=#{runner.session_id}")
46
+ end
47
+
48
+ protected
49
+ def full_spec_suite_url
50
+ "#{Server.root_url}/specs"
51
+ end
52
+ end
53
+
54
+ class << self
55
+ def find(id)
56
+ instances[id.to_s]
57
+ end
58
+
59
+ def finalize(session_id, text)
60
+ if runner = find(session_id)
61
+ runner.finalize(text)
62
+ end
63
+ end
64
+
65
+ def register(runner)
66
+ instances[runner.session_id] = runner
67
+ end
68
+
69
+ protected
70
+ def instances
71
+ @instances ||= {}
72
+ end
73
+ end
74
+
75
+ include FileUtils
76
+ property :driver
77
+ attr_reader :profile_dir, :session_run_result
78
+
79
+ def after_initialize
80
+ profile_base = "#{::Dir.tmpdir}/js_test_core/#{self.class.name}"
81
+ mkdir_p profile_base
82
+ @profile_dir = "#{profile_base}/#{Time.now.to_i}"
83
+ end
84
+
85
+ def finalize(session_run_result)
86
+ driver.stop
87
+ @session_run_result = session_run_result.to_s
88
+ end
89
+
90
+ def running?
91
+ driver.session_started?
92
+ end
93
+
94
+ def successful?
95
+ !running? && session_run_result.empty?
96
+ end
97
+
98
+ def failed?
99
+ !running? && !successful?
100
+ end
101
+
102
+ def session_id
103
+ driver.session_id
104
+ end
105
+ end
106
+ end
107
+ end