Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Watchexec #229

Merged
merged 2 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ DEPENDENCIES
retest!

BUNDLED WITH
2.4.21
2.3.27
21 changes: 14 additions & 7 deletions exe/retest
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require 'retest'
$stdout.sync = true
listen_rd, listen_wr = IO.pipe
Signal.trap(:INT) do
$stdout.puts "Goodbye"
puts "Goodbye"
listen_rd.close
listen_wr.close
exit
Expand All @@ -14,12 +14,12 @@ end
options = Retest::Options.new(ARGV)

if options.help?
$stdout.puts options.help
puts options.help
return
end

if options.version?
$stdout.puts Retest::VERSION
puts Retest::VERSION
return
end

Expand All @@ -28,6 +28,7 @@ repository = Retest::Repository.new(files: Retest::VersionControl.files, prompt:
command = Retest::Command.for_options(options)
runner = Retest::Runner.new(command)
sounds = Retest::Sounds.for(options)
watcher = Retest::Watcher.for(options.watcher)

sounds.play(:start)
runner.add_observer(sounds)
Expand All @@ -43,25 +44,31 @@ if options.params[:diff]
return
end

if watcher == Retest::Watcher::Watchexec
puts "Watcher: [WATCHEXEC]"
else Retest::Watcher::Default
puts "Watcher: [LISTEN]"
end

launching_message = "Launching Retest..."
if options.force_polling?
launching_message = "Launching Retest with polling method..."
end

# Main action

$stdout.puts launching_message
Retest.listen(options) do |modified, added, removed|
puts launching_message
Retest.listen(options, listener: watcher) do |modified, added, removed|
begin
repository.sync(added: added, removed: removed)
runner.sync(added: added, removed: removed)

listen_wr.puts "file changed: #{(modified + added).first}"
rescue => e
$stdout.puts "Something went wrong: #{e.message}"
puts "Something went wrong: #{e.message}"
end
end
$stdout.puts "Ready to refactor! You can make file changes now"
puts "Ready to refactor! You can make file changes now"

def run_command(input:, program:)
program.clear_terminal
Expand Down
2 changes: 2 additions & 0 deletions features/hanami-app/retest/retest_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def test_with_no_command

assert_match <<~OUTPUT, @output.read
Setup identified: [RAKE]. Using command: 'bundle exec rake test TEST=<test>'
Watcher: [LISTEN]
Launching Retest...
Ready to refactor! You can make file changes now
OUTPUT
Expand All @@ -81,6 +82,7 @@ def test_with_no_command_all

assert_match <<~OUTPUT, @output.read
Setup identified: [RAKE]. Using command: 'bundle exec rake test'
Watcher: [LISTEN]
Launching Retest...
Ready to refactor! You can make file changes now
OUTPUT
Expand Down
2 changes: 2 additions & 0 deletions features/rails-app/retest/retest_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def test_with_no_command

assert_match <<~OUTPUT, @output.read
Setup identified: [RAILS]. Using command: 'bin/rails test <test>'
Watcher: [LISTEN]
Launching Retest...
Ready to refactor! You can make file changes now
OUTPUT
Expand All @@ -81,6 +82,7 @@ def test_with_no_command_all

assert_match <<~OUTPUT, @output.read
Setup identified: [RAILS]. Using command: 'bin/rails test'
Watcher: [LISTEN]
Launching Retest...
Ready to refactor! You can make file changes now
OUTPUT
Expand Down
2 changes: 2 additions & 0 deletions features/rspec-rails/retest/retest_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def test_with_no_command

assert_match <<~OUTPUT, @output.read
Setup identified: [RSPEC]. Using command: 'bundle exec rspec <test>'
Watcher: [LISTEN]
Launching Retest...
Ready to refactor! You can make file changes now
OUTPUT
Expand All @@ -81,6 +82,7 @@ def test_with_no_command_all

assert_match <<~OUTPUT, @output.read
Setup identified: [RSPEC]. Using command: 'bundle exec rspec'
Watcher: [LISTEN]
Launching Retest...
Ready to refactor! You can make file changes now
OUTPUT
Expand Down
1 change: 1 addition & 0 deletions features/rspec-ruby/retest/retest_test/flags_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def test_with_no_command

assert_match <<~OUTPUT, @output.read
Setup identified: [RSPEC]. Using command: 'bundle exec rspec <test>'
Watcher: [LISTEN]
Launching Retest...
Ready to refactor! You can make file changes now
OUTPUT
Expand Down
1 change: 1 addition & 0 deletions features/ruby-app/retest/retest_test/flags_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def test_with_no_command

assert_match <<~OUTPUT, @output.read
Setup identified: [RUBY]. Using command: 'bundle exec ruby <test>'
Watcher: [LISTEN]
Launching Retest...
Ready to refactor! You can make file changes now
OUTPUT
Expand Down
1 change: 1 addition & 0 deletions features/ruby-bare/retest/scenarios/auto_flag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def test_start_retest

assert_match <<~OUTPUT, @output.read
Setup identified: [RUBY]. Using command: 'ruby <test>'
Watcher: [LISTEN]
Launching Retest...
Ready to refactor! You can make file changes now
OUTPUT
Expand Down
2 changes: 1 addition & 1 deletion features/ruby-bare/retest/scenarios/custom_extensions.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class CustomExtensionTest < Minitest::Test
def setup
@command = %Q{retest "echo 'I captured a change'" --ext="\\.txt$"}
@command = %Q{retest "echo 'I captured a change'" --exts="txt"}
end

def teardown
Expand Down
7 changes: 4 additions & 3 deletions lib/retest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@
require "retest/program"
require "retest/prompt"
require "retest/sounds"
require "retest/watcher"

Listen.adapter_warn_behavior = :log

module Retest
class Error < StandardError; end
class FileNotFound < StandardError; end

def self.listen(options, listener: Listen)
listener.to('.', only: options.extension, relative: true, force_polling: options.force_polling?) do |modified, added, removed|
def self.listen(options, listener: Watcher::Default)
listener.watch(dir: '.', extensions: options.extensions, polling: options.force_polling?) do |modified, added, removed|
yield modified, added, removed
end.start
end
end
end
25 changes: 19 additions & 6 deletions lib/retest/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,19 @@ class Options
long "--diff=git-branch"
end

option :ext do
desc "Regex of file extensions to listen to"
long "--ext=regex"
default "\\.rb$"
option :exts do
desc "Comma separated of filenames extensions to filter to"
long "--exts=<EXTENSIONS>"
default "rb"
convert :list
end

option :watcher do
desc "Tool used to watch file events"
permit %i[listen watchexec]
long "--watcher=<WATCHER>"
short "-w"
convert :sym
end

flag :all do
Expand Down Expand Up @@ -155,8 +164,12 @@ def force_polling?
params[:polling]
end

def extension
Regexp.new(params[:ext])
def extensions
params[:exts]
end

def watcher
params[:watcher] || :installed
end

def merge(options = [])
Expand Down
4 changes: 2 additions & 2 deletions lib/retest/version_control.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ module VersionControl

module_function

def files
[Git, NoVersionControl].find(&:installed?).files
def files(extensions: [])
[Git, NoVersionControl].find(&:installed?).files(extensions: extensions)
end
end
end
8 changes: 6 additions & 2 deletions lib/retest/version_control/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ def name
'git'
end

def files
(untracked_files + tracked_files).sort
def files(extensions: [])
result = (untracked_files + tracked_files).sort
unless extensions.empty?
result.select! { |file| /\.(?:#{extensions.join('|')})$/.match?(file) }
end
result
end

def diff_files(branch)
Expand Down
10 changes: 8 additions & 2 deletions lib/retest/version_control/no_version_control.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ def name
'default'
end

def files
Dir.glob('**/*') - Dir.glob('{tmp,node_modules}/**/*')
def files(extensions: [])
result = if extensions.empty?
Dir.glob('**/*')
else
Dir.glob("**/*.{#{extensions.join(',')}}")
end

result - Dir.glob('{tmp,node_modules}/**/*')
end
end
end
Expand Down
89 changes: 89 additions & 0 deletions lib/retest/watcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
module Retest
module Watcher
def self.for(watcher)
tool = case watcher.to_s
when 'listen' then Default
when 'watchexec' then Watchexec
when '', 'installed' then installed
else raise ArgumentError, "Unknown #{watcher}"
end

unless tool.installed?
raise ArgumentError, "#{watcher} not installed on machine"
end

tool
end

def self.installed
[Watchexec, Default].find(&:installed?)
end

module Default
def self.installed?
true
end

def self.watch(dir:, extensions:, polling: false)
Listen.to(dir, only: extensions_regex(extensions), relative: true, polling: polling) do |modified, added, removed|
yield modified, added, removed
end.start
end

def self.extensions_regex(extensions)
Regexp.new("\\.(?:#{extensions.join("|")})$")
end
end

module Watchexec
def self.installed?
system "watchexec --version > /dev/null 2>&1"
end

def self.watch(dir:, extensions:, polling: false)
require 'open3'
command = "watchexec --exts #{extensions.join(',')} -w #{dir} --emit-events-to stdio --no-meta --only-emit-events"

# watch_rd, watch_wr = IO.pipe
# pid = Process.spawn(command, out: watch_wr)
# at_exit do
# Process.kill("TERM", pid) if pid
# watch_rd.close
# watch_wr.close
# end

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):(?<path>.*)/.match(data.strip)
if change
added, modified, removed = result = [[], [], []]
path = Pathname(change[:path]).relative_path_from(Dir.pwd).to_s
file_exist = File.exist?(path)
file_cached = files.key?(path)
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
end
end
32 changes: 17 additions & 15 deletions test/retest/options/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@ Arguments:


Options:
--all Run all the specs of a specificied ruby setup
--diff=git-branch Pipes all matching tests from diffed branch to test
command
--ext=regex Regex of file extensions to listen to (default
"\\.rb$")
-h, --help Print usage
--notify Play a sound when specs pass or fail (macOS only)
--polling Use polling method when listening to file changes
Some filesystems won't work without it
VM/Vagrant Shared folders, NFS, Samba, sshfs...
--rails Shortcut for a standard Rails setup
--rake Shortcut for a standard Rake setup
--rspec Shortcut for a standard RSpec setup
--ruby Shortcut for a Ruby project
-v, --version Print retest version
--all Run all the specs of a specificied ruby setup
--diff=git-branch Pipes all matching tests from diffed branch to test
command
--exts=<EXTENSIONS> Comma separated of filenames extensions to filter
to (default "rb")
-h, --help Print usage
--notify Play a sound when specs pass or fail (macOS only)
--polling Use polling method when listening to file changes
Some filesystems won't work without it
VM/Vagrant Shared folders, NFS, Samba, sshfs...
--rails Shortcut for a standard Rails setup
--rake Shortcut for a standard Rake setup
--rspec Shortcut for a standard RSpec setup
--ruby Shortcut for a Ruby project
-v, --version Print retest version
-w, --watcher=<WATCHER> Tool used to watch file events (permitted: listen,
watchexec)

Examples:
Runs a matching rails test after a file change
Expand Down
Loading