Skip to content

Commit 396b818

Browse files
committed
Add PhantomJS runner for UJS integration tests
1 parent 0256880 commit 396b818

File tree

5 files changed

+221
-2
lines changed

5 files changed

+221
-2
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ cache:
66
directories:
77
- /tmp/cache/unicode_conformance
88
- /tmp/beanstalkd-1.10
9+
- node_modules
10+
- $HOME/.nvm
911

1012
services:
1113
- memcached
@@ -21,6 +23,8 @@ before_install:
2123
- "gem update bundler"
2224
- "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
2325
- "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
26+
- "nvm install node"
27+
- "[[ $(phantomjs --version) > '2' ]] || npm install -g phantomjs-prebuilt"
2428

2529
before_script:
2630
- bundle update

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ group :test do
9797
end
9898

9999
gem "benchmark-ips"
100+
gem "childprocess"
100101
end
101102

102103
platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do

Gemfile.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ DEPENDENCIES
382382
blade
383383
blade-sauce_labs_plugin
384384
byebug
385+
childprocess
385386
coffee-rails
386387
dalli (>= 2.2.1)
387388
delayed_job!

actionview/Rakefile

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ task :package
88
# Run the unit tests
99

1010
desc "Run all unit tests"
11-
task test: ["test:template", "test:integration:action_pack", "test:integration:active_record"]
11+
task test: ["test:template", "test:integration:action_pack", "test:integration:active_record", "test:integration:ujs"]
1212

1313
namespace :test do
1414
task :isolated do
@@ -43,13 +43,29 @@ namespace :test do
4343
t.verbose = true
4444
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
4545
end
46+
47+
desc "UJS Integration Tests"
48+
task :ujs do
49+
if phantomjs = which("phantomjs")
50+
process = start_process
51+
puts "Web server process started..."
52+
fail "Failed due to timeout running server process" unless wait_for_process(4567)
53+
puts "Running tests with phantomjs..."
54+
output = `#{phantomjs} test/ujs/runner.js http://localhost:4567`
55+
puts output.lines.grep(/passed/)
56+
fail "UJS tests failed" unless $?.success?
57+
stop_process(process)
58+
else
59+
puts "Skipping UJS tests because PhantomJS was not detected in PATH"
60+
end
61+
end
4662
end
4763
end
4864

4965
namespace :ujs do
5066
desc "Starts the test server"
5167
task :server do
52-
system 'bundle exec rackup test/ujs/config.ru -p 4567 -s puma'
68+
system "bundle exec rackup test/ujs/config.ru -p 4567 -s puma"
5369
end
5470
end
5571

@@ -58,3 +74,52 @@ task :lines do
5874
files = FileList["lib/**/*.rb"]
5975
CodeTools::LineStatistics.new(files).print_loc
6076
end
77+
78+
def which(cmd)
79+
exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
80+
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
81+
exts.each { |ext|
82+
exe = "#{path}/#{cmd}#{ext}"
83+
return exe if File.executable? exe
84+
}
85+
end
86+
return nil
87+
end
88+
89+
def start_process
90+
require "childprocess"
91+
FileUtils.mkdir_p("test/ujs/log")
92+
args = %w(ruby -S bundle exec rackup test/ujs/config.ru -p 4567 -s puma)
93+
process = ChildProcess.build(*args)
94+
process.start
95+
process
96+
end
97+
98+
def stop_process(process)
99+
begin
100+
process.poll_for_exit(5)
101+
rescue ChildProcess::TimeoutError
102+
process.stop
103+
end
104+
end
105+
106+
def wait_for_process(port, timeout = 5)
107+
timestamp = Time.now.to_i + timeout
108+
while true
109+
if port_busy?(port)
110+
return true
111+
elsif Time.now.to_i > timestamp
112+
puts "timed out after #{timeout} seconds"
113+
return false
114+
end
115+
end
116+
end
117+
118+
# TODO: Test this in a Windows machine.
119+
def port_busy?(port)
120+
if RbConfig::CONFIG["host_os"] =~ /msdos|mswin|djgpp|mingw|windows/
121+
`netstat -an | findstr ":#{port}.*:[^:]*$"` != ""
122+
else
123+
`lsof -i :#{port}` != ""
124+
end
125+
end

actionview/test/ujs/runner.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* PhantomJS Runner QUnit Plugin 1.2.0
3+
*
4+
* PhantomJS binaries: http://phantomjs.org/download.html
5+
* Requires PhantomJS 1.6+ (1.7+ recommended)
6+
*
7+
* Run with:
8+
* phantomjs runner.js [url-of-your-qunit-testsuite]
9+
*
10+
* e.g.
11+
* phantomjs runner.js http://localhost/qunit/test/index.html
12+
*/
13+
14+
/*global phantom:false, require:false, console:false, window:false, QUnit:false */
15+
16+
(function() {
17+
'use strict';
18+
19+
var url, page, timeout,
20+
args = require('system').args;
21+
22+
// arg[0]: scriptName, args[1...]: arguments
23+
if (args.length < 2 || args.length > 3) {
24+
console.error('Usage:\n phantomjs runner.js [url-of-your-qunit-testsuite] [timeout-in-seconds]');
25+
phantom.exit(1);
26+
}
27+
28+
url = args[1];
29+
page = require('webpage').create();
30+
if (args[2] !== undefined) {
31+
timeout = parseInt(args[2], 10);
32+
}
33+
34+
// Route `console.log()` calls from within the Page context to the main Phantom context (i.e. current `this`)
35+
page.onConsoleMessage = function(msg) {
36+
console.log(msg);
37+
};
38+
39+
page.onInitialized = function() {
40+
page.evaluate(addLogging);
41+
};
42+
43+
page.onCallback = function(message) {
44+
var result,
45+
failed;
46+
47+
if (message) {
48+
if (message.name === 'QUnit.done') {
49+
result = message.data;
50+
failed = !result || !result.total || result.failed;
51+
52+
if (!result.total) {
53+
console.error('No tests were executed. Are you loading tests asynchronously?');
54+
}
55+
56+
phantom.exit(failed ? 1 : 0);
57+
}
58+
}
59+
};
60+
61+
page.open(url, function(status) {
62+
if (status !== 'success') {
63+
console.error('Unable to access network: ' + status);
64+
phantom.exit(1);
65+
} else {
66+
// Cannot do this verification with the 'DOMContentLoaded' handler because it
67+
// will be too late to attach it if a page does not have any script tags.
68+
var qunitMissing = page.evaluate(function() { return (typeof QUnit === 'undefined' || !QUnit); });
69+
if (qunitMissing) {
70+
console.error('The `QUnit` object is not present on this page.');
71+
phantom.exit(1);
72+
}
73+
74+
// Set a timeout on the test running, otherwise tests with async problems will hang forever
75+
if (typeof timeout === 'number') {
76+
setTimeout(function() {
77+
console.error('The specified timeout of ' + timeout + ' seconds has expired. Aborting...');
78+
phantom.exit(1);
79+
}, timeout * 1000);
80+
}
81+
82+
// Do nothing... the callback mechanism will handle everything!
83+
}
84+
});
85+
86+
function addLogging() {
87+
window.document.addEventListener('DOMContentLoaded', function() {
88+
var currentTestAssertions = [];
89+
90+
QUnit.log(function(details) {
91+
var response;
92+
93+
// Ignore passing assertions
94+
if (details.result) {
95+
return;
96+
}
97+
98+
response = details.message || '';
99+
100+
if (typeof details.expected !== 'undefined') {
101+
if (response) {
102+
response += ', ';
103+
}
104+
105+
response += 'expected: ' + details.expected + ', but was: ' + details.actual;
106+
}
107+
108+
if (details.source) {
109+
response += "\n" + details.source;
110+
}
111+
112+
currentTestAssertions.push('Failed assertion: ' + response);
113+
});
114+
115+
QUnit.testDone(function(result) {
116+
var i,
117+
len,
118+
name = '';
119+
120+
if (result.module) {
121+
name += result.module + ': ';
122+
}
123+
name += result.name;
124+
125+
if (result.failed) {
126+
console.log('\n' + 'Test failed: ' + name);
127+
128+
for (i = 0, len = currentTestAssertions.length; i < len; i++) {
129+
console.log(' ' + currentTestAssertions[i]);
130+
}
131+
}
132+
133+
currentTestAssertions.length = 0;
134+
});
135+
136+
QUnit.done(function(result) {
137+
console.log('\n' + 'Took ' + result.runtime + 'ms to run ' + result.total + ' tests. ' + result.passed + ' passed, ' + result.failed + ' failed.');
138+
139+
if (typeof window.callPhantom === 'function') {
140+
window.callPhantom({
141+
'name': 'QUnit.done',
142+
'data': result
143+
});
144+
}
145+
});
146+
}, false);
147+
}
148+
})();

0 commit comments

Comments
 (0)