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
@@ -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