2
2
require 'sinatra/base'
3
3
require 'cgi'
4
4
require 'json'
5
+ require 'rbconfig'
5
6
6
7
# A barebones runner for testing across multiple browsers quickly.
7
8
#
41
42
# rake test:run BROWSERS=chrome GREP=gsub
42
43
# # (will run all tests whose names contain "gsub" in only Chrome)
43
44
45
+ # TODO:
46
+ #
47
+ # - Figure out a better way to manage the Sinatra app. Forking is the best
48
+ # way to separate its stdout and stderr, but that doesn't work on Windows.
49
+ # - Allow the user to specify paths to browser executables via a YAML file or
50
+ # something. Especially crucial on Windows.
51
+ # - Get the test server to report more stuff about failures so that the
52
+ # runner's output can be more specific about what failed and why.
53
+ #
54
+
44
55
module Runner
45
56
57
+ host = RbConfig ::CONFIG [ 'host' ]
58
+ IS_WINDOWS = host . include? ( 'mswin' ) || host . include? ( 'mingw32' )
59
+
46
60
module Browsers
47
61
48
62
class Abstract
@@ -58,7 +72,6 @@ def supported?
58
72
end
59
73
60
74
def host
61
- require 'rbconfig'
62
75
RbConfig ::CONFIG [ 'host' ]
63
76
end
64
77
@@ -67,7 +80,7 @@ def macos?
67
80
end
68
81
69
82
def windows?
70
- host . include? ( 'mswin' )
83
+ host . include? ( 'mswin' ) || host . include? ( 'mingw32' )
71
84
end
72
85
73
86
def linux?
@@ -76,20 +89,16 @@ def linux?
76
89
77
90
def visit ( url )
78
91
if windows?
79
- system ( "#{ path } #{ url } " )
92
+ system ( %Q[ "#{ path } " " #{ url } "] )
80
93
elsif macos?
81
94
system ( "open -g -a '#{ path } ' '#{ url } '" )
82
95
elsif linux?
83
- system ( "#{ name } #{ url } " )
96
+ system ( %Q[ "#{ name } " " #{ url } "] )
84
97
end
85
98
end
86
99
87
100
def installed?
88
- if macos?
89
- installed = File . exists? ( path )
90
- else
91
- true # TODO
92
- end
101
+ path && File . exists? ( path )
93
102
end
94
103
95
104
def name
@@ -105,7 +114,7 @@ def path
105
114
if macos?
106
115
File . expand_path ( "/Applications/#{ name } .app" )
107
116
else
108
- @path
117
+ @path || nil
109
118
end
110
119
end
111
120
end
@@ -120,15 +129,6 @@ def supported?
120
129
true
121
130
end
122
131
123
- def path
124
- if windows?
125
- Pathname . new ( 'C:\Program Files' ) . join (
126
- 'Mozilla Firefox' , 'firefox.exe' )
127
- else
128
- super
129
- end
130
- end
131
-
132
132
end
133
133
134
134
class IE < Abstract
@@ -141,7 +141,11 @@ def supported?
141
141
windows?
142
142
end
143
143
144
- def visit
144
+ def installed?
145
+ windows?
146
+ end
147
+
148
+ def visit ( url )
145
149
ie = WIN32OLE . new ( 'InternetExplorer.Application' )
146
150
ie . visible = true
147
151
ie . Navigate ( url )
@@ -159,6 +163,10 @@ def supported?
159
163
160
164
class Chrome < Abstract
161
165
166
+ def initialize ( path = nil )
167
+ @path = path || 'C:\Program Files\Google\Chrome\Application\chrome.exe'
168
+ end
169
+
162
170
def name
163
171
'Google Chrome'
164
172
end
@@ -188,17 +196,22 @@ def initialize(path='C:\Program Files\Opera\Opera.exe')
188
196
# page will make a JSONP call when all the tests have been run.
189
197
class ResultsListener < Sinatra ::Base
190
198
set :port , 4568
199
+ set :logging , false
191
200
192
201
get '/results' do
193
202
results = {
194
- : tests => params [ :tests ] . to_i ,
195
- : passes => params [ :passes ] . to_i ,
196
- : failures => params [ :failures ] . to_i ,
197
- : duration => params [ :duration ] . to_f
203
+ " tests" => params [ :tests ] . to_i ,
204
+ " passes" => params [ :passes ] . to_i ,
205
+ " failures" => params [ :failures ] . to_i ,
206
+ " duration" => params [ :duration ] . to_f
198
207
}
199
208
200
- pipe = Runner ::write_pipe
201
- pipe . write ( JSON . dump ( results ) + "\n " )
209
+ if IS_WINDOWS
210
+ Runner ::queue . push ( results )
211
+ else
212
+ pipe = Runner ::write_pipe
213
+ pipe . write ( JSON . dump ( results ) + "\n " )
214
+ end
202
215
203
216
# We don't even need to render anything; the test page doesn't care
204
217
# about a response.
@@ -209,6 +222,10 @@ class << self
209
222
210
223
attr_accessor :read_pipe , :write_pipe
211
224
225
+ def queue
226
+ @queue ||= Queue . new
227
+ end
228
+
212
229
def run ( browsers = nil , tests = nil , grep = nil )
213
230
@browsers = browsers . nil? ? BROWSERS . keys :
214
231
browsers . split ( /\s *,\s */ ) . map ( &:to_sym )
@@ -223,25 +240,37 @@ def run(browsers=nil, tests=nil, grep=nil)
223
240
@url << "&grep=#{ CGI ::escape ( @grep ) } "
224
241
end
225
242
226
- Runner ::read_pipe , Runner ::write_pipe = IO . pipe
227
-
228
- # Start up the Sinatra app to listen for test results, but do it in a
229
- # fork because it sends some output to stdout and stderr that is
230
- # irrelevant and annoying.
231
- pid = fork do
232
- Runner ::read_pipe . close
233
- STDOUT . reopen ( '/dev/null' , 'w' )
234
- STDERR . reopen ( '/dev/null' , 'w' )
235
-
236
- ResultsListener . run!
237
- end
243
+ # If we're on Linux/OS X, we want to fork off a process here, because
244
+ # it gives us better control over stdout/stderr. But Windows doesn't
245
+ # support forking, so we have to fall back to threads.
246
+ if IS_WINDOWS
247
+ thread_id = Thread . new do
248
+ # I don't see an easy way to turn off WEBrick's annoying logging,
249
+ # so let's just ignore it.
250
+ $stderr = StringIO . new
251
+ ResultsListener . run!
252
+ end
253
+ else
254
+ Runner ::read_pipe , Runner ::write_pipe = IO . pipe
255
+
256
+ # Start up the Sinatra app to listen for test results, but do it in a
257
+ # fork because it sends some output to stdout and stderr that is
258
+ # irrelevant and annoying.
259
+ pid = fork do
260
+ Runner ::read_pipe . close
261
+ STDOUT . reopen ( '/dev/null' , 'w' )
262
+ STDERR . reopen ( '/dev/null' , 'w' )
263
+
264
+ ResultsListener . run!
265
+ end
238
266
239
- Runner ::write_pipe . close
267
+ Runner ::write_pipe . close
240
268
241
- # Make sure we clean up the forked process when we're done.
242
- at_exit do
243
- Process . kill ( 9 , pid )
244
- Process . wait ( pid )
269
+ # Make sure we clean up the forked process when we're done.
270
+ at_exit do
271
+ Process . kill ( 9 , pid )
272
+ Process . wait ( pid )
273
+ end
245
274
end
246
275
247
276
trap ( 'INT' ) { exit }
@@ -263,11 +292,16 @@ def run(browsers=nil, tests=nil, grep=nil)
263
292
browser . visit ( @url )
264
293
browser . teardown
265
294
266
- message = Runner ::read_pipe . gets
267
- results = JSON . parse ( message )
268
- results_table [ browser . name ] = results
269
-
295
+ if IS_WINDOWS
296
+ # On Windows we need to slow down a bit. I don't know why.
297
+ sleep 2
298
+ results = Runner ::queue . pop
299
+ else
300
+ message = Runner ::read_pipe . gets
301
+ results = JSON . parse ( message )
302
+ end
270
303
puts "done."
304
+ results_table [ browser . name ] = results
271
305
end
272
306
273
307
puts "\n \n "
0 commit comments