mirror of
https://github.com/discourse/discourse.git
synced 2025-01-22 13:43:17 +08:00
add qunit to autospec
This commit is contained in:
parent
e679ba97a3
commit
b56b11d96a
|
@ -62,16 +62,12 @@ class Users::OmniauthCallbacksController < ApplicationController
|
|||
BUILTIN_AUTH.each do |authenticator|
|
||||
if authenticator.name == name
|
||||
raise Discourse::InvalidAccess.new("provider is not enabled") unless SiteSetting.send("enable_#{name}_logins?")
|
||||
|
||||
return authenticator
|
||||
end
|
||||
end
|
||||
|
||||
Discourse.auth_providers.each do |provider|
|
||||
if provider.name == name
|
||||
|
||||
return provider.authenticator
|
||||
end
|
||||
return provider.authenticator if provider.name == name
|
||||
end
|
||||
|
||||
raise Discourse::InvalidAccess.new("provider is not found")
|
||||
|
|
|
@ -2,10 +2,9 @@ require_dependency "auth/current_user_provider"
|
|||
|
||||
class Auth::DefaultCurrentUserProvider
|
||||
|
||||
CURRENT_USER_KEY = "_DISCOURSE_CURRENT_USER"
|
||||
API_KEY = "_DISCOURSE_API"
|
||||
|
||||
TOKEN_COOKIE = "_t"
|
||||
CURRENT_USER_KEY ||= "_DISCOURSE_CURRENT_USER"
|
||||
API_KEY ||= "_DISCOURSE_API"
|
||||
TOKEN_COOKIE ||= "_t"
|
||||
|
||||
# do all current user initialization here
|
||||
def initialize(env)
|
||||
|
|
|
@ -1,22 +1,36 @@
|
|||
module Autospec
|
||||
|
||||
class BaseRunner
|
||||
def run(args, specs)
|
||||
end
|
||||
|
||||
def abort
|
||||
end
|
||||
|
||||
def reload
|
||||
|
||||
# used when starting the runner - preloading happens here
|
||||
def start(opts = {})
|
||||
end
|
||||
|
||||
# indicates whether tests are running
|
||||
def running?
|
||||
true
|
||||
end
|
||||
|
||||
def start
|
||||
# launch a batch of specs/tests
|
||||
def run(specs)
|
||||
end
|
||||
|
||||
# used when we need to reload the whole application
|
||||
def reload
|
||||
end
|
||||
|
||||
# used to abort the current run
|
||||
def abort
|
||||
end
|
||||
|
||||
def failed_specs
|
||||
[]
|
||||
end
|
||||
|
||||
# used to stop the runner
|
||||
def stop
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,39 +1,46 @@
|
|||
require "rspec/core/formatters/base_formatter"
|
||||
require "rspec/core/formatters/base_text_formatter"
|
||||
|
||||
module Autospec; end
|
||||
class Autospec::Formatter < RSpec::Core::Formatters::BaseFormatter
|
||||
|
||||
def dump_summary(duration, total, failures, pending)
|
||||
# failed_specs = examples.delete_if{|e| e.execution_result[:status] != "failed"}.map{|s| s.metadata[:location]}
|
||||
class Autospec::Formatter < RSpec::Core::Formatters::BaseTextFormatter
|
||||
|
||||
# # if this fails don't kill everything
|
||||
# begin
|
||||
# FileUtils.mkdir_p('tmp')
|
||||
# File.open("./tmp/rspec_result","w") do |f|
|
||||
# f.puts failed_specs.join("\n")
|
||||
# end
|
||||
# rescue
|
||||
# # nothing really we can do, at least don't kill the test runner
|
||||
# end
|
||||
RSPEC_RESULT = "./tmp/rspec_result"
|
||||
|
||||
def initialize(output)
|
||||
super
|
||||
FileUtils.mkdir_p("tmp") unless Dir.exists?("tmp")
|
||||
end
|
||||
|
||||
def start(count)
|
||||
FileUtils.mkdir_p('tmp')
|
||||
@fail_file = File.open("./tmp/rspec_result","w")
|
||||
super(count)
|
||||
def start(example_count)
|
||||
super
|
||||
File.delete(RSPEC_RESULT) if File.exists?(RSPEC_RESULT)
|
||||
@fail_file = File.open(RSPEC_RESULT,"w")
|
||||
end
|
||||
|
||||
def example_passed(example)
|
||||
super
|
||||
output.print success_color(".")
|
||||
end
|
||||
|
||||
def example_pending(example)
|
||||
super
|
||||
output.print pending_color("*")
|
||||
end
|
||||
|
||||
def example_failed(example)
|
||||
super
|
||||
output.print failure_color("F")
|
||||
@fail_file.puts(example.metadata[:location] + " ")
|
||||
@fail_file.flush
|
||||
end
|
||||
|
||||
def start_dump
|
||||
super
|
||||
output.puts
|
||||
end
|
||||
|
||||
def close
|
||||
@fail_file.close
|
||||
super
|
||||
end
|
||||
|
||||
def example_failed(example)
|
||||
@fail_file.puts example.metadata[:location]
|
||||
@fail_file.flush
|
||||
super(example)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
241
lib/autospec/manager.rb
Normal file
241
lib/autospec/manager.rb
Normal file
|
@ -0,0 +1,241 @@
|
|||
require "listen"
|
||||
require "thread"
|
||||
require "fileutils"
|
||||
require "autospec/reload_css"
|
||||
require "autospec/base_runner"
|
||||
|
||||
module Autospec; end
|
||||
|
||||
class Autospec::Manager
|
||||
|
||||
def self.run(opts={})
|
||||
self.new.run(opts)
|
||||
end
|
||||
|
||||
def initialize
|
||||
@queue = []
|
||||
@mutex = Mutex.new
|
||||
@signal = ConditionVariable.new
|
||||
end
|
||||
|
||||
def run(opts = {})
|
||||
@runners = [ruby_runner, javascript_runner]
|
||||
|
||||
Signal.trap("HUP") { stop_runners; exit }
|
||||
Signal.trap("INT") { stop_runners; exit }
|
||||
|
||||
ensure_all_specs_will_run
|
||||
start_runners
|
||||
start_service_queue
|
||||
listen_for_changes
|
||||
|
||||
puts "Press [ENTER] to stop the current run"
|
||||
while @runners.any?(&:running?)
|
||||
STDIN.gets
|
||||
process_queue
|
||||
end
|
||||
|
||||
rescue => e
|
||||
fail(e, "failed in run")
|
||||
ensure
|
||||
stop_runners
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ruby_runner
|
||||
if ENV["SPORK"]
|
||||
require "autospec/spork_runner"
|
||||
Autospec::SporkRunner.new
|
||||
else
|
||||
require "autospec/simple_runner"
|
||||
Autospec::SimpleRunner.new
|
||||
end
|
||||
end
|
||||
|
||||
def javascript_runner
|
||||
require "autospec/qunit_runner"
|
||||
Autospec::QunitRunner.new
|
||||
end
|
||||
|
||||
def ensure_all_specs_will_run
|
||||
@runners.each do |runner|
|
||||
@queue << ['spec', 'spec', runner] unless @queue.any? { |f, s, r| s == "spec" && r == runner }
|
||||
end
|
||||
end
|
||||
|
||||
[:start, :stop, :abort].each do |verb|
|
||||
define_method("#{verb}_runners") do
|
||||
@runners.each(&verb)
|
||||
end
|
||||
end
|
||||
|
||||
def start_service_queue
|
||||
Thread.new do
|
||||
while true
|
||||
thread_loop
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# the main loop, will run the specs in the queue till one fails or the queue is empty
|
||||
def thread_loop
|
||||
@mutex.synchronize do
|
||||
current = @queue.first
|
||||
last_failed = false
|
||||
last_failed = process_spec(current) if current
|
||||
# stop & wait for the queue to have at least one item or when there's been a failure
|
||||
@signal.wait(@mutex) if @queue.length == 0 || last_failed
|
||||
end
|
||||
rescue => e
|
||||
fail(e, "failed in main loop")
|
||||
end
|
||||
|
||||
# will actually run the spec and check whether the spec has failed or not
|
||||
def process_spec(current)
|
||||
has_failed = false
|
||||
# retrieve the instance of the runner
|
||||
runner = current[2]
|
||||
# actually run the spec (blocking call)
|
||||
result = runner.run(current[1]).to_i
|
||||
|
||||
if result == 0
|
||||
# remove the spec from the queue
|
||||
@queue.shift
|
||||
else
|
||||
has_failed = true
|
||||
if result > 0
|
||||
focus_on_failed_tests(current)
|
||||
ensure_all_specs_will_run
|
||||
end
|
||||
end
|
||||
|
||||
has_failed
|
||||
end
|
||||
|
||||
def focus_on_failed_tests(current)
|
||||
runner = current[2]
|
||||
# we only want 1 focus in the queue
|
||||
@queue.shift if current[0] == "focus"
|
||||
# focus on the first 10 failed specs
|
||||
failed_specs = runner.failed_specs[0..10]
|
||||
# focus on the failed specs
|
||||
@queue.unshift ["focus", failed_specs.join(" "), runner] if failed_specs.length > 0
|
||||
end
|
||||
|
||||
def listen_for_changes(opts = {})
|
||||
options = {
|
||||
ignore: /^public|^lib\/autospec/,
|
||||
relative_paths: true,
|
||||
}
|
||||
|
||||
if opts[:force_polling]
|
||||
options[:force_polling] = true
|
||||
options[:latency] = opts[:latency] || 3
|
||||
end
|
||||
|
||||
Thread.start do
|
||||
Listen.to('.', options) do |modified, added, removed|
|
||||
process_change([modified, added].flatten.compact)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def process_change(files)
|
||||
return if files.length == 0
|
||||
specs = []
|
||||
hit = false
|
||||
|
||||
files.each do |file|
|
||||
@runners.each do |runner|
|
||||
# reloaders
|
||||
runner.reloaders.each do |k|
|
||||
if k.match(file)
|
||||
runner.reload
|
||||
return
|
||||
end
|
||||
end
|
||||
# watchers
|
||||
runner.watchers.each do |k,v|
|
||||
if m = k.match(file)
|
||||
hit = true
|
||||
spec = v ? (v.arity == 1 ? v.call(m) : v.call) : file
|
||||
specs << [file, spec, runner] if File.exists?(spec) || Dir.exists?(spec)
|
||||
end
|
||||
end
|
||||
end
|
||||
# special watcher for styles/templates
|
||||
Autospec::ReloadCss::WATCHERS.each do |k,v|
|
||||
matches = []
|
||||
matches << file if k.match(file)
|
||||
Autospec::ReloadCss.run_on_change(matches) if matches.present?
|
||||
end
|
||||
end
|
||||
|
||||
queue_specs(specs) if hit
|
||||
|
||||
rescue => e
|
||||
fail(e, "failed in watcher")
|
||||
end
|
||||
|
||||
def queue_specs(specs)
|
||||
if specs.length == 0
|
||||
locked = @mutex.try_lock
|
||||
if locked
|
||||
@signal.signal
|
||||
@mutex.unlock
|
||||
end
|
||||
return
|
||||
else
|
||||
abort_runners
|
||||
end
|
||||
|
||||
@mutex.synchronize do
|
||||
specs.each do |file, spec, runner|
|
||||
# make sure there's no other instance of this spec in the queue
|
||||
@queue.delete_if { |f, s, r| s.strip == spec.strip && r == runner }
|
||||
# deal with focused specs
|
||||
if @queue.first && @queue.first[0] == "focus"
|
||||
focus = @queue.shift
|
||||
@queue.unshift([file, spec, runner])
|
||||
if focus[1].include?(spec) || file != spec
|
||||
@queue.unshift(focus)
|
||||
end
|
||||
else
|
||||
@queue.unshift([file, spec, runner])
|
||||
end
|
||||
end
|
||||
@signal.signal
|
||||
end
|
||||
end
|
||||
|
||||
def process_queue
|
||||
if @queue.length == 0
|
||||
ensure_all_specs_will_run
|
||||
@signal.signal
|
||||
else
|
||||
current = @queue.first
|
||||
runner = current[2]
|
||||
specs = runner.failed_specs
|
||||
puts
|
||||
puts
|
||||
if specs.length == 0
|
||||
puts "No specs have failed yet!"
|
||||
puts
|
||||
else
|
||||
puts "The following specs have failed:"
|
||||
specs.each { |s| puts s }
|
||||
puts
|
||||
specs = specs.map { |s| [s, s, runner] }
|
||||
queue_specs(specs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fail(exception, message = nil)
|
||||
puts message if message
|
||||
puts exception.message
|
||||
puts exception.backtrace.join("\n")
|
||||
end
|
||||
|
||||
end
|
150
lib/autospec/qunit_runner.rb
Normal file
150
lib/autospec/qunit_runner.rb
Normal file
|
@ -0,0 +1,150 @@
|
|||
require "demon/rails_autospec"
|
||||
|
||||
module Autospec
|
||||
|
||||
class QunitRunner < BaseRunner
|
||||
|
||||
WATCHERS = {}
|
||||
def self.watch(pattern, &blk); WATCHERS[pattern] = blk; end
|
||||
def watchers; WATCHERS; end
|
||||
|
||||
# Discourse specific
|
||||
watch(%r{^app/assets/javascripts/discourse/(.+)\.js$}) { |m| "test/javascripts/#{m[1]}_test.js" }
|
||||
watch(%r{^app/assets/javascripts/admin/(.+)\.js$}) { |m| "test/javascripts/admin/#{m[1]}_test.js" }
|
||||
watch(%r{^test/javascripts/.+\.js$})
|
||||
|
||||
RELOADERS = Set.new
|
||||
def self.reload(pattern); RELOADERS << pattern; end
|
||||
def reloaders; RELOADERS; end
|
||||
|
||||
# Discourse specific
|
||||
reload(%r{^test/javascripts/fixtures/.+_fixtures\.js$})
|
||||
reload(%r{^test/javascripts/(helpers|mixins)/.+\.js$})
|
||||
reload("test/javascripts/test_helper.js")
|
||||
|
||||
require "socket"
|
||||
|
||||
class PhantomJsNotInstalled < Exception; end
|
||||
|
||||
def initialize
|
||||
ensure_phantomjs_is_installed
|
||||
end
|
||||
|
||||
def start
|
||||
# ensure we can launch the rails server
|
||||
unless port_available?(port)
|
||||
puts "Port #{port} is not available"
|
||||
puts "Either kill the process using that port or use the `TEST_SERVER_PORT` environment variable"
|
||||
return
|
||||
end
|
||||
|
||||
# start rails
|
||||
start_rails_server
|
||||
@running = true
|
||||
end
|
||||
|
||||
def running?
|
||||
@running
|
||||
end
|
||||
|
||||
def run(specs)
|
||||
puts "Running Qunit: #{specs}"
|
||||
|
||||
abort
|
||||
|
||||
qunit_url = "http://localhost:#{port}/qunit"
|
||||
|
||||
if specs != "spec" && specs.split.length == 1
|
||||
module_name = try_to_find_module_name(specs.strip)
|
||||
qunit_url << "?module=#{module_name}" if module_name
|
||||
end
|
||||
|
||||
cmd = "phantomjs #{Rails.root}/lib/autospec/run-qunit.js \"#{qunit_url}\""
|
||||
|
||||
@pid = Process.spawn(cmd)
|
||||
_, status = Process.wait2(@pid)
|
||||
|
||||
status.exitstatus
|
||||
end
|
||||
|
||||
def reload
|
||||
stop_rails_server
|
||||
sleep 1
|
||||
start_rails_server
|
||||
end
|
||||
|
||||
def abort
|
||||
if @pid
|
||||
children_processes(@pid).each { |pid| kill_process(pid) }
|
||||
kill_process(@pid)
|
||||
@pid = nil
|
||||
end
|
||||
end
|
||||
|
||||
def failed_specs
|
||||
specs = []
|
||||
path = './tmp/qunit_result'
|
||||
specs = File.readlines(path) if File.exist?(path)
|
||||
specs
|
||||
end
|
||||
|
||||
def stop
|
||||
# kill phantomjs first
|
||||
abort
|
||||
stop_rails_server
|
||||
@running = false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_phantomjs_is_installed
|
||||
raise PhantomJsNotInstalled.new unless system("command -v phantomjs >/dev/null;")
|
||||
end
|
||||
|
||||
def port_available?(port)
|
||||
TCPServer.open(port).close
|
||||
true
|
||||
rescue Errno::EADDRINUSE
|
||||
false
|
||||
end
|
||||
|
||||
def port
|
||||
@port ||= ENV["TEST_SERVER_PORT"] || 60099
|
||||
end
|
||||
|
||||
def start_rails_server
|
||||
Demon::RailsAutospec.start(1)
|
||||
end
|
||||
|
||||
def stop_rails_server
|
||||
Demon::RailsAutospec.stop
|
||||
end
|
||||
|
||||
def children_processes(base = Process.pid)
|
||||
process_tree = Hash.new { |hash, key| hash[key] = [key] }
|
||||
Hash[*`ps -eo pid,ppid`.scan(/\d+/).map(&:to_i)].each do |pid, ppid|
|
||||
process_tree[ppid] << process_tree[pid]
|
||||
end
|
||||
process_tree[base].flatten - [base]
|
||||
end
|
||||
|
||||
def kill_process(pid)
|
||||
return unless pid
|
||||
Process.kill("INT", pid) rescue nil
|
||||
while (Process.getpgid(pid) rescue nil)
|
||||
sleep 0.001
|
||||
end
|
||||
end
|
||||
|
||||
def try_to_find_module_name(file)
|
||||
return unless File.exists?(file)
|
||||
File.open(file, "r").each_line do |line|
|
||||
if m = /module\(['"]([^'"]+)/i.match(line)
|
||||
return m[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,23 +1,23 @@
|
|||
module Autospec; end
|
||||
|
||||
class Autospec::ReloadCss
|
||||
|
||||
MATCHERS = {}
|
||||
WATCHERS = {}
|
||||
def self.watch(pattern, &blk)
|
||||
MATCHERS[pattern] = blk
|
||||
WATCHERS[pattern] = blk
|
||||
end
|
||||
|
||||
watch(/tmp\/refresh_browser/)
|
||||
# css, scss, sass or handlebars
|
||||
watch(/\.css$/)
|
||||
watch(/\.css\.erb$/)
|
||||
watch(/\.sass$/)
|
||||
watch(/\.scss$/)
|
||||
watch(/\.sass\.erb$/)
|
||||
watch(/\.ca?ss\.erb$/)
|
||||
watch(/\.s[ac]ss$/)
|
||||
watch(/\.handlebars$/)
|
||||
|
||||
def self.message_bus
|
||||
MessageBus::Instance.new.tap do |bus|
|
||||
bus.site_id_lookup do
|
||||
# this is going to be dev the majority of the time, if you have multisite configured in dev stuff may be different
|
||||
# this is going to be dev the majority of the time
|
||||
# if you have multisite configured in dev stuff may be different
|
||||
"default"
|
||||
end
|
||||
end
|
||||
|
@ -26,13 +26,13 @@ class Autospec::ReloadCss
|
|||
def self.run_on_change(paths)
|
||||
paths.map! do |p|
|
||||
hash = nil
|
||||
fullpath = Rails.root.to_s + "/" + p
|
||||
hash = Digest::MD5.hexdigest(File.read(fullpath)) if File.exists? fullpath
|
||||
fullpath = "#{Rails.root}/#{p}"
|
||||
hash = Digest::MD5.hexdigest(File.read(fullpath)) if File.exists?(fullpath)
|
||||
p = p.sub /\.sass\.erb/, ""
|
||||
p = p.sub /\.sass/, ""
|
||||
p = p.sub /\.scss/, ""
|
||||
p = p.sub /^app\/assets\/stylesheets/, "assets"
|
||||
{name: p, hash: hash}
|
||||
{ name: p, hash: hash }
|
||||
end
|
||||
message_bus.publish "/file-change", paths
|
||||
end
|
||||
|
|
43
lib/autospec/rspec_runner.rb
Normal file
43
lib/autospec/rspec_runner.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
module Autospec
|
||||
|
||||
class RspecRunner < BaseRunner
|
||||
|
||||
WATCHERS = {}
|
||||
def self.watch(pattern, &blk); WATCHERS[pattern] = blk; end
|
||||
def watchers; WATCHERS; end
|
||||
|
||||
# Discourse specific
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/components/#{m[1]}_spec.rb" }
|
||||
|
||||
# Rails example
|
||||
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
||||
watch(%r{^app/(.+)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
watch(%r{^spec/support/.+\.rb$}) { "spec" }
|
||||
watch("app/controllers/application_controller.rb") { "spec/controllers" }
|
||||
|
||||
# Capybara request specs
|
||||
watch(%r{^app/views/(.+)/.+\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
||||
|
||||
# Fabrication
|
||||
watch(%r{^spec/fabricators/.+_fabricator\.rb$}) { "spec" }
|
||||
|
||||
RELOADERS = Set.new
|
||||
def self.reload(pattern); RELOADERS << pattern; end
|
||||
def reloaders; RELOADERS; end
|
||||
|
||||
# We need to reload the whole app when changing any of these files
|
||||
reload('spec/spec_helper.rb')
|
||||
reload('config/(.*).rb')
|
||||
reload('app/helpers/(.*).rb')
|
||||
|
||||
def failed_specs
|
||||
specs = []
|
||||
path = './tmp/rspec_result'
|
||||
specs = File.readlines(path) if File.exist?(path)
|
||||
specs
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
175
lib/autospec/run-qunit.js
Normal file
175
lib/autospec/run-qunit.js
Normal file
|
@ -0,0 +1,175 @@
|
|||
// THIS FILE IS CALLED BY "qunit_runner.rb" IN AUTOSPEC
|
||||
|
||||
if (phantom.args.length != 1) {
|
||||
console.log("Usage: " + phantom.scriptName + " <URL>");
|
||||
phantom.exit(1);
|
||||
}
|
||||
|
||||
var system = require('system'),
|
||||
fs = require('fs'),
|
||||
page = require('webpage').create(),
|
||||
QUNIT_RESULT = "./tmp/qunit_result";
|
||||
|
||||
if (fs.exists(QUNIT_RESULT) && fs.isFile(QUNIT_RESULT)) { fs.remove(QUNIT_RESULT); }
|
||||
|
||||
page.onConsoleMessage = function (message) {
|
||||
// filter out Ember's debug messages
|
||||
if (message.slice(0, 8) === "WARNING:") { return; }
|
||||
if (message.slice(0, 6) === "DEBUG:") { return; }
|
||||
|
||||
console.log(message);
|
||||
};
|
||||
|
||||
page.onCallback = function (message) {
|
||||
// write to the result file
|
||||
if (message.slice(0, 5) === "FILE:") { fs.write(QUNIT_RESULT, message.slice(6), "a"); }
|
||||
// forward the message to the standard output
|
||||
if (message.slice(0, 6) === "PRINT:") { system.stdout.write(message.slice(7)); }
|
||||
};
|
||||
|
||||
page.start = new Date();
|
||||
|
||||
// -----------------------------------WARNING --------------------------------------
|
||||
// calling "console.log" BELOW this line will go through the "page.onConsoleMessage"
|
||||
// -----------------------------------WARNING --------------------------------------
|
||||
page.open(phantom.args[0], function (status) {
|
||||
if (status !== "success") {
|
||||
console.log("\nNO NETWORK :(\n");
|
||||
phantom.exit(1);
|
||||
} else {
|
||||
console.log("QUnit loaded in " + (new Date() - page.start) + " ms");
|
||||
|
||||
page.evaluate(colorizer);
|
||||
page.evaluate(logQUnit);
|
||||
|
||||
// wait up to 60 seconds for QUnit to finish
|
||||
var timeout = 60 * 1000,
|
||||
start = Date.now();
|
||||
|
||||
var interval = setInterval(function() {
|
||||
if (Date.now() - start > timeout) {
|
||||
console.error("\nTIME OUT :(\n");
|
||||
phantom.exit(1);
|
||||
} else {
|
||||
var qunitResult = page.evaluate(function() { return window.qunitResult; });
|
||||
if (qunitResult) {
|
||||
clearInterval(interval);
|
||||
if (qunitResult.failed > 0) {
|
||||
phantom.exit(1);
|
||||
} else {
|
||||
phantom.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
|
||||
// https://github.com/jquery/qunit/pull/470
|
||||
function colorizer() {
|
||||
window.ANSI = {
|
||||
colorMap: {
|
||||
"red": "\u001b[31m",
|
||||
"green": "\u001b[32m",
|
||||
"blue": "\u001b[34m",
|
||||
"end": "\u001b[0m"
|
||||
},
|
||||
highlightMap: {
|
||||
"red": "\u001b[41m\u001b[37m", // change 37 to 30 for black text
|
||||
"green": "\u001b[42m\u001b[30m",
|
||||
"blue": "\u001b[44m\u001b[37m",
|
||||
"end": "\u001b[0m"
|
||||
},
|
||||
|
||||
highlight: function (text, color) {
|
||||
var colorCode = this.highlightMap[color],
|
||||
colorEnd = this.highlightMap.end;
|
||||
|
||||
return colorCode + text + colorEnd;
|
||||
},
|
||||
|
||||
colorize: function (text, color) {
|
||||
var colorCode = this.colorMap[color],
|
||||
colorEnd = this.colorMap.end;
|
||||
|
||||
return colorCode + text + colorEnd;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function logQUnit() {
|
||||
// keep track of error messages
|
||||
var errors = {};
|
||||
|
||||
QUnit.begin(function () {
|
||||
console.log("BEGIN");
|
||||
});
|
||||
|
||||
QUnit.log(function (context) {
|
||||
if (!context.result) {
|
||||
var module = context.module,
|
||||
test = context.name;
|
||||
|
||||
var assertion = {
|
||||
message: context.message,
|
||||
expected: context.expected,
|
||||
actual: context.actual
|
||||
};
|
||||
|
||||
if (!errors[module]) { errors[module] = {}; }
|
||||
if (!errors[module][test]) { errors[module][test] = []; }
|
||||
errors[module][test].push(assertion);
|
||||
|
||||
var fileName = context.source
|
||||
.replace(/[^\S\n]+at[^\S\n]+/g, "")
|
||||
.split("\n")[1]
|
||||
.replace(/\?.+$/, "")
|
||||
.replace(/^.+\/assets\//, "test/javascripts/");
|
||||
window.callPhantom("FILE: " + fileName + " ");
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.testDone(function (context) {
|
||||
if (context.failed > 0) {
|
||||
window.callPhantom("PRINT: " + ANSI.colorize("F", "red"));
|
||||
} else {
|
||||
window.callPhantom("PRINT: " + ANSI.colorize(".", "green"));
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.done(function (context) {
|
||||
console.log("\n");
|
||||
|
||||
// display failures
|
||||
if (Object.keys(errors).length > 0) {
|
||||
console.log("Failures:\n");
|
||||
for (m in errors) {
|
||||
var module = errors[m];
|
||||
console.log("Module Failed: " + ANSI.highlight(m, "red"));
|
||||
for (t in module) {
|
||||
var test = module[t];
|
||||
console.log(" Test Failed: " + t);
|
||||
for (var a = 0; a < test.length; a++) {
|
||||
var assertion = test[a];
|
||||
console.log(" Assertion Failed: " + (assertion.message || ""));
|
||||
if (assertion.expected) {
|
||||
console.log(" Expected: " + assertion.expected);
|
||||
console.log(" Actual: " + assertion.actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// display summary
|
||||
console.log("\n");
|
||||
console.log("Finished in " + (context.runtime / 1000) + " seconds");
|
||||
var color = context.failed > 0 ? "red" : "green";
|
||||
console.log(ANSI.colorize(context.total + " examples, " + context.failed + " failures", color));
|
||||
|
||||
// we're done
|
||||
window.qunitResult = context;
|
||||
});
|
||||
|
||||
};
|
|
@ -1,308 +0,0 @@
|
|||
require "drb/drb"
|
||||
require "thread"
|
||||
require "fileutils"
|
||||
require "autospec/reload_css"
|
||||
require "autospec/base_runner"
|
||||
require "autospec/simple_runner"
|
||||
require "autospec/spork_runner"
|
||||
|
||||
module Autospec; end
|
||||
|
||||
class Autospec::Runner
|
||||
MATCHERS = {}
|
||||
def self.watch(pattern, &blk)
|
||||
MATCHERS[pattern] = blk
|
||||
end
|
||||
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/components/#{m[1]}_spec.rb" }
|
||||
|
||||
# Rails example
|
||||
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
||||
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
||||
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb" }
|
||||
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
||||
watch("app/controllers/application_controller.rb") { "spec/controllers" }
|
||||
|
||||
# Capybara request specs
|
||||
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
||||
|
||||
# Fabrication
|
||||
watch(%r{^spec/fabricators/(.+)_fabricator\.rb$}) { "spec" }
|
||||
|
||||
RELOAD_MATCHERS = Set.new
|
||||
def self.watch_reload(pattern)
|
||||
RELOAD_MATCHERS << pattern
|
||||
end
|
||||
|
||||
watch_reload('spec/spec_helper.rb')
|
||||
watch_reload('config/(.*).rb')
|
||||
watch_reload(%r{app/helpers/(.*).rb})
|
||||
|
||||
def self.run(opts={})
|
||||
self.new.run(opts)
|
||||
end
|
||||
|
||||
def initialize
|
||||
@queue = []
|
||||
@mutex = Mutex.new
|
||||
@signal = ConditionVariable.new
|
||||
start_service_queue
|
||||
end
|
||||
|
||||
def run(opts = {})
|
||||
|
||||
puts "Forced polling (slower) - inotify does not work on network filesystems, use local filesystem to avoid" if opts[:force_polling]
|
||||
|
||||
if ENV["SPORK"] == "0"
|
||||
puts "Using Simple Runner"
|
||||
@runner = Autospec::SimpleRunner.new
|
||||
else
|
||||
puts "Using Spork Runner"
|
||||
@runner = Autospec::SporkRunner.new
|
||||
end
|
||||
@runner.start
|
||||
|
||||
Signal.trap("HUP") {@runner.stop; exit }
|
||||
Signal.trap("SIGINT") {@runner.stop; exit }
|
||||
|
||||
options = {filter: /^app|^spec|^lib/, relative_paths: true}
|
||||
|
||||
if opts[:force_polling]
|
||||
options[:force_polling] = true
|
||||
options[:latency] = opts[:latency] || 3
|
||||
end
|
||||
|
||||
Thread.start do
|
||||
Listen.to('.', options ) do |modified, added, removed|
|
||||
process_change([modified, added].flatten.compact)
|
||||
end
|
||||
end
|
||||
|
||||
@mutex.synchronize do
|
||||
@queue << ['spec', 'spec']
|
||||
@signal.signal
|
||||
end
|
||||
|
||||
while @runner.running?
|
||||
process_queue
|
||||
end
|
||||
|
||||
rescue => e
|
||||
puts e
|
||||
puts e.backtrace
|
||||
@runner.stop
|
||||
end
|
||||
|
||||
def process_queue
|
||||
STDIN.gets
|
||||
|
||||
if @queue.length == 0
|
||||
@queue << ['spec', 'spec']
|
||||
@signal.signal
|
||||
else
|
||||
specs = failed_specs(:delete => false)
|
||||
puts
|
||||
puts
|
||||
if specs.length == 0
|
||||
puts "No specs have failed yet!"
|
||||
puts
|
||||
else
|
||||
puts "The following specs have failed: "
|
||||
specs.each do |s|
|
||||
puts s
|
||||
end
|
||||
puts
|
||||
queue_specs(specs.zip specs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def wait_for(timeout_milliseconds)
|
||||
timeout = (timeout_milliseconds + 0.0) / 1000
|
||||
finish = Time.now + timeout
|
||||
t = Thread.new do
|
||||
while Time.now < finish && !yield
|
||||
sleep(0.001)
|
||||
end
|
||||
end
|
||||
t.join rescue nil
|
||||
end
|
||||
|
||||
def force_polling?
|
||||
works = false
|
||||
|
||||
begin
|
||||
require 'rb-inotify'
|
||||
require 'fileutils'
|
||||
n = INotify::Notifier.new
|
||||
FileUtils.touch('./tmp/test_polling')
|
||||
|
||||
n.watch("./tmp", :modify, :attrib){ works = true }
|
||||
quit = false
|
||||
Thread.new do
|
||||
while !works && !quit
|
||||
if IO.select([n.to_io], [], [], 0.1)
|
||||
n.process
|
||||
end
|
||||
end
|
||||
end
|
||||
sleep 0.01
|
||||
|
||||
FileUtils.touch('./tmp/test_polling')
|
||||
wait_for(100) { works }
|
||||
File.unlink('./tmp/test_polling')
|
||||
n.stop
|
||||
quit = true
|
||||
rescue LoadError
|
||||
#assume it works (mac)
|
||||
works = true
|
||||
end
|
||||
|
||||
!works
|
||||
end
|
||||
|
||||
|
||||
def process_change(files)
|
||||
return unless files.length > 0
|
||||
|
||||
specs = []
|
||||
hit = false
|
||||
files.each do |file|
|
||||
RELOAD_MATCHERS.each do |k|
|
||||
if k.match(file)
|
||||
@runner.reload
|
||||
return
|
||||
end
|
||||
end
|
||||
MATCHERS.each do |k,v|
|
||||
if m = k.match(file)
|
||||
hit = true
|
||||
spec = v ? ( v.arity == 1 ? v.call(m) : v.call ) : file
|
||||
if File.exists?(spec) || Dir.exists?(spec)
|
||||
specs << [file, spec]
|
||||
end
|
||||
end
|
||||
end
|
||||
Autospec::ReloadCss::MATCHERS.each do |k,v|
|
||||
matches = []
|
||||
if k.match(file)
|
||||
matches << file
|
||||
end
|
||||
Autospec::ReloadCss.run_on_change(matches) if matches.present?
|
||||
end
|
||||
end
|
||||
queue_specs(specs) if hit
|
||||
rescue => e
|
||||
p "failed in watcher"
|
||||
p e
|
||||
p e.backtrace
|
||||
end
|
||||
|
||||
def queue_specs(specs)
|
||||
if specs.length == 0
|
||||
locked = @mutex.try_lock
|
||||
if locked
|
||||
@signal.signal
|
||||
@mutex.unlock
|
||||
end
|
||||
return
|
||||
else
|
||||
@runner.abort
|
||||
end
|
||||
|
||||
@mutex.synchronize do
|
||||
specs.each do |c,spec|
|
||||
@queue.delete([c,spec])
|
||||
if @queue.last && @queue.last[0] == "focus"
|
||||
focus = @queue.pop
|
||||
@queue << [c,spec]
|
||||
if focus[1].include?(spec) || c != spec
|
||||
@queue << focus
|
||||
end
|
||||
else
|
||||
@queue << [c,spec]
|
||||
end
|
||||
end
|
||||
@signal.signal
|
||||
end
|
||||
end
|
||||
|
||||
def thread_loop
|
||||
@mutex.synchronize do
|
||||
last_failed = false
|
||||
current = @queue.last
|
||||
if current
|
||||
last_failed = process_spec(current[1])
|
||||
end
|
||||
wait = @queue.length == 0 || last_failed
|
||||
@signal.wait(@mutex) if wait
|
||||
end
|
||||
rescue => e
|
||||
p "DISASTA PASTA"
|
||||
puts e
|
||||
puts e.backtrace
|
||||
end
|
||||
|
||||
def process_spec(spec)
|
||||
last_failed = false
|
||||
result = run_spec(spec)
|
||||
if result == 0
|
||||
@queue.pop
|
||||
else
|
||||
last_failed = true
|
||||
if result.to_i > 0
|
||||
focus_on_failed_tests
|
||||
ensure_all_specs_will_run
|
||||
end
|
||||
end
|
||||
|
||||
last_failed
|
||||
end
|
||||
|
||||
def start_service_queue
|
||||
@worker ||= Thread.new do
|
||||
while true
|
||||
thread_loop
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def focus_on_failed_tests
|
||||
current = @queue.last
|
||||
specs = failed_specs[0..10]
|
||||
if current[0] == "focus"
|
||||
@queue.pop
|
||||
end
|
||||
@queue << ["focus", specs.join(" ")]
|
||||
end
|
||||
|
||||
def ensure_all_specs_will_run
|
||||
unless @queue.any?{|s,t| t == 'spec'}
|
||||
@queue.unshift(['spec','spec'])
|
||||
end
|
||||
end
|
||||
|
||||
def failed_specs(opts={:delete => true})
|
||||
specs = []
|
||||
path = './tmp/rspec_result'
|
||||
if File.exist?(path)
|
||||
specs = File.open(path) { |file| file.read.split("\n") }
|
||||
File.delete(path) if opts[:delete]
|
||||
end
|
||||
|
||||
specs
|
||||
end
|
||||
|
||||
def run_spec(specs)
|
||||
File.delete("tmp/rspec_result") if File.exists?("tmp/rspec_result")
|
||||
args = ["-f", "progress", specs.split(" "),
|
||||
"-r", "#{File.dirname(__FILE__)}/formatter.rb",
|
||||
"-f", "Autospec::Formatter"].flatten
|
||||
|
||||
@runner.run(args, specs)
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
|
@ -1,26 +1,36 @@
|
|||
require "autospec/rspec_runner"
|
||||
|
||||
module Autospec
|
||||
class SimpleRunner < BaseRunner
|
||||
|
||||
class SimpleRunner < RspecRunner
|
||||
|
||||
def run(specs)
|
||||
puts "Running Rspec: " << specs
|
||||
# kill previous rspec instance
|
||||
abort
|
||||
# we use our custom rspec formatter
|
||||
args = ["-r", "#{File.dirname(__FILE__)}/formatter.rb",
|
||||
"-f", "Autospec::Formatter", specs.split].flatten.join(" ")
|
||||
# launch rspec
|
||||
@pid = Process.spawn({"RAILS_ENV" => "test"}, "bundle exec rspec #{args}")
|
||||
_, status = Process.wait2(@pid)
|
||||
status.exitstatus
|
||||
end
|
||||
|
||||
def abort
|
||||
if @pid
|
||||
Process.kill("SIGINT", @pid) rescue nil
|
||||
while(Process.getpgid(@pid) rescue nil)
|
||||
Process.kill("INT", @pid) rescue nil
|
||||
while (Process.getpgid(@pid) rescue nil)
|
||||
sleep 0.001
|
||||
end
|
||||
@pid = nil
|
||||
end
|
||||
end
|
||||
|
||||
def run(args, spec)
|
||||
self.abort
|
||||
puts "Running: " << spec
|
||||
@pid = Process.spawn({"RAILS_ENV" => "test"}, "bundle exec rspec " << args.join(" "))
|
||||
pid, status = Process.wait2(@pid)
|
||||
status
|
||||
def stop
|
||||
abort
|
||||
end
|
||||
|
||||
def stop
|
||||
self.abort
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
require "drb/drb"
|
||||
require "autospec/rspec_runner"
|
||||
|
||||
module Autospec
|
||||
class SporkRunner < BaseRunner
|
||||
|
||||
class SporkRunner < RspecRunner
|
||||
|
||||
def start
|
||||
if already_running?(pid_file)
|
||||
|
@ -13,33 +17,38 @@ module Autospec
|
|||
end
|
||||
|
||||
def running?
|
||||
# launch a thread that will wait for spork to die
|
||||
@monitor_thread ||=
|
||||
Thread.new do
|
||||
Process.wait(@spork_pid)
|
||||
@spork_running = false
|
||||
end
|
||||
|
||||
@spork_running
|
||||
end
|
||||
|
||||
def stop
|
||||
stop_spork
|
||||
end
|
||||
|
||||
def run(args,specs)
|
||||
def run(specs)
|
||||
args = ["-r", "#{File.dirname(__FILE__)}/formatter.rb",
|
||||
"-f", "Autospec::Formatter", specs.split].flatten
|
||||
spork_service.run(args,$stderr,$stdout)
|
||||
end
|
||||
|
||||
def abort
|
||||
spork_service.abort
|
||||
end
|
||||
|
||||
def reload
|
||||
stop_spork
|
||||
sleep 1
|
||||
start_spork
|
||||
end
|
||||
|
||||
def abort
|
||||
spork_service.abort
|
||||
end
|
||||
|
||||
def stop
|
||||
stop_spork
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def spork_pid_file
|
||||
Rails.root + "tmp/pids/spork.pid"
|
||||
end
|
||||
|
@ -55,7 +64,7 @@ module Autospec
|
|||
end
|
||||
end
|
||||
|
||||
def write_pid_file(file,pid)
|
||||
def write_pid_file(file, pid)
|
||||
FileUtils.mkdir_p(Rails.root + "tmp/pids")
|
||||
File.open(file,'w') do |f|
|
||||
f.write(pid)
|
||||
|
@ -67,25 +76,18 @@ module Autospec
|
|||
end
|
||||
|
||||
def spork_service
|
||||
|
||||
unless @drb_listener_running
|
||||
begin
|
||||
DRb.start_service("druby://127.0.0.1:0")
|
||||
rescue SocketError, Errno::EADDRNOTAVAIL
|
||||
DRb.start_service("druby://:0")
|
||||
end
|
||||
|
||||
@drb_listener_running = true
|
||||
end
|
||||
|
||||
@spork_service ||= DRbObject.new_with_uri("druby://127.0.0.1:8989")
|
||||
end
|
||||
|
||||
def stop_spork
|
||||
pid = File.read(spork_pid_file).to_i
|
||||
Process.kill("SIGTERM",pid)
|
||||
end
|
||||
|
||||
def start_spork
|
||||
if already_running?(spork_pid_file)
|
||||
puts "Killing old orphan spork instance"
|
||||
|
@ -101,7 +103,13 @@ module Autospec
|
|||
running = spork_running?
|
||||
sleep 0.01
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def stop_spork
|
||||
pid = File.read(spork_pid_file).to_i
|
||||
Process.kill("SIGTERM", pid) rescue nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
142
lib/demon/base.rb
Normal file
142
lib/demon/base.rb
Normal file
|
@ -0,0 +1,142 @@
|
|||
module Demon; end
|
||||
|
||||
# intelligent fork based demonizer
|
||||
class Demon::Base
|
||||
|
||||
def self.start(count)
|
||||
@demons ||= {}
|
||||
count.times do |i|
|
||||
(@demons["#{prefix}_#{i}"] ||= new(i)).start
|
||||
end
|
||||
end
|
||||
|
||||
def self.stop
|
||||
return unless @demons
|
||||
@demons.values.each do |demon|
|
||||
demon.stop
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(index)
|
||||
@index = index
|
||||
@pid = nil
|
||||
@parent_pid = Process.pid
|
||||
@monitor = nil
|
||||
end
|
||||
|
||||
def pid_file
|
||||
"#{Rails.root}/tmp/pids/#{self.class.prefix}_#{@index}.pid"
|
||||
end
|
||||
|
||||
def stop
|
||||
if @monitor
|
||||
@monitor.kill
|
||||
@monitor.join
|
||||
@monitor = nil
|
||||
end
|
||||
|
||||
if @pid
|
||||
Process.kill("HUP",@pid)
|
||||
@pid = nil
|
||||
end
|
||||
end
|
||||
|
||||
def start
|
||||
if existing = already_running?
|
||||
# should not happen ... so kill violently
|
||||
Process.kill("TERM",existing)
|
||||
end
|
||||
|
||||
return if @pid
|
||||
|
||||
if @pid = fork
|
||||
write_pid_file
|
||||
monitor_child
|
||||
return
|
||||
end
|
||||
|
||||
monitor_parent
|
||||
establish_app
|
||||
after_fork
|
||||
end
|
||||
|
||||
def already_running?
|
||||
if File.exists? pid_file
|
||||
pid = File.read(pid_file).to_i
|
||||
if alive?(pid)
|
||||
return pid
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def monitor_child
|
||||
@monitor ||= Thread.new do
|
||||
while true
|
||||
sleep 5
|
||||
unless alive?(@pid)
|
||||
STDERR.puts "#{@pid} died, restarting the process"
|
||||
@pid = nil
|
||||
start
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_pid_file
|
||||
FileUtils.mkdir_p(Rails.root + "tmp/pids")
|
||||
File.open(pid_file,'w') do |f|
|
||||
f.write(@pid)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_pid_file
|
||||
File.delete(pid_file)
|
||||
end
|
||||
|
||||
def monitor_parent
|
||||
Thread.new do
|
||||
while true
|
||||
unless alive?(@parent_pid)
|
||||
Process.kill "QUIT", Process.pid
|
||||
end
|
||||
sleep 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def alive?(pid)
|
||||
begin
|
||||
Process.getpgid(pid)
|
||||
true
|
||||
rescue Errno::ESRCH
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def establish_app
|
||||
ActiveRecord::Base.connection_handler.clear_active_connections!
|
||||
ActiveRecord::Base.establish_connection
|
||||
$redis.client.reconnect
|
||||
Rails.cache.reconnect
|
||||
MessageBus.after_fork
|
||||
|
||||
Signal.trap("HUP") do
|
||||
begin
|
||||
delete_pid_file
|
||||
ensure
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
# keep stuff simple for now
|
||||
$stdout.reopen("/dev/null", "w")
|
||||
$stderr.reopen("/dev/null", "w")
|
||||
end
|
||||
|
||||
def after_fork
|
||||
end
|
||||
end
|
25
lib/demon/rails_autospec.rb
Normal file
25
lib/demon/rails_autospec.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
require "demon/base"
|
||||
|
||||
class Demon::RailsAutospec < Demon::Base
|
||||
|
||||
def self.prefix
|
||||
"rails-autospec"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def after_fork
|
||||
require "rack"
|
||||
ENV["RAILS_ENV"] = "test"
|
||||
Rack::Server.start(
|
||||
:config => "config.ru",
|
||||
:AccessLog => [],
|
||||
:Port => ENV["TEST_SERVER_PORT"] || 60099,
|
||||
)
|
||||
rescue => e
|
||||
STDERR.puts e.message
|
||||
STDERR.puts e.backtrace.join("\n")
|
||||
exit 1
|
||||
end
|
||||
|
||||
end
|
|
@ -1,148 +1,7 @@
|
|||
module Demon; end
|
||||
|
||||
# intelligent fork based demonizer for sidekiq
|
||||
class Demon::Base
|
||||
|
||||
def self.start(count)
|
||||
@demons ||= {}
|
||||
count.times do |i|
|
||||
(@demons["#{prefix}_#{i}"] ||= new(i)).start
|
||||
end
|
||||
end
|
||||
|
||||
def self.stop
|
||||
@demons.values.each do |demon|
|
||||
demon.stop
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(index)
|
||||
@index = index
|
||||
@pid = nil
|
||||
@parent_pid = Process.pid
|
||||
@monitor = nil
|
||||
end
|
||||
|
||||
def pid_file
|
||||
"#{Rails.root}/tmp/pids/#{self.class.prefix}_#{@index}.pid"
|
||||
end
|
||||
|
||||
def stop
|
||||
if @monitor
|
||||
@monitor.kill
|
||||
@monitor.join
|
||||
@monitor = nil
|
||||
end
|
||||
|
||||
if @pid
|
||||
Process.kill("SIGHUP",@pid)
|
||||
@pid = nil
|
||||
end
|
||||
end
|
||||
|
||||
def start
|
||||
if existing = already_running?
|
||||
# should not happen ... so kill violently
|
||||
Process.kill("SIGTERM",existing)
|
||||
end
|
||||
|
||||
return if @pid
|
||||
|
||||
if @pid = fork
|
||||
write_pid_file
|
||||
monitor_child
|
||||
return
|
||||
end
|
||||
|
||||
monitor_parent
|
||||
establish_app
|
||||
after_fork
|
||||
end
|
||||
|
||||
def already_running?
|
||||
if File.exists? pid_file
|
||||
pid = File.read(pid_file).to_i
|
||||
if alive?(pid)
|
||||
return pid
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def monitor_child
|
||||
@monitor ||= Thread.new do
|
||||
while true
|
||||
sleep 5
|
||||
unless alive?(@pid)
|
||||
STDERR.puts "#{@pid} died, restarting sidekiq"
|
||||
@pid = nil
|
||||
start
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_pid_file
|
||||
FileUtils.mkdir_p(Rails.root + "tmp/pids")
|
||||
File.open(pid_file,'w') do |f|
|
||||
f.write(@pid)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_pid_file
|
||||
File.delete(pid_file)
|
||||
end
|
||||
|
||||
def monitor_parent
|
||||
Thread.new do
|
||||
while true
|
||||
unless alive?(@parent_pid)
|
||||
Process.kill "QUIT", Process.pid
|
||||
end
|
||||
sleep 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def alive?(pid)
|
||||
begin
|
||||
Process.getpgid(pid)
|
||||
true
|
||||
rescue Errno::ESRCH
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def establish_app
|
||||
|
||||
|
||||
ActiveRecord::Base.connection_handler.clear_active_connections!
|
||||
ActiveRecord::Base.establish_connection
|
||||
$redis.client.reconnect
|
||||
Rails.cache.reconnect
|
||||
MessageBus.after_fork
|
||||
|
||||
Signal.trap("HUP") do
|
||||
begin
|
||||
delete_pid_file
|
||||
ensure
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
# keep stuff simple for now
|
||||
$stdout.reopen("/dev/null", "w")
|
||||
# $stderr.reopen("/dev/null", "w")
|
||||
end
|
||||
|
||||
def after_fork
|
||||
end
|
||||
end
|
||||
require "demon/base"
|
||||
|
||||
class Demon::Sidekiq < Demon::Base
|
||||
|
||||
def self.prefix
|
||||
"sidekiq"
|
||||
end
|
||||
|
@ -151,18 +10,15 @@ class Demon::Sidekiq < Demon::Base
|
|||
|
||||
def after_fork
|
||||
require 'sidekiq/cli'
|
||||
begin
|
||||
# Reload initializer cause it needs to run after sidekiq/cli
|
||||
# was required
|
||||
load Rails.root + "config/initializers/sidekiq.rb"
|
||||
cli = Sidekiq::CLI.instance
|
||||
cli.parse([])
|
||||
cli.run
|
||||
rescue => e
|
||||
STDERR.puts e.message
|
||||
STDERR.puts e.backtrace.join("\n")
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Reload initializer cause it needs to run after sidekiq/cli was required
|
||||
load Rails.root + "config/initializers/sidekiq.rb"
|
||||
cli = Sidekiq::CLI.instance
|
||||
cli.parse([])
|
||||
cli.run
|
||||
rescue => e
|
||||
STDERR.puts e.message
|
||||
STDERR.puts e.backtrace.join("\n")
|
||||
exit 1
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -4,22 +4,17 @@
|
|||
|
||||
desc "Run all specs automatically as needed"
|
||||
task "autospec" => :environment do
|
||||
require 'autospec/manager'
|
||||
|
||||
if RUBY_PLATFORM.include?('linux')
|
||||
require 'rb-inotify'
|
||||
end
|
||||
|
||||
require 'listen'
|
||||
|
||||
puts "If file watching is not working you can force polling with: bundle exec rake autospec p l=3"
|
||||
require 'autospec/runner'
|
||||
|
||||
force_polling = ARGV.any?{|a| a == "p" || a == "polling"}
|
||||
latency = ((ARGV.find{|a| a =~ /l=|latency=/}||"").split("=")[1] || 3).to_i
|
||||
force_polling = ARGV.any?{ |a| a == "p" || a == "polling" }
|
||||
latency = ((ARGV.find{ |a| a =~ /l=|latency=/ } || "").split("=")[1] || 3).to_i
|
||||
|
||||
if force_polling
|
||||
puts "polling has been forced (slower) checking every #{latency} #{"second".pluralize(latency)}"
|
||||
puts "Polling has been forced (slower) - checking every #{latency} #{"second".pluralize(latency)}"
|
||||
else
|
||||
puts "If file watching is not working, you can force polling with: bundle exec rake autospec p l=3"
|
||||
end
|
||||
|
||||
Autospec::Runner.run(force_polling: force_polling, latency: latency)
|
||||
Autospec::Manager.run(force_polling: force_polling, latency: latency)
|
||||
|
||||
end
|
||||
|
|
|
@ -28,7 +28,6 @@ describe UserSerializer do
|
|||
end
|
||||
|
||||
it "has a name" do
|
||||
puts json[:name]
|
||||
json[:name].should be_blank
|
||||
end
|
||||
end
|
||||
|
|
16
vendor/assets/javascripts/run-qunit.js
vendored
16
vendor/assets/javascripts/run-qunit.js
vendored
|
@ -19,16 +19,14 @@ page.onConsoleMessage = function(msg) {
|
|||
if (msg.slice(0,8) === 'WARNING:') { return; }
|
||||
if (msg.slice(0,6) === 'DEBUG:') { return; }
|
||||
|
||||
// Hack to access the print method
|
||||
// If there's a better way to do this, please change
|
||||
if (msg.slice(0,6) === 'PRINT:') {
|
||||
print(msg.slice(7));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(msg);
|
||||
};
|
||||
|
||||
page.onCallback = function (message) {
|
||||
// forward the message to the standard output
|
||||
system.stdout.write(message);
|
||||
};
|
||||
|
||||
page.open(args[0], function(status) {
|
||||
if (status !== 'success') {
|
||||
console.error("Unable to access network");
|
||||
|
@ -80,9 +78,9 @@ function logQUnit() {
|
|||
var msg = " Test Failed: " + context.name + assertionErrors.join(" ");
|
||||
testErrors.push(msg);
|
||||
assertionErrors = [];
|
||||
console.log('PRINT: F');
|
||||
window.callPhantom('F');
|
||||
} else {
|
||||
console.log('PRINT: .');
|
||||
window.callPhantom('.');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user