generated from mattbrictson/gem
-
Notifications
You must be signed in to change notification settings - Fork 0
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
Implement a simple, non-interactive --watch
mode
#4
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
module MightyTest | ||
autoload :VERSION, "mighty_test/version" | ||
autoload :CLI, "mighty_test/cli" | ||
autoload :FileSystem, "mighty_test/file_system" | ||
autoload :MinitestRunner, "mighty_test/minitest_runner" | ||
autoload :OptionParser, "mighty_test/option_parser" | ||
autoload :TestParser, "mighty_test/test_parser" | ||
autoload :Watcher, "mighty_test/watcher" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
module MightyTest | ||
class FileSystem | ||
def listen(&) | ||
require "listen" | ||
Listen.to(*%w[app lib test].select { |p| Dir.exist?(p) }, relative: true, &).tap(&:start) | ||
end | ||
|
||
def find_matching_test_file(path) | ||
return nil unless path && File.exist?(path) && !Dir.exist?(path) | ||
return path if path.match?(%r{^test/.*_test.rb$}) | ||
|
||
test_path = path[%r{^(?:app|lib)/(.+)\.[^\.]+$}, 1].then { "test/#{_1}_test.rb" } | ||
test_path if test_path && File.exist?(test_path) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
require "concurrent" | ||
|
||
module MightyTest | ||
class Watcher | ||
WATCHING_FOR_CHANGES = "Watching for changes to source and test files. Press ctrl-c to exit.".freeze | ||
|
||
def initialize(extra_args: [], file_system: FileSystem.new, system_proc: method(:system)) | ||
@event = Concurrent::MVar.new | ||
@extra_args = extra_args | ||
@file_system = file_system | ||
@system_proc = system_proc | ||
end | ||
|
||
def run(iterations: :indefinitely) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗒️ Normally the watcher runs indefinitely, waiting for file system changes. The |
||
start_listener | ||
puts WATCHING_FOR_CHANGES | ||
|
||
loop_for(iterations) do | ||
case await_next_event | ||
in [:file_system_changed, paths] | ||
mt(*paths) if paths.any? | ||
in [:tests_completed, :pass | :fail] | ||
puts WATCHING_FOR_CHANGES | ||
end | ||
end | ||
ensure | ||
listener&.stop | ||
end | ||
|
||
private | ||
|
||
attr_reader :extra_args, :file_system, :listener, :system_proc | ||
|
||
def mt(*test_paths) | ||
success = system_proc.call("mt", *extra_args, "--", *test_paths.flatten) | ||
post_event(:tests_completed, success ? :pass : :fail) | ||
rescue Interrupt | ||
# Pressing ctrl-c kills the fs_event background process, so we have to manually restart it. | ||
restart_listener | ||
end | ||
|
||
def start_listener | ||
listener.stop if listener && !listener.stopped? | ||
|
||
@listener = file_system.listen do |modified, added, _removed| | ||
# Pause listener so that subsequent changes are queued up while we are running the tests | ||
listener.pause unless listener.stopped? | ||
|
||
test_paths = [*modified, *added].filter_map do |path| | ||
file_system.find_matching_test_file(path) | ||
end | ||
|
||
post_event(:file_system_changed, test_paths.uniq) | ||
end | ||
end | ||
alias restart_listener start_listener | ||
|
||
def loop_for(iterations, &) | ||
iterations == :indefinitely ? loop(&) : iterations.times(&) | ||
end | ||
|
||
def await_next_event | ||
listener.start if listener.paused? | ||
@event.take | ||
end | ||
|
||
def post_event(*event) | ||
@event.put(event) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
require "test_helper" | ||
|
||
module MightyTest | ||
class FileSystemTest < Minitest::Test | ||
include FixturesPath | ||
|
||
def test_find_matching_test_file_returns_nil_for_nil_path | ||
assert_nil find_matching_test_file(nil) | ||
end | ||
|
||
def test_find_matching_test_file_returns_nil_for_non_existent_path | ||
assert_nil find_matching_test_file("path/to/nowhere.rb") | ||
end | ||
|
||
def test_find_matching_test_file_returns_nil_for_directory_path | ||
assert_nil find_matching_test_file("lib/example", in: fixtures_path.join("example_project")) | ||
end | ||
|
||
def test_find_matching_test_file_returns_nil_for_path_with_no_corresponding_test | ||
assert_nil find_matching_test_file("lib/example/version.rb", in: fixtures_path.join("example_project")) | ||
end | ||
|
||
def test_find_matching_test_file_returns_nil_for_a_test_support_file | ||
assert_nil find_matching_test_file("test/test_helper.rb", in: fixtures_path.join("example_project")) | ||
end | ||
|
||
def test_find_matching_test_file_returns_argument_if_it_is_already_a_test | ||
test_path = find_matching_test_file("test/example_test.rb", in: fixtures_path.join("example_project")) | ||
assert_equal("test/example_test.rb", test_path) | ||
end | ||
|
||
def test_find_matching_test_file_returns_matching_test_given_an_implementation_path_in_a_gem_project | ||
test_path = find_matching_test_file("lib/example.rb", in: fixtures_path.join("example_project")) | ||
assert_equal("test/example_test.rb", test_path) | ||
end | ||
|
||
def test_find_matching_test_file_returns_matching_test_given_a_model_path_in_a_rails_project | ||
test_path = find_matching_test_file("app/models/user.rb", in: fixtures_path.join("rails_project")) | ||
assert_equal("test/models/user_test.rb", test_path) | ||
end | ||
|
||
private | ||
|
||
def find_matching_test_file(path, in: ".") | ||
Dir.chdir(binding.local_variable_get(:in)) do | ||
FileSystem.new.find_matching_test_file(path) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
require "test_helper" | ||
|
||
module MightyTest | ||
class WatcherTest < Minitest::Test | ||
include FixturesPath | ||
|
||
def test_watcher_passes_unique_set_of_test_files_to_mt_command_based_on_changes_detected | ||
system_proc { |*args| puts "[SYSTEM] #{args.join(' ')}" } | ||
listen_thread do |callback| | ||
callback.call(["lib/example.rb", "test/focused_test.rb"], ["test/focused_test.rb"], []) | ||
end | ||
|
||
stdout, = run_watcher(iterations: 1, in: fixtures_path.join("example_project")) | ||
|
||
assert_includes(stdout, "[SYSTEM] mt -- test/example_test.rb test/focused_test.rb\n") | ||
end | ||
|
||
def test_watcher_does_nothing_if_a_detected_change_has_no_corresponding_test_file | ||
system_proc { |*args| puts "[SYSTEM] #{args.join(' ')}" } | ||
listen_thread do |callback| | ||
callback.call(["lib/example/version.rb"], [], []) | ||
end | ||
|
||
stdout, = run_watcher(iterations: 1, in: fixtures_path.join("example_project")) | ||
|
||
refute_includes(stdout, "[SYSTEM]") | ||
end | ||
|
||
def test_watcher_passes_extra_args_through_to_mt_command | ||
system_proc { |*args| puts "[SYSTEM] #{args.join(' ')}" } | ||
listen_thread do |callback| | ||
callback.call(["test/example_test.rb"], [], []) | ||
end | ||
|
||
stdout, = run_watcher(iterations: 1, extra_args: ["--fail-fast"], in: fixtures_path.join("example_project")) | ||
|
||
assert_includes(stdout, "[SYSTEM] mt --fail-fast -- test/example_test.rb\n") | ||
end | ||
|
||
def test_watcher_prints_a_status_message_after_successful_test_run | ||
system_proc do |*args| | ||
puts "[SYSTEM] #{args.join(' ')}" | ||
true | ||
end | ||
listen_thread do |callback| | ||
callback.call(["test/example_test.rb"], [], []) | ||
end | ||
|
||
stdout, = run_watcher(iterations: 2, in: fixtures_path.join("example_project")) | ||
|
||
assert_includes(stdout, <<~EXPECTED) | ||
[SYSTEM] mt -- test/example_test.rb | ||
Watching for changes to source and test files. Press ctrl-c to exit. | ||
EXPECTED | ||
end | ||
|
||
def test_watcher_prints_a_status_message_after_failed_test_run | ||
system_proc do |*args| | ||
puts "[SYSTEM] #{args.join(' ')}" | ||
false | ||
end | ||
listen_thread do |callback| | ||
callback.call(["test/example_test.rb"], [], []) | ||
end | ||
|
||
stdout, = run_watcher(iterations: 2, in: fixtures_path.join("example_project")) | ||
|
||
assert_includes(stdout, <<~EXPECTED) | ||
[SYSTEM] mt -- test/example_test.rb | ||
Watching for changes to source and test files. Press ctrl-c to exit. | ||
EXPECTED | ||
end | ||
|
||
def test_watcher_restarts_the_listener_when_a_test_run_is_interrupted | ||
thread_count = 0 | ||
system_proc { |*| raise Interrupt } | ||
listen_thread do |callback| | ||
thread_count += 1 | ||
callback.call(["test/example_test.rb"], [], []) unless thread_count > 2 | ||
end | ||
|
||
run_watcher(iterations: 2, in: fixtures_path.join("example_project")) | ||
assert_equal(2, thread_count) | ||
end | ||
|
||
private | ||
|
||
class Listener | ||
def initialize(thread, callback) | ||
Thread.new do | ||
thread.call(callback) | ||
end | ||
end | ||
|
||
def start | ||
end | ||
|
||
def stop | ||
end | ||
|
||
def pause | ||
end | ||
|
||
def stopped? | ||
false | ||
end | ||
|
||
def paused? | ||
false | ||
end | ||
end | ||
|
||
def run_watcher(iterations:, in: ".", extra_args: []) | ||
listen_thread = @listen_thread | ||
file_system = FileSystem.new | ||
file_system.define_singleton_method(:listen) { |&callback| Listener.new(listen_thread, callback) } | ||
capture_io do | ||
Dir.chdir(binding.local_variable_get(:in)) do | ||
@watcher = Watcher.new(extra_args:, file_system:, system_proc: @system_proc) | ||
@watcher.run(iterations:) | ||
end | ||
end | ||
end | ||
|
||
def listen_thread(&thread) | ||
@listen_thread = thread | ||
end | ||
|
||
def system_proc(&proc) | ||
@system_proc = proc | ||
end | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗒️ Implements a simple algorithm that matches e.g.
app/models/user.rb
totest/models/user_test.rb
.