xian-test-jasmine 0.1.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.
@@ -0,0 +1,3 @@
1
+ require 'jasmine-ruby/jasmine_helper'
2
+ require 'jasmine-ruby/jasmine_runner'
3
+ require 'jasmine-ruby/jasmine_spec_builder'
@@ -0,0 +1,48 @@
1
+ class JasmineHelper
2
+ def self.lib_dir
3
+ File.expand_path(File.join(root, 'lib'))
4
+ end
5
+
6
+ def self.jasmine
7
+ ['/lib/' + File.basename(Dir.glob("#{JasmineHelper.lib_dir}/jasmine*.js").first)] +
8
+ ['/lib/json2.js',
9
+ '/lib/TrivialReporter.js']
10
+ end
11
+
12
+ def self.root
13
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'jasmine'))
14
+ end
15
+
16
+ def self.spec_dir
17
+ File.expand_path('spec')
18
+ end
19
+
20
+ def self.spec_files
21
+ Dir.glob(File.join(spec_dir, "javascripts/**/*[Ss]pec.js"))
22
+ end
23
+
24
+ def self.specs
25
+ spec_files.collect {|f| f.sub(spec_dir, "/spec")}
26
+ end
27
+
28
+ def self.spec_helpers_files
29
+ Dir.glob(File.join(spec_dir, "helpers/**/*.js"))
30
+ end
31
+
32
+ def self.spec_helpers
33
+ spec_helpers_files.collect {|f| f.sub(spec_dir, "/spec")}
34
+ end
35
+
36
+ def self.meta_spec_path
37
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'jasmine-ruby', 'jasmine_meta_spec.rb'))
38
+ end
39
+
40
+ def self.files
41
+ []
42
+ end
43
+
44
+ def self.stylesheets
45
+ []
46
+ end
47
+
48
+ end
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require "selenium_rc"
3
+ require File.expand_path(File.join(File.dirname(__FILE__), "jasmine_helper.rb"))
4
+ helper_overrides = File.expand_path(File.join(Dir.pwd, "spec", "helpers", "jasmine_helper.rb"))
5
+ if File.exist?(helper_overrides)
6
+ require helper_overrides
7
+ end
8
+ require File.expand_path(File.join(File.dirname(__FILE__), "jasmine_runner.rb"))
9
+ require File.expand_path(File.join(File.dirname(__FILE__), "jasmine_spec_builder"))
10
+
11
+ jasmine_runner = Jasmine::Runner.new(SeleniumRC::Server.new.jar_path,
12
+ Dir.pwd,
13
+ JasmineHelper.specs,
14
+ { :spec_helpers => JasmineHelper.files + JasmineHelper.spec_helpers,
15
+ :stylesheets => JasmineHelper.stylesheets
16
+ })
17
+
18
+ spec_builder = Jasmine::SpecBuilder.new(JasmineHelper.spec_files, jasmine_runner)
19
+
20
+ should_stop = false
21
+
22
+ Spec::Runner.configure do |config|
23
+ config.after(:suite) do
24
+ spec_builder.stop if should_stop
25
+ end
26
+ end
27
+
28
+ spec_builder.start
29
+ should_stop = true
30
+ spec_builder.declare_suites
@@ -0,0 +1,300 @@
1
+ require 'socket'
2
+ require 'erb'
3
+ require 'json'
4
+
5
+ module Jasmine
6
+ def self.root
7
+ File.expand_path(File.join(File.dirname(__FILE__), '../../jasmine'))
8
+ end
9
+
10
+ # this seemingly-over-complex method is necessary to get an open port on at least some of our Macs
11
+ def self.open_socket_on_unused_port
12
+ infos = Socket::getaddrinfo("localhost", nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM, 0, Socket::AI_PASSIVE)
13
+ families = Hash[*infos.collect { |af, *_| af }.uniq.zip([]).flatten]
14
+
15
+ return TCPServer.open('0.0.0.0', 0) if families.has_key?('AF_INET')
16
+ return TCPServer.open('::', 0) if families.has_key?('AF_INET6')
17
+ return TCPServer.open(0)
18
+ end
19
+
20
+ def self.find_unused_port
21
+ socket = open_socket_on_unused_port
22
+ port = socket.addr[1]
23
+ socket.close
24
+ port
25
+ end
26
+
27
+ def self.server_is_listening_on(hostname, port)
28
+ require 'socket'
29
+ begin
30
+ socket = TCPSocket.open(hostname, port)
31
+ rescue Errno::ECONNREFUSED
32
+ return false
33
+ end
34
+ socket.close
35
+ true
36
+ end
37
+
38
+ def self.wait_for_listener(port, name = "required process", seconds_to_wait = 10)
39
+ time_out_at = Time.now + seconds_to_wait
40
+ until server_is_listening_on "localhost", port
41
+ sleep 0.1
42
+ puts "Waiting for #{name} on #{port}..."
43
+ raise "#{name} didn't show up on port #{port} after #{seconds_to_wait} seconds." if Time.now > time_out_at
44
+ end
45
+ end
46
+
47
+ def self.kill_process_group(process_group_id, signal="TERM")
48
+ Process.kill signal, -process_group_id # negative pid means kill process group. (see man 2 kill)
49
+ end
50
+
51
+ def self.cachebust(files, root_dir="", replace=nil, replace_with=nil)
52
+ require 'digest/md5'
53
+ files.collect do |file_name|
54
+ real_file_name = replace && replace_with ? file_name.sub(replace, replace_with) : file_name
55
+ begin
56
+ digest = Digest::MD5.hexdigest(File.read("#{root_dir}#{real_file_name}"))
57
+ rescue
58
+ digest = "MISSING-FILE"
59
+ end
60
+ "#{file_name}?cachebust=#{digest}"
61
+ end
62
+ end
63
+
64
+ class RunAdapter
65
+ def initialize(spec_files_or_proc, options = {})
66
+ @spec_files_or_proc = Jasmine.files(spec_files_or_proc) || []
67
+ @jasmine_files = Jasmine.files(options[:jasmine_files]) || [
68
+ "/__JASMINE_ROOT__/lib/" + File.basename(Dir.glob("#{Jasmine.root}/lib/jasmine*.js").first),
69
+ "/__JASMINE_ROOT__/lib/TrivialReporter.js",
70
+ "/__JASMINE_ROOT__/lib/json2.js",
71
+ "/__JASMINE_ROOT__/lib/consolex.js",
72
+ ]
73
+ @stylesheets = ["/__JASMINE_ROOT__/lib/jasmine.css"] + (Jasmine.files(options[:stylesheets]) || [])
74
+ @spec_helpers = Jasmine.files(options[:spec_helpers]) || []
75
+ end
76
+
77
+ def call(env)
78
+ run
79
+ end
80
+
81
+ def run
82
+ stylesheets = @stylesheets
83
+ spec_helpers = @spec_helpers
84
+ spec_files = @spec_files_or_proc
85
+
86
+ jasmine_files = @jasmine_files
87
+ jasmine_files = jasmine_files.call if jasmine_files.respond_to?(:call)
88
+
89
+ css_files = @stylesheets
90
+
91
+
92
+ body = ERB.new(File.read(File.join(File.dirname(__FILE__), "run.html"))).result(binding)
93
+ [
94
+ 200,
95
+ { 'Content-Type' => 'text/html' },
96
+ body
97
+ ]
98
+ end
99
+
100
+
101
+ end
102
+
103
+ class Redirect
104
+ def initialize(url)
105
+ @url = url
106
+ end
107
+
108
+ def call(env)
109
+ [
110
+ 302,
111
+ { 'Location' => @url },
112
+ []
113
+ ]
114
+ end
115
+ end
116
+
117
+ class JsAlert
118
+ def call(env)
119
+ [
120
+ 200,
121
+ { 'Content-Type' => 'application/javascript' },
122
+ "document.write('<p>Couldn\\'t load #{env["PATH_INFO"]}!</p>');"
123
+ ]
124
+ end
125
+ end
126
+
127
+ class FocusedSuite
128
+ def initialize(spec_files_or_proc, options)
129
+ @spec_files_or_proc = Jasmine.files(spec_files_or_proc) || []
130
+ @options = options
131
+ end
132
+
133
+ def call(env)
134
+ spec_files = @spec_files_or_proc
135
+ matching_specs = spec_files.select {|spec_file| spec_file =~ /#{Regexp.escape(env["PATH_INFO"])}/ }.compact
136
+ if !matching_specs.empty?
137
+ run_adapter = Jasmine::RunAdapter.new(matching_specs, @options)
138
+ run_adapter.run
139
+ else
140
+ [
141
+ 200,
142
+ { 'Content-Type' => 'application/javascript' },
143
+ "document.write('<p>Couldn\\'t find any specs matching #{env["PATH_INFO"]}!</p>');"
144
+ ]
145
+ end
146
+ end
147
+
148
+ end
149
+
150
+ class SimpleServer
151
+ def self.start(port, root_path, spec_files_or_proc, options = {})
152
+ require 'thin'
153
+ config = {
154
+ '/__suite__' => Jasmine::FocusedSuite.new(spec_files_or_proc, options),
155
+ '/run.html' => Jasmine::Redirect.new('/'),
156
+ '/' => Jasmine::RunAdapter.new(spec_files_or_proc, options)
157
+ }
158
+ if (options[:mappings])
159
+ options[:mappings].each do |from, to|
160
+ config[from] = Rack::File.new(to)
161
+ end
162
+ end
163
+
164
+ config["/__JASMINE_ROOT__"] = Rack::File.new(Jasmine.root)
165
+
166
+ file_serve_config = {
167
+ '/' => Rack::File.new(root_path)
168
+ }
169
+
170
+ app = Rack::Cascade.new([
171
+ Rack::URLMap.new(file_serve_config),
172
+ Rack::URLMap.new(config),
173
+ JsAlert.new
174
+ ])
175
+
176
+ begin
177
+ Thin::Server.start('0.0.0.0', port, app)
178
+ rescue RuntimeError => e
179
+ raise e unless e.message == 'no acceptor'
180
+ raise RuntimeError.new("A server is already running on port #{port}")
181
+ end
182
+ end
183
+ end
184
+
185
+ class SimpleClient
186
+ def initialize(selenium_host, selenium_port, selenium_browser_start_command, http_address)
187
+ require 'selenium/client'
188
+ @driver = Selenium::Client::Driver.new(
189
+ selenium_host,
190
+ selenium_port,
191
+ selenium_browser_start_command,
192
+ http_address
193
+ )
194
+ @http_address = http_address
195
+ end
196
+
197
+ def tests_have_finished?
198
+ @driver.get_eval("window.jasmine.getEnv().currentRunner.finished") == "true"
199
+ end
200
+
201
+ def connect
202
+ @driver.start
203
+ @driver.open("/")
204
+ end
205
+
206
+ def disconnect
207
+ @driver.stop
208
+ end
209
+
210
+ def run
211
+ until tests_have_finished? do
212
+ sleep 0.1
213
+ end
214
+
215
+ puts @driver.get_eval("window.results()")
216
+ failed_count = @driver.get_eval("window.jasmine.getEnv().currentRunner.results().failedCount").to_i
217
+ failed_count == 0
218
+ end
219
+
220
+ def eval_js(script)
221
+ escaped_script = "'" + script.gsub(/(['\\])/) { '\\' + $1 } + "'"
222
+
223
+ result = @driver.get_eval(" try { eval(#{escaped_script}, window); } catch(err) { window.eval(#{escaped_script}); }")
224
+ JSON.parse("[#{result}]")[0]
225
+ end
226
+ end
227
+
228
+ class Runner
229
+ def initialize(selenium_jar_path, root_path, spec_files, options={})
230
+ @root_path = root_path
231
+ @selenium_jar_path = selenium_jar_path
232
+ @spec_files = spec_files
233
+ @options = options
234
+
235
+ @browser = options[:browser] ? options[:browser].delete(:browser) : 'firefox'
236
+ @selenium_pid = nil
237
+ @jasmine_server_pid = nil
238
+ end
239
+
240
+ def start
241
+ start_servers
242
+ @client = Jasmine::SimpleClient.new("localhost", @selenium_server_port, "*#{@browser}", "http://localhost:#{@jasmine_server_port}/")
243
+ @client.connect
244
+ end
245
+
246
+ def stop
247
+ @client.disconnect
248
+ stop_servers
249
+ end
250
+
251
+ def start_servers
252
+ @jasmine_server_port = Jasmine::find_unused_port
253
+ @selenium_server_port = Jasmine::find_unused_port
254
+
255
+ @selenium_pid = fork do
256
+ Process.setpgrp
257
+ exec "java -jar #{@selenium_jar_path} -port #{@selenium_server_port} > /dev/null 2>&1"
258
+ end
259
+ puts "selenium started. pid is #{@selenium_pid}"
260
+
261
+ @jasmine_server_pid = fork do
262
+ Process.setpgrp
263
+ Jasmine::SimpleServer.start(@jasmine_server_port, @root_path, @spec_files, @options)
264
+ exit! 0
265
+ end
266
+ puts "jasmine server started. pid is #{@jasmine_server_pid}"
267
+
268
+ Jasmine::wait_for_listener(@selenium_server_port, "selenium server")
269
+ Jasmine::wait_for_listener(@jasmine_server_port, "jasmine server")
270
+ end
271
+
272
+ def stop_servers
273
+ puts "shutting down the servers..."
274
+ Jasmine::kill_process_group(@selenium_pid) if @selenium_pid
275
+ Jasmine::kill_process_group(@jasmine_server_pid) if @jasmine_server_pid
276
+ end
277
+
278
+ def run
279
+ begin
280
+ start
281
+ puts "servers are listening on their ports -- running the test script..."
282
+ tests_passed = @client.run
283
+ ensure
284
+ stop
285
+ end
286
+ return tests_passed
287
+ end
288
+
289
+ def eval_js(script)
290
+ @client.eval_js(script)
291
+ end
292
+ end
293
+
294
+ def self.files(f)
295
+ result = f
296
+ result = result.call if result.respond_to?(:call)
297
+ result
298
+ end
299
+
300
+ end
@@ -0,0 +1,152 @@
1
+ require 'enumerator'
2
+ module Jasmine
3
+
4
+ class SpecBuilder
5
+ attr_accessor :suites
6
+
7
+ def initialize(spec_files, runner)
8
+ @spec_files = spec_files
9
+ @runner = runner
10
+ @spec_ids = []
11
+ end
12
+
13
+ def start
14
+ guess_example_locations
15
+
16
+ @runner.start
17
+ load_suite_info
18
+ wait_for_suites_to_finish_running
19
+ end
20
+
21
+ def stop
22
+ @runner.stop
23
+ end
24
+
25
+ def script_path
26
+ File.expand_path(__FILE__)
27
+ end
28
+
29
+ def guess_example_locations
30
+ @example_locations = {}
31
+
32
+ example_name_parts = []
33
+ previous_indent_level = 0
34
+ @spec_files.each do |filename|
35
+ line_number = 1
36
+ File.open(filename, "r") do |file|
37
+ file.readlines.each do |line|
38
+ match = /^(\s*)(describe|it)\s*\(\s*["'](.*)["']\s*,\s*function/.match(line)
39
+ if (match)
40
+ indent_level = match[1].length / 2
41
+ example_name = match[3]
42
+ example_name_parts[indent_level] = example_name
43
+
44
+ full_example_name = example_name_parts.slice(0, indent_level + 1).join(" ")
45
+ @example_locations[full_example_name] = "#{filename}:#{line_number}: in `it'"
46
+ end
47
+ line_number += 1
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ def load_suite_info
54
+ started = Time.now
55
+ while !eval_js('jsApiReporter && jsApiReporter.started') do
56
+ raise "couldn't connect to Jasmine after 60 seconds" if (started + 60 < Time.now)
57
+ sleep 0.1
58
+ end
59
+
60
+ @suites = eval_js('JSON.stringify(jsApiReporter.suites())')
61
+ end
62
+
63
+ def results_for(spec_id)
64
+ @spec_results ||= load_results
65
+ @spec_results[spec_id.to_s]
66
+ end
67
+
68
+ def load_results
69
+ @spec_results = {}
70
+ @spec_ids.each_slice(50) do |slice|
71
+ @spec_results.merge!(eval_js("JSON.stringify(jsApiReporter.resultsForSpecs(#{JSON.generate(slice)}))"))
72
+ end
73
+ @spec_results
74
+ end
75
+
76
+ def wait_for_suites_to_finish_running
77
+ puts "Waiting for suite to finish in browser ..."
78
+ while !eval_js('jsApiReporter.finished') do
79
+ sleep 0.1
80
+ end
81
+ end
82
+
83
+ def declare_suites
84
+ me = self
85
+ suites.each do |suite|
86
+ declare_suite(self, suite)
87
+ end
88
+ end
89
+
90
+ def declare_suite(parent, suite)
91
+ me = self
92
+ parent.describe suite["name"] do
93
+ suite["children"].each do |suite_or_spec|
94
+ type = suite_or_spec["type"]
95
+ if type == "suite"
96
+ me.declare_suite(self, suite_or_spec)
97
+ elsif type == "spec"
98
+ me.declare_spec(self, suite_or_spec)
99
+ else
100
+ raise "unknown type #{type} for #{suite_or_spec.inspect}"
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def declare_spec(parent, spec)
107
+ me = self
108
+ example_name = spec["name"]
109
+ @spec_ids << spec["id"]
110
+ backtrace = @example_locations[parent.description + " " + example_name]
111
+ parent.it example_name, {}, backtrace do
112
+ me.report_spec(spec["id"])
113
+ end
114
+ end
115
+
116
+ def report_spec(spec_id)
117
+ spec_results = results_for(spec_id)
118
+
119
+ out = ""
120
+ messages = spec_results['messages'].each do |message|
121
+ case
122
+ when message["type"] == "MessageResult"
123
+ puts message["text"]
124
+ puts "\n"
125
+ else
126
+ unless message["message"] =~ /^Passed.$/
127
+ STDERR << message["message"]
128
+ STDERR << "\n"
129
+
130
+ out << message["message"]
131
+ out << "\n"
132
+ end
133
+
134
+ if !message["passed"] && message["trace"]["stack"]
135
+ stack_trace = message["trace"]["stack"].gsub(/<br \/>/, "\n").gsub(/<\/?b>/, " ")
136
+ STDERR << stack_trace.gsub(/\(.*\)@http:\/\/localhost:[0-9]+\/specs\//, "/spec/")
137
+ STDERR << "\n"
138
+ end
139
+ end
140
+
141
+ end
142
+ fail out unless spec_results['result'] == 'passed'
143
+ puts out unless out.empty?
144
+ end
145
+
146
+ private
147
+
148
+ def eval_js(js)
149
+ @runner.eval_js(js)
150
+ end
151
+ end
152
+ end