Skip to content

Commit 4f4803a

Browse files
Get the test runner working on Windows.
Windows, of course, doesn’t support process forking, so I have to use threads/queues on that awesome platform. Not ideal.
1 parent 73e4e3f commit 4f4803a

File tree

1 file changed

+81
-47
lines changed

1 file changed

+81
-47
lines changed

test.new/runner.rb

Lines changed: 81 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require 'sinatra/base'
33
require 'cgi'
44
require 'json'
5+
require 'rbconfig'
56

67
# A barebones runner for testing across multiple browsers quickly.
78
#
@@ -41,8 +42,21 @@
4142
# rake test:run BROWSERS=chrome GREP=gsub
4243
# # (will run all tests whose names contain "gsub" in only Chrome)
4344

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+
4455
module Runner
4556

57+
host = RbConfig::CONFIG['host']
58+
IS_WINDOWS = host.include?('mswin') || host.include?('mingw32')
59+
4660
module Browsers
4761

4862
class Abstract
@@ -58,7 +72,6 @@ def supported?
5872
end
5973

6074
def host
61-
require 'rbconfig'
6275
RbConfig::CONFIG['host']
6376
end
6477

@@ -67,7 +80,7 @@ def macos?
6780
end
6881

6982
def windows?
70-
host.include?('mswin')
83+
host.include?('mswin') || host.include?('mingw32')
7184
end
7285

7386
def linux?
@@ -76,20 +89,16 @@ def linux?
7689

7790
def visit(url)
7891
if windows?
79-
system("#{path} #{url}")
92+
system(%Q["#{path}" "#{url}"])
8093
elsif macos?
8194
system("open -g -a '#{path}' '#{url}'")
8295
elsif linux?
83-
system("#{name} #{url}")
96+
system(%Q["#{name}" "#{url}"])
8497
end
8598
end
8699

87100
def installed?
88-
if macos?
89-
installed = File.exists?(path)
90-
else
91-
true # TODO
92-
end
101+
path && File.exists?(path)
93102
end
94103

95104
def name
@@ -105,7 +114,7 @@ def path
105114
if macos?
106115
File.expand_path("/Applications/#{name}.app")
107116
else
108-
@path
117+
@path || nil
109118
end
110119
end
111120
end
@@ -120,15 +129,6 @@ def supported?
120129
true
121130
end
122131

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-
132132
end
133133

134134
class IE < Abstract
@@ -141,7 +141,11 @@ def supported?
141141
windows?
142142
end
143143

144-
def visit
144+
def installed?
145+
windows?
146+
end
147+
148+
def visit(url)
145149
ie = WIN32OLE.new('InternetExplorer.Application')
146150
ie.visible = true
147151
ie.Navigate(url)
@@ -159,6 +163,10 @@ def supported?
159163

160164
class Chrome < Abstract
161165

166+
def initialize(path=nil)
167+
@path = path || 'C:\Program Files\Google\Chrome\Application\chrome.exe'
168+
end
169+
162170
def name
163171
'Google Chrome'
164172
end
@@ -188,17 +196,22 @@ def initialize(path='C:\Program Files\Opera\Opera.exe')
188196
# page will make a JSONP call when all the tests have been run.
189197
class ResultsListener < Sinatra::Base
190198
set :port, 4568
199+
set :logging, false
191200

192201
get '/results' do
193202
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
198207
}
199208

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
202215

203216
# We don't even need to render anything; the test page doesn't care
204217
# about a response.
@@ -209,6 +222,10 @@ class << self
209222

210223
attr_accessor :read_pipe, :write_pipe
211224

225+
def queue
226+
@queue ||= Queue.new
227+
end
228+
212229
def run(browsers=nil, tests=nil, grep=nil)
213230
@browsers = browsers.nil? ? BROWSERS.keys :
214231
browsers.split(/\s*,\s*/).map(&:to_sym)
@@ -223,25 +240,37 @@ def run(browsers=nil, tests=nil, grep=nil)
223240
@url << "&grep=#{CGI::escape(@grep)}"
224241
end
225242

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
238266

239-
Runner::write_pipe.close
267+
Runner::write_pipe.close
240268

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
245274
end
246275

247276
trap('INT') { exit }
@@ -263,11 +292,16 @@ def run(browsers=nil, tests=nil, grep=nil)
263292
browser.visit(@url)
264293
browser.teardown
265294

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
270303
puts "done."
304+
results_table[browser.name] = results
271305
end
272306

273307
puts "\n\n"

0 commit comments

Comments
 (0)