js_test_core 0.1.1 → 0.2.0

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