diff --git a/lib/retest.rb b/lib/retest.rb index 91fb3b8..e2576d5 100644 --- a/lib/retest.rb +++ b/lib/retest.rb @@ -1,5 +1,3 @@ -require 'listen' - require 'string/similarity' require 'observer' @@ -17,8 +15,6 @@ require "retest/sounds" require "retest/watcher" -Listen.adapter_warn_behavior = :log - module Retest class Error < StandardError; end class FileNotFound < StandardError; end diff --git a/lib/retest/runner.rb b/lib/retest/runner.rb index 1fa186e..e02c6f9 100644 --- a/lib/retest/runner.rb +++ b/lib/retest/runner.rb @@ -1,3 +1,4 @@ +require 'forwardable' require_relative "runner/cached_test_file" module Retest diff --git a/lib/retest/watcher.rb b/lib/retest/watcher.rb index 80632e6..aebec7c 100644 --- a/lib/retest/watcher.rb +++ b/lib/retest/watcher.rb @@ -1,3 +1,5 @@ +require 'pathname' + module Retest module Watcher def self.for(watcher) @@ -24,25 +26,19 @@ def self.installed? true end + def self.extensions_regex(extensions) + Regexp.new("\\.(?:#{extensions.join("|")})$") + end + def self.watch(dir:, extensions:, polling: false) + command = "./lib/scripts/listen --exts #{extensions.join(',')} -w #{dir} --polling #{polling}" + + watch_rd, watch_wr = IO.pipe # Process needs its own process group otherwise the process gets killed on INT signal # We need the process to still run when trying to stop the current test run # Maybe there is another way to prevent killing these but for now a new process groups works - # Process group created with: Process.setsid - watch_rd, watch_wr = IO.pipe - pid = fork do - Process.setsid - Listen.to(dir, only: extensions_regex(extensions), relative: true, polling: polling) do |modified, added, removed| - if modified.any? - watch_wr.write "modify:#{modified.first}" - elsif added.any? - watch_wr.write "create:#{added.first}" - elsif removed.any? - watch_wr.write "remove:#{removed.first}" - end - end.start - sleep - end + # Process group created with: pgroup: true + pid = Process.spawn(command, out: watch_wr, pgroup: true) at_exit do Process.kill("TERM", pid) if pid @@ -59,12 +55,13 @@ def self.watch(dir:, extensions:, polling: false) change = /^(?create|remove|modify):(?.*)/.match(data.strip) next unless change + path = Pathname(change[:path]).relative_path_from(Dir.pwd).to_s modified, added, removed = result = [[], [], []] case change[:action] - when 'modify' then modified << change[:path] - when 'create' then added << change[:path] - when 'remove' then removed << change[:path] + when 'modify' then modified << path + when 'create' then added << path + when 'remove' then removed << path end yield result @@ -72,10 +69,6 @@ def self.watch(dir:, extensions:, polling: false) end end end - - def self.extensions_regex(extensions) - Regexp.new("\\.(?:#{extensions.join("|")})$") - end end module Watchexec @@ -85,7 +78,6 @@ def self.installed? def self.watch(dir:, extensions:, polling: false) command = "watchexec --exts #{extensions.join(',')} -w #{dir} --emit-events-to stdio --no-meta --only-emit-events" - files = VersionControl.files(extensions: extensions).zip([]).to_h watch_rd, watch_wr = IO.pipe # Process needs its own process group otherwise the process gets killed on INT signal @@ -101,11 +93,15 @@ def self.watch(dir:, extensions:, polling: false) end Thread.new do + files = VersionControl.files(extensions: extensions).zip([]).to_h + loop do ready = IO.select([watch_rd]) readable_connections = ready[0] readable_connections.each do |conn| data = conn.readpartial(4096) + # Watchexec is not great at figuring out whether a file has been deleted and comes as an update. + # This is why we're not looking at the action like we do with Listen. change = /^(?:create|remove|rename|modify):(?.*)/.match(data.strip) next unless change @@ -129,41 +125,6 @@ def self.watch(dir:, extensions:, polling: false) end end end - - # require 'open3' - # Thread.new do - # files = VersionControl.files(extensions: extensions).zip([]).to_h - - # Open3.popen3(command) do |stdin, stdout, stderr, wait_thr| - # loop do - # ready = IO.select([stdout]) - # readable_connections = ready[0] - # readable_connections.each do |conn| - # data = conn.readpartial(4096) - # change = /^(?:create|remove|rename|modify):(?.*)/.match(data.strip) - - # next unless change - - # path = Pathname(change[:path]).relative_path_from(Dir.pwd).to_s - # file_exist = File.exist?(path) - # file_cached = files.key?(path) - - # modified, added, removed = result = [[], [], []] - # if file_exist && file_cached - # modified << path - # elsif file_exist && !file_cached - # added << path - # files[path] = nil - # elsif !file_exist && file_cached - # removed << path - # files.delete(path) - # end - - # yield result - # end - # end - # end - # end end end end diff --git a/lib/scripts/listen b/lib/scripts/listen new file mode 100755 index 0000000..15224fb --- /dev/null +++ b/lib/scripts/listen @@ -0,0 +1,57 @@ +#!/usr/bin/env ruby + +$stdout.sync = true + +require 'listen' +require 'optparse' + +options = {} +OptionParser.new do |opts| + opts.banner = "Usage: scripts/listen.rb [options]" + + opts.on("--exts rb,js,ts", Array, "Extensions to watch for") do |list| + options[:extensions] = list + end + + opts.on("--polling BOOLEAN", "Force Listen to use polling") do |value| + options[:polling] = value + end + + opts.on("-w", "--watch .", "Directory to listen to") do |value| + options[:dir] = value + end + + opts.on("-h", "--help", "Prints help") do + puts opts + exit + end +end.parse! + +unless options.key?(:extensions) + raise ArgumentError, 'must provide the files extensions to watch for' +end + +unless options.key?(:polling) + raise ArgumentError, 'must provide the polling option' +end + +unless options.key?(:dir) + raise ArgumentError, 'must provide the directory path to watch' +end + +def extensions_regex(extensions) + Regexp.new("\\.(?:#{extensions.join("|")})$") +end + +Listen.adapter_warn_behavior = :log + +Listen.to(options[:dir], only: extensions_regex(options[:extensions]), polling: options[:polling]) do |modified, added, removed| + if modified.any? + $stdout.puts "modify:#{modified.first}" + elsif added.any? + $stdout.puts "create:#{added.first}" + elsif removed.any? + $stdout.puts "remove:#{removed.first}" + end +end.start +sleep