mirror of
https://github.com/discourse/discourse.git
synced 2024-11-27 11:53:44 +08:00
59097b207f
Over the years we accrued many spelling mistakes in the code base. This PR attempts to fix spelling mistakes and typos in all areas of the code that are extremely safe to change - comments - test descriptions - other low risk areas
389 lines
10 KiB
Ruby
389 lines
10 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "listen"
|
|
require "thread"
|
|
require "fileutils"
|
|
require "autospec/reload_css"
|
|
require "autospec/base_runner"
|
|
require "socket_server"
|
|
|
|
module Autospec; end
|
|
|
|
class Autospec::Manager
|
|
|
|
def self.run(opts = {})
|
|
self.new(opts).run
|
|
end
|
|
|
|
def initialize(opts = {})
|
|
@opts = opts
|
|
@debug = opts[:debug]
|
|
@auto_run_all = ENV["AUTO_RUN_ALL"] != "0"
|
|
@queue = []
|
|
@mutex = Mutex.new
|
|
@signal = ConditionVariable.new
|
|
@runners = [ruby_runner]
|
|
|
|
if ENV["QUNIT"] == "1"
|
|
@runners << javascript_runner
|
|
else
|
|
puts "Skipping JS tests, run them in the browser at /qunit or add QUNIT=1 to env"
|
|
end
|
|
end
|
|
|
|
def run
|
|
Signal.trap("HUP") { stop_runners; exit }
|
|
|
|
Signal.trap("INT") do
|
|
begin
|
|
stop_runners
|
|
rescue => e
|
|
puts "FAILED TO STOP RUNNERS #{e}"
|
|
end
|
|
exit
|
|
end
|
|
|
|
ensure_all_specs_will_run if @auto_run_all
|
|
start_runners
|
|
start_service_queue
|
|
listen_for_changes
|
|
|
|
puts "Press [ENTER] to stop the current run"
|
|
puts "Press [ENTER] while stopped to run all specs" unless @auto_run_all
|
|
while @runners.any?(&:running?)
|
|
STDIN.gets
|
|
process_queue
|
|
end
|
|
|
|
rescue => e
|
|
fail(e, "failed in run")
|
|
ensure
|
|
stop_runners
|
|
end
|
|
|
|
private
|
|
|
|
def ruby_runner
|
|
require "autospec/simple_runner"
|
|
Autospec::SimpleRunner.new
|
|
end
|
|
|
|
def javascript_runner
|
|
require "autospec/qunit_runner"
|
|
Autospec::QunitRunner.new
|
|
end
|
|
|
|
def ensure_all_specs_will_run(current_runner = nil)
|
|
puts "@@@@@@@@@@@@ ensure_all_specs_will_run" if @debug
|
|
|
|
@queue.reject! { |_, s, _| s == "spec" }
|
|
|
|
if current_runner
|
|
@queue.concat [['spec', 'spec', current_runner]]
|
|
end
|
|
|
|
@runners.each do |runner|
|
|
@queue.concat [['spec', 'spec', runner]] unless @queue.any? { |_, s, r| s == "spec" && r == runner }
|
|
end
|
|
end
|
|
|
|
[:start, :stop, :abort].each do |verb|
|
|
define_method("#{verb}_runners") do
|
|
puts "@@@@@@@@@@@@ #{verb}_runners" if @debug
|
|
@runners.each(&verb)
|
|
end
|
|
end
|
|
|
|
def start_service_queue
|
|
puts "@@@@@@@@@@@@ start_service_queue" if @debug
|
|
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
|
|
puts "@@@@@@@@@@@@ thread_loop" if @debug
|
|
@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
|
|
if @debug
|
|
puts "@@@@@@@@@@@@ waiting because..."
|
|
puts "@@@@@@@@@@@@ ...current spec has failed" if last_failed
|
|
puts "@@@@@@@@@@@@ ...queue is empty" if @queue.length == 0
|
|
end
|
|
@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)
|
|
puts "@@@@@@@@@@@@ process_spec --> #{current}" if @debug
|
|
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
|
|
puts "@@@@@@@@@@@@ success" if @debug
|
|
# remove the spec from the queue
|
|
@queue.shift
|
|
else
|
|
puts "@@@@@@@@@@@@ failure" if @debug
|
|
has_failed = true
|
|
if result > 0
|
|
focus_on_failed_tests(current)
|
|
ensure_all_specs_will_run(runner) if @auto_run_all
|
|
end
|
|
end
|
|
|
|
has_failed
|
|
end
|
|
|
|
def focus_on_failed_tests(current)
|
|
puts "@@@@@@@@@@@@ focus_on_failed_tests --> #{current}" if @debug
|
|
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]
|
|
puts "@@@@@@@@@@@@ failed_specs --> #{failed_specs}" if @debug
|
|
|
|
# try focus tag
|
|
if failed_specs.length > 0
|
|
filename, _ = failed_specs[0].split(":")
|
|
if filename && File.exist?(filename) && !File.directory?(filename)
|
|
spec = File.read(filename)
|
|
start, _ = spec.split(/\S*#focus\S*$/)
|
|
if start.length < spec.length
|
|
line = start.scan(/\n/).length + 1
|
|
puts "Found #focus tag on line #{line}!"
|
|
failed_specs = ["#{filename}:#{line + 1}"]
|
|
end
|
|
end
|
|
end
|
|
|
|
# focus on the failed specs
|
|
@queue.unshift ["focus", failed_specs.join(" "), runner] if failed_specs.length > 0
|
|
end
|
|
|
|
def root_path
|
|
root_path ||= File.expand_path(File.dirname(__FILE__) + "../../..")
|
|
end
|
|
|
|
def reverse_symlink_map
|
|
map = {}
|
|
Dir[root_path + "/plugins/*"].each do |f|
|
|
next if !File.directory? f
|
|
resolved = File.realpath(f)
|
|
if resolved != f
|
|
map[resolved] = f
|
|
end
|
|
end
|
|
map
|
|
end
|
|
|
|
# plugins can be symlinked, try to figure out which plugin this is
|
|
def reverse_symlink(file)
|
|
resolved = file
|
|
@reverse_map ||= reverse_symlink_map
|
|
@reverse_map.each do |location, discourse_location|
|
|
if file.start_with?(location)
|
|
resolved = discourse_location + file[location.length..-1]
|
|
end
|
|
end
|
|
|
|
resolved
|
|
end
|
|
|
|
def listen_for_changes
|
|
puts "@@@@@@@@@@@@ listen_for_changes" if @debug
|
|
|
|
options = {
|
|
ignore: /^lib\/autospec/,
|
|
}
|
|
|
|
if @opts[:force_polling]
|
|
options[:force_polling] = true
|
|
options[:latency] = @opts[:latency] || 3
|
|
end
|
|
|
|
path = root_path
|
|
|
|
if ENV['VIM_AUTOSPEC']
|
|
STDERR.puts "Using VIM file listener"
|
|
|
|
socket_path = (Rails.root + "tmp/file_change.sock").to_s
|
|
FileUtils.rm_f(socket_path)
|
|
server = SocketServer.new(socket_path)
|
|
server.start do |line|
|
|
file, line = line.split(' ')
|
|
file = reverse_symlink(file)
|
|
file = file.sub(Rails.root.to_s + "/", "")
|
|
# process_change can acquire a mutex and block
|
|
# the acceptor
|
|
Thread.new do
|
|
if file =~ /(es6|js)$/
|
|
process_change([[file]])
|
|
else
|
|
process_change([[file, line]])
|
|
end
|
|
end
|
|
"OK"
|
|
end
|
|
return
|
|
end
|
|
|
|
# to speed up boot we use a thread
|
|
["spec", "lib", "app", "config", "test", "vendor", "plugins"].each do |watch|
|
|
|
|
puts "@@@@@@@@@ Listen to #{path}/#{watch} #{options}" if @debug
|
|
Thread.new do
|
|
begin
|
|
listener = Listen.to("#{path}/#{watch}", options) do |modified, added, _|
|
|
paths = [modified, added].flatten
|
|
paths.compact!
|
|
paths.map! do |long|
|
|
long = reverse_symlink(long)
|
|
long[(path.length + 1)..-1]
|
|
end
|
|
process_change(paths)
|
|
end
|
|
listener.start
|
|
sleep
|
|
rescue => e
|
|
puts "FAILED to listen on changes to #{path}/#{watch}"
|
|
puts e
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
def process_change(files)
|
|
return if files.length == 0
|
|
|
|
puts "@@@@@@@@@@@@ process_change --> #{files}" if @debug
|
|
|
|
specs = []
|
|
hit = false
|
|
|
|
files.each do |file, line|
|
|
@runners.each do |runner|
|
|
# reloaders
|
|
runner.reloaders.each do |k|
|
|
if k.match(file)
|
|
puts "@@@@@@@@@@@@ #{file} matched a reloader for #{runner}" if @debug
|
|
runner.reload
|
|
return
|
|
end
|
|
end
|
|
# watchers
|
|
runner.watchers.each do |k, v|
|
|
if m = k.match(file)
|
|
puts "@@@@@@@@@@@@ #{file} matched a watcher for #{runner}" if @debug
|
|
hit = true
|
|
spec = v ? (v.arity == 1 ? v.call(m) : v.call) : file
|
|
with_line = spec
|
|
if spec == file && line
|
|
with_line = spec + ":" << line.to_s
|
|
end
|
|
if File.exists?(spec) || Dir.exists?(spec)
|
|
if with_line != spec
|
|
specs << [file, spec, runner]
|
|
end
|
|
specs << [file, with_line, runner]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
queue_specs(specs) if hit
|
|
rescue => e
|
|
fail(e, "failed in watcher")
|
|
end
|
|
|
|
def queue_specs(specs)
|
|
puts "@@@@@@@@@@@@ queue_specs --> #{specs}" if @debug
|
|
|
|
if specs.length == 0
|
|
locked = @mutex.try_lock
|
|
if locked
|
|
@signal.signal
|
|
@mutex.unlock
|
|
end
|
|
return
|
|
else
|
|
abort_runners
|
|
end
|
|
|
|
puts "@@@@@@@@@@@@ waiting for the mutex" if @debug
|
|
@mutex.synchronize do
|
|
puts "@@@@@@@@@@@@ queueing specs" if @debug
|
|
puts "@@@@@@@@@@@@ #{@queue}" if @debug
|
|
specs.each do |file, spec, runner|
|
|
# make sure there's no other instance of this spec in the queue
|
|
@queue.delete_if { |_, s, r| s.strip.start_with?(spec.strip) && r == runner }
|
|
# deal with focused specs
|
|
if @queue.first && @queue.first[0] == "focus"
|
|
focus = @queue.shift
|
|
@queue.unshift([file, spec, runner])
|
|
unless spec.include?(":") && focus[1].include?(spec.split(":")[0])
|
|
if focus[1].include?(spec) || file != spec
|
|
@queue.unshift(focus)
|
|
end
|
|
end
|
|
else
|
|
@queue.unshift([file, spec, runner])
|
|
end
|
|
|
|
# push run all specs to end of queue in correct order
|
|
ensure_all_specs_will_run(runner) if @auto_run_all
|
|
end
|
|
puts "@@@@@@@@@@@@ specs queued" if @debug
|
|
puts "@@@@@@@@@@@@ #{@queue}" if @debug
|
|
@signal.signal
|
|
end
|
|
end
|
|
|
|
def process_queue
|
|
puts "@@@@@@@@@@@@ process_queue" if @debug
|
|
if @queue.length == 0
|
|
puts "@@@@@@@@@@@@ queue is empty..." if @debug
|
|
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! Aborting anyway"
|
|
puts
|
|
abort_runners
|
|
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
|