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
@@ -0,0 +1,44 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class Session < ThinRest::Resource
4
+ class Collection < ThinRest::Resource
5
+ route ANY do |env, id|
6
+ Session.new(env.merge(:id => id))
7
+ end
8
+ end
9
+
10
+ RUNNING = 'running'
11
+ SUCCESSFUL_COMPLETION = 'success'
12
+ FAILURE_COMPLETION = 'failure'
13
+
14
+ property :id
15
+
16
+ def get
17
+ runner = Runner.find(id)
18
+ if runner
19
+ connection.send_head
20
+ if runner.running?
21
+ connection.send_body("status=#{RUNNING}")
22
+ else
23
+ if runner.successful?
24
+ connection.send_body("status=#{SUCCESSFUL_COMPLETION}")
25
+ else
26
+ connection.send_body("status=#{FAILURE_COMPLETION}&reason=#{runner.session_run_result}")
27
+ end
28
+ end
29
+ else
30
+ connection.send_head(404)
31
+ connection.send_body("")
32
+ end
33
+ end
34
+
35
+ route 'finish' do |env, name|
36
+ SessionFinish.new(env.merge(:session => self))
37
+ end
38
+
39
+ def associated_with_a_runner?
40
+ id.to_s != ""
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,17 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class SessionFinish < ThinRest::Resource
4
+ property :session
5
+
6
+ def post
7
+ if session.associated_with_a_runner?
8
+ Runner.finalize(session.id, rack_request['text'])
9
+ else
10
+ STDOUT.puts rack_request['text']
11
+ end
12
+ connection.send_head
13
+ connection.send_body("")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -2,7 +2,7 @@ module JsTestCore
2
2
  module Resources
3
3
  module Specs
4
4
  class SpecDirSuperclass < ::JsTestCore::Resources::Dir
5
- def get(request, response)
5
+ def get
6
6
  raise NotImplementedError, "#{self.class}#get needs to be implemented"
7
7
  end
8
8
  end
@@ -12,13 +12,9 @@ module JsTestCore
12
12
  glob("/**/*_spec.js")
13
13
  end
14
14
 
15
- def locate(name)
16
- if file = file(name)
17
- file
18
- elsif subdir = subdir(name)
19
- subdir
20
- elsif spec_file = spec_file(name)
21
- spec_file
15
+ route ANY do |env, name|
16
+ if result = (file(name) || subdir(name) || spec_file(name))
17
+ result
22
18
  else
23
19
  base_path = "#{relative_path}/#{name}"
24
20
  raise "No file or directory found at #{base_path} or spec found at #{base_path}.js."
@@ -28,18 +24,18 @@ module JsTestCore
28
24
  protected
29
25
 
30
26
  def subdir(name)
31
- absolute_dir_path, relative_dir_path = determine_child_paths(name)
32
- if ::File.directory?(absolute_dir_path)
33
- SpecDir.new(absolute_dir_path, relative_dir_path)
27
+ absolute_path, relative_path = determine_child_paths(name)
28
+ if ::File.directory?(absolute_path)
29
+ SpecDir.new(env.merge(:absolute_path => absolute_path, :relative_path => relative_path))
34
30
  else
35
31
  nil
36
32
  end
37
33
  end
38
34
 
39
35
  def spec_file(name)
40
- absolute_file_path, relative_file_path = determine_child_paths("#{name}.js")
41
- if ::File.exists?(absolute_file_path) && !::File.directory?(absolute_file_path)
42
- SpecFile.new(absolute_file_path, relative_file_path)
36
+ absolute_path, relative_path = determine_child_paths("#{name}.js")
37
+ if ::File.exists?(absolute_path) && !::File.directory?(absolute_path)
38
+ SpecFile.new(env.merge(:absolute_path => absolute_path, :relative_path => relative_path))
43
39
  else
44
40
  nil
45
41
  end
@@ -2,7 +2,7 @@ module JsTestCore
2
2
  module Resources
3
3
  module Specs
4
4
  class SpecFileSuperclass < ::JsTestCore::Resources::File
5
- def get(request, response)
5
+ def get
6
6
  raise NotImplementedError, "#{self.class}#get needs to be implemented"
7
7
  end
8
8
  end
@@ -1,20 +1,52 @@
1
1
  module JsTestCore
2
2
  module Resources
3
- class WebRoot
4
- LOCATIONS = [
5
- ['core', lambda do
6
- Resources::Dir.new(JsTestCore::Server.core_path, "/core")
7
- end],
8
- ['implementations', lambda do
9
- Resources::Dir.new(JsTestCore::Server.implementation_root_path, "/implementations")
10
- end],
11
- ['suites', lambda do
12
- Resources::Suite
13
- end],
14
- ['runners', lambda do
15
- Resources::Runners.new
16
- end]
17
- ]
3
+ class WebRoot < ThinRest::Resource
4
+ route "" do |env, name|
5
+ self
6
+ end
7
+ route "core" do |env, name|
8
+ Resources::Dir.new(env.merge(
9
+ :absolute_path => JsTestCore::Server.core_path,
10
+ :relative_path => "/core"
11
+ ))
12
+ end
13
+ route "implementations" do |env, name|
14
+ Resources::Dir.new(env.merge(
15
+ :absolute_path => JsTestCore::Server.implementation_root_path,
16
+ :relative_path => "/implementations"
17
+ ))
18
+ end
19
+ route "sessions", "JsTestCore::Resources::Session::Collection"
20
+ route "session" do |env, name|
21
+ Session.new(env.merge(:id => rack_request.cookies["session_id"]))
22
+ end
23
+ route "runners", "JsTestCore::Resources::Runner::Collection"
24
+ route "specs" do |env, name|
25
+ if self.class.dispatch_strategy == :specs
26
+ Resources::Specs::SpecDir.new(env.merge(
27
+ :absolute_path => JsTestCore::Server.spec_root_path,
28
+ :relative_path => "/specs"
29
+ ))
30
+ else
31
+ Resources::FileNotFound.new(env.merge(:name => name))
32
+ end
33
+ end
34
+ route ANY do |env, name|
35
+ potential_file_in_public_path = "#{public_path}/#{name}"
36
+ if ::File.directory?(potential_file_in_public_path)
37
+ Resources::Dir.new(env.merge(
38
+ :absolute_path => potential_file_in_public_path,
39
+ :relative_path => "/#{name}"
40
+ ))
41
+ elsif ::File.exists?(potential_file_in_public_path)
42
+ Resources::File.new(env.merge(
43
+ :absolute_path => potential_file_in_public_path,
44
+ :relative_path => "/#{name}"
45
+ ))
46
+ else
47
+ Resources::FileNotFound.new(env.merge(:name => name))
48
+ end
49
+ end
18
50
 
19
51
  class << self
20
52
  attr_accessor :dispatch_strategy
@@ -23,31 +55,11 @@ module JsTestCore
23
55
  end
24
56
  end
25
57
 
26
- attr_reader :public_path
27
- def initialize(public_path)
28
- @public_path = ::File.expand_path(public_path)
29
- end
30
-
31
- def locate(name)
32
- if self.class.dispatch_strategy == :specs && name == 'specs'
33
- return JsTestCore::Resources::Specs::SpecDir.new(JsTestCore::Server.spec_root_path, "/specs")
34
- end
58
+ property :public_path
35
59
 
36
- location, initializer = LOCATIONS.find do |location|
37
- location.first == name
38
- end
39
- if initializer
40
- initializer.call
41
- else
42
- potential_file_in_public_path = "#{public_path}/#{name}"
43
- if ::File.directory?(potential_file_in_public_path)
44
- Resources::Dir.new(potential_file_in_public_path, "/#{name}")
45
- elsif ::File.exists?(potential_file_in_public_path)
46
- Resources::File.new(potential_file_in_public_path, "/#{name}")
47
- else
48
- raise "Invalid path: #{name}"
49
- end
50
- end
60
+ def get
61
+ connection.send_head(200, :Location => "/#{self.class.dispatch_strategy}")
62
+ connection.send_body("<html><head></head><body>Welcome to the Js Test Server. Click the following link to run you <a href=/specs>spec suite</a>.</body></html>")
51
63
  end
52
64
  end
53
65
  end
@@ -0,0 +1,48 @@
1
+ module JsTestCore
2
+ class SeleniumServerConfiguration
3
+ attr_reader :parameters
4
+
5
+ class << self
6
+ def query_string_from(*args, &block)
7
+ new(*args, &block).query_string
8
+ end
9
+ end
10
+
11
+ def initialize(parameters={})
12
+ @parameters = parameters
13
+ end
14
+
15
+
16
+ def query_string
17
+ parts = [selenium_browser_start_command, selenium_host, selenium_port]
18
+ parts << spec_url if url
19
+ parts.join('&')
20
+ end
21
+
22
+ private
23
+
24
+ def selenium_browser_start_command
25
+ "selenium_browser_start_command=#{parameter_or_default_for(:selenium_browser_start_command, '*firefox')}"
26
+ end
27
+
28
+ def selenium_host
29
+ "selenium_host=#{parameter_or_default_for(:selenium_host, 'localhost')}"
30
+ end
31
+
32
+ def selenium_port
33
+ "selenium_port=#{parameter_or_default_for(:selenium_port, 4444)}"
34
+ end
35
+
36
+ def spec_url
37
+ "spec_url=#{url}"
38
+ end
39
+
40
+ def url
41
+ parameters[:spec_url]
42
+ end
43
+
44
+ def parameter_or_default_for(parameter_name, default = nil)
45
+ CGI.escape((parameters[parameter_name] || default).to_s)
46
+ end
47
+ end
48
+ end
@@ -14,9 +14,6 @@ module JsTestCore
14
14
  def implementation_root_path; instance.implementation_root_path; end
15
15
  def public_path; instance.public_path; end
16
16
  def core_path; instance.core_path; end
17
- def test_dir_class; instance.test_dir_class; end
18
- def test_file_class; instance.test_file_class; end
19
- def connection; instance.connection; end
20
17
  def request; instance.request; end
21
18
  def response; instance.response; end
22
19
  def root_url; instance.root_url; end
@@ -34,37 +31,14 @@ module JsTestCore
34
31
  end
35
32
 
36
33
  def run(options)
37
- server = ::Thin::Server.new(options[:Host], options[:Port], self)
34
+ server = ::Thin::Server.new(options[:Host], options[:Port]) do
35
+ use Rack::CommonLogger
36
+ end
38
37
  server.backend = ::Thin::Backends::JsTestCoreServer.new(options[:Host], options[:Port])
39
38
  server.backend.server = server
40
39
  server.start!
41
40
  end
42
41
 
43
- def call(env)
44
- self.connection = env['js_test_core.connection']
45
- self.request = Rack::Request.new(env)
46
- self.response = Rack::Response.new
47
- method = request.request_method.downcase.to_sym
48
- get_resource(request).send(method, request, response)
49
- response.finish
50
- ensure
51
- self.connection = nil
52
- self.request = nil
53
- self.response = nil
54
- end
55
-
56
- def connection
57
- Thread.current[:connection]
58
- end
59
-
60
- def request
61
- Thread.current[:request]
62
- end
63
-
64
- def response
65
- Thread.current[:response]
66
- end
67
-
68
42
  def root_url
69
43
  "http://#{host}:#{port}"
70
44
  end
@@ -72,40 +46,5 @@ module JsTestCore
72
46
  def core_path
73
47
  JsTestCore.core_path
74
48
  end
75
-
76
- def test_dir_class
77
- JsTestCore.adapter.test_dir_class
78
- end
79
-
80
- def test_file_class
81
- JsTestCore.adapter.test_file_class
82
- end
83
-
84
- protected
85
- def connection=(connection)
86
- Thread.current[:connection] = connection
87
- end
88
-
89
- def request=(request)
90
- Thread.current[:request] = request
91
- end
92
-
93
- def response=(response)
94
- Thread.current[:response] = response
95
- end
96
-
97
- def path_parts(req)
98
- request.path_info.split('/').reject { |part| part == "" }
99
- end
100
-
101
- def get_resource(request)
102
- path_parts(request).inject(Resources::WebRoot.new(public_path)) do |resource, child_resource_name|
103
- resource.locate(child_resource_name)
104
- end
105
- rescue Exception => e
106
- detailed_exception = Exception.new("Error handling path #{request.path_info}\n#{e.message}")
107
- detailed_exception.set_backtrace(e.backtrace)
108
- raise detailed_exception
109
- end
110
49
  end
111
50
  end
@@ -1,42 +1,8 @@
1
1
  module Thin
2
- class JsTestCoreConnection < Connection
3
- def process
4
- # Add client info to the request env
5
- @request.remote_address = remote_address
6
-
7
- env = @request.env
8
- env['js_test_core.connection'] = self
9
- @response.status, @response.headers, @response.body = @app.call(env)
10
- send_data @response.head
11
- if !@response.body.empty? || @response.headers.to_s.include?("Content-Length: 0")
12
- send_body @response.body
13
- end
14
- rescue Exception => e
15
- handle_error e
16
- end
17
-
18
- def send_body(rack_response)
19
- rack_response.each do |chunk|
20
- send_data chunk
21
- end
22
- # If no more request on that same connection, we close it.
23
- close_connection_after_writing unless persistent?
24
- rescue Exception => e
25
- handle_error e
26
- ensure
27
- @request.close rescue nil
28
- @response.close rescue nil
29
-
30
- # Prepare the connection for another request if the client
31
- # supports HTTP pipelining (persistent connection).
32
- post_init if persistent?
33
- end
34
-
35
- def handle_error(error)
36
- log "!! Unexpected error while processing request: #{error.message}"
37
- log error.backtrace
38
- log_error
39
- close_connection rescue nil
2
+ class JsTestCoreConnection < ThinRest::Connection
3
+ protected
4
+ def root_resource
5
+ ::JsTestCore::Resources::WebRoot.new(:connection => self, :public_path => ::JsTestCore::Server.public_path)
40
6
  end
41
7
  end
42
8
  end
@@ -0,0 +1,167 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../unit_spec_helper")
2
+
3
+ module JsTestCore
4
+ describe Client do
5
+ describe '.run' do
6
+ attr_reader :stdout, :request
7
+ before do
8
+ @stdout = StringIO.new
9
+ Client.const_set(:STDOUT, stdout)
10
+ @request = "http request"
11
+ mock(Net::HTTP).start(DEFAULT_HOST, DEFAULT_PORT).yields(request)
12
+ stub.instance_of(Client).sleep
13
+ end
14
+
15
+ after do
16
+ Client.__send__(:remove_const, :STDOUT)
17
+ end
18
+
19
+ it "tells the server to start a session run in Firefox and polls the status of the session until the session is complete" do
20
+ mock_post_to_runner("*firefox")
21
+ mock_polling_returns([running_status, running_status, success_status])
22
+ Client.run
23
+ end
24
+
25
+ context "when the Session run ends in 'success'" do
26
+ before do
27
+ mock_post_to_runner("*firefox")
28
+ mock_polling_returns([running_status, running_status, success_status])
29
+ end
30
+
31
+ it "reports success" do
32
+ Client.run
33
+ stdout.string.strip.should == "SUCCESS"
34
+ end
35
+
36
+ it "returns true" do
37
+ Client.run.should be_true
38
+ end
39
+ end
40
+
41
+ context "when the Session run ends in 'failure'" do
42
+ attr_reader :failure_reason
43
+ before do
44
+ mock_post_to_runner("*firefox")
45
+ @failure_reason = "I have a failed test"
46
+ mock_polling_returns([running_status, running_status, failure_status(failure_reason)])
47
+ end
48
+
49
+ it "reports failure and reason" do
50
+ Client.run
51
+ stdout.string.strip.should include("FAILURE")
52
+ stdout.string.strip.should include(failure_reason)
53
+ end
54
+
55
+ it "returns false" do
56
+ Client.run.should be_false
57
+ end
58
+
59
+ it "reports the reason for failure"
60
+ end
61
+
62
+ context "when the Session is not found" do
63
+ it "raises a SessionNotFound error" do
64
+ mock_post_to_runner("*firefox")
65
+ mock(request).get("/sessions/my_session_id") do
66
+ stub(session_response = Object.new).code {"404"}
67
+ session_response
68
+ end
69
+ lambda {Client.run}.should raise_error(Client::SessionNotFound)
70
+ end
71
+ end
72
+
73
+ context "when the Session run ends in with invalid status" do
74
+ it "raises an InvalidStatusResponse" do
75
+ mock_post_to_runner("*firefox")
76
+ mock_polling_returns([running_status, running_status, "status=this is an unexpected status result"])
77
+ lambda {Client.run}.should raise_error(Client::InvalidStatusResponse)
78
+ end
79
+ end
80
+
81
+ def mock_post_to_runner(selenium_browser_start_command)
82
+ mock(start_session_response = Object.new).body {"session_id=my_session_id"}
83
+ mock(request).post("/runners", "selenium_browser_start_command=#{CGI.escape(selenium_browser_start_command)}&selenium_host=localhost&selenium_port=4444") do
84
+ start_session_response
85
+ end
86
+ end
87
+
88
+ def mock_polling_returns(session_statuses=[])
89
+ mock(request).get("/sessions/my_session_id") do
90
+ stub(session_response = Object.new).body {session_statuses.shift}
91
+ stub(session_response).code {"200"}
92
+ session_response
93
+ end.times(session_statuses.length)
94
+ end
95
+
96
+ def running_status
97
+ "status=#{Resources::Session::RUNNING}"
98
+ end
99
+
100
+ def success_status
101
+ "status=#{Resources::Session::SUCCESSFUL_COMPLETION}"
102
+ end
103
+
104
+ def failure_status(reason)
105
+ "status=#{Resources::Session::FAILURE_COMPLETION}&reason=#{reason}"
106
+ end
107
+ end
108
+
109
+ describe ".run_argv" do
110
+ attr_reader :request, :response
111
+ before do
112
+ stub(Client).puts
113
+ end
114
+
115
+ describe "when passed in Hash contains :selenium_browser_start_command" do
116
+ it "passes the spec_url as a post parameter" do
117
+ selenium_browser_start_command = '*iexplore'
118
+ mock(Client).run(:selenium_browser_start_command => selenium_browser_start_command)
119
+ client = Client.run_argv(['--selenium_browser_start_command', selenium_browser_start_command])
120
+ end
121
+ end
122
+
123
+ describe "when passed in Hash contains :spec_url" do
124
+ it "passes the spec_url as a post parameter" do
125
+ spec_url = 'http://foobar.com/foo'
126
+ mock(Client).run(:spec_url => spec_url)
127
+ client = Client.run_argv(['--spec_url', spec_url])
128
+ end
129
+ end
130
+
131
+ describe "when passed in Hash contains :selenium_host" do
132
+ it "passes the selenium_host as a post parameter" do
133
+ selenium_host = 'test-runner'
134
+ mock(Client).run(:selenium_host => selenium_host)
135
+ client = Client.run_argv(['--selenium_host', selenium_host])
136
+ end
137
+ end
138
+
139
+ describe "when passed in Hash contains :selenium_port" do
140
+ it "passes the selenium_port as a post parameter" do
141
+ selenium_port = "5000"
142
+ mock(Client).run(:selenium_port => selenium_port)
143
+ client = Client.run_argv(['--selenium_port', selenium_port])
144
+ end
145
+ end
146
+ end
147
+
148
+ describe '#parts_from_query' do
149
+ attr_reader :client
150
+ before do
151
+ @client = Client.new(params_does_not_matter = {})
152
+ end
153
+
154
+ it "parses empty query into an empty hash" do
155
+ client.parts_from_query("").should == {}
156
+ end
157
+
158
+ it "parses a single key value pair into a single-element hash" do
159
+ client.parts_from_query("foo=bar").should == {'foo' => 'bar'}
160
+ end
161
+
162
+ it "parses a multiple key value pairs into a multi-element hash" do
163
+ client.parts_from_query("foo=bar&baz=quux").should == {'foo' => 'bar', 'baz' => 'quux'}
164
+ end
165
+ end
166
+ end
167
+ end