diff --git a/lib/mighty_test/file_system.rb b/lib/mighty_test/file_system.rb index 38fda07..e7e8875 100644 --- a/lib/mighty_test/file_system.rb +++ b/lib/mighty_test/file_system.rb @@ -1,3 +1,5 @@ +require "open3" + module MightyTest class FileSystem def listen(&) @@ -17,5 +19,14 @@ def find_test_paths(directory="test") glob = File.join(directory, "**/*_test.rb") Dir[glob] end + + def find_new_and_changed_paths + out, _err, status = Open3.capture3(*%w[git ls-files --deduplicate -m -o --exclude-standard test app lib]) + return [] unless status.success? + + out.lines(chomp: true).reject(&:empty?).uniq + rescue SystemCallError + [] + end end end diff --git a/lib/mighty_test/watcher.rb b/lib/mighty_test/watcher.rb index adb1dbf..0674c26 100644 --- a/lib/mighty_test/watcher.rb +++ b/lib/mighty_test/watcher.rb @@ -20,14 +20,11 @@ def run(iterations: :indefinitely) # rubocop:disable Metrics/MethodLength loop_for(iterations) do case await_next_event in [:file_system_changed, [_, *] => paths] - console.clear - puts paths.join("\n") - puts - mt(*paths) + run_matching_test_files(paths) in [:keypress, "\r" | "\n"] - console.clear - puts "Running all tests...\n\n" - mt + run_all_tests + in [:keypress, "d"] + run_matching_test_files_from_git_diff in [:keypress, "q"] break else @@ -43,6 +40,32 @@ def run(iterations: :indefinitely) # rubocop:disable Metrics/MethodLength attr_reader :console, :extra_args, :file_system, :listener, :system_proc + def run_all_tests + console.clear + puts "Running all tests..." + puts + mt + end + + def run_matching_test_files(paths) + test_paths = paths.flat_map { |path| file_system.find_matching_test_path(path) }.compact.uniq + return false if test_paths.empty? + + console.clear + puts test_paths.join("\n") + puts + mt(*test_paths) + true + end + + def run_matching_test_files_from_git_diff + return if run_matching_test_files(file_system.find_new_and_changed_paths) + + console.clear + puts "No affected test files detected since the last git commit." + puts WATCHING_FOR_CHANGES + end + def mt(*test_paths) command = ["mt", *extra_args] command.append("--", *test_paths.flatten) if test_paths.any? @@ -63,12 +86,7 @@ def start_file_system_listener @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_path(path) - end - - post_event(:file_system_changed, test_paths.uniq) + post_event(:file_system_changed, [*modified, *added].uniq) end end alias restart_file_system_listener start_file_system_listener diff --git a/test/mighty_test/file_system_test.rb b/test/mighty_test/file_system_test.rb index a2c03bd..0bfce58 100644 --- a/test/mighty_test/file_system_test.rb +++ b/test/mighty_test/file_system_test.rb @@ -1,4 +1,5 @@ require "test_helper" +require "open3" module MightyTest class FileSystemTest < Minitest::Test @@ -71,6 +72,47 @@ def test_find_test_paths_returns_test_files_in_specific_directory ) end + def test_find_new_and_changed_paths_returns_empty_array_if_git_exits_with_error + status = Minitest::Mock.new + status.expect(:success?, false) + + paths = Open3.stub(:capture3, ["", "oh no!", status]) do + FileSystem.new.find_new_and_changed_paths + end + + assert_empty paths + end + + def test_find_new_and_changed_paths_returns_empty_array_if_system_call_fails + paths = Open3.stub(:capture3, ->(*) { raise SystemCallError, "oh no!" }) do + FileSystem.new.find_new_and_changed_paths + end + + assert_empty paths + end + + def test_find_new_and_changed_paths_returns_array_based_on_git_output + git_output = <<~OUT + lib/mighty_test/file_system.rb + test/mighty_test/file_system_test.rb + OUT + + status = Minitest::Mock.new + status.expect(:success?, true) + + paths = Open3.stub(:capture3, [git_output, "", status]) do + FileSystem.new.find_new_and_changed_paths + end + + assert_equal( + %w[ + lib/mighty_test/file_system.rb + test/mighty_test/file_system_test.rb + ], + paths + ) + end + private def find_matching_test_path(path, in: ".") diff --git a/test/mighty_test/watcher_test.rb b/test/mighty_test/watcher_test.rb index a6ff71a..98abd6f 100644 --- a/test/mighty_test/watcher_test.rb +++ b/test/mighty_test/watcher_test.rb @@ -124,6 +124,43 @@ def test_watcher_runs_all_tests_when_enter_key_is_pressed EXPECTED end + def test_watcher_runs_new_and_changed_files_according_to_git_when_d_key_is_pressed + system_proc do |*args| + puts "[SYSTEM] #{args.join(' ')}" + true + end + + file_system = FileSystem.new + stdout, = file_system.stub(:find_new_and_changed_paths, %w[lib/example.rb]) do + run_watcher(file_system:, stdin: "dq", in: fixtures_path.join("example_project")) + end + + assert_includes(stdout, <<~EXPECTED) + [CLEAR] + test/example_test.rb + + [SYSTEM] mt -- test/example_test.rb + EXPECTED + end + + def test_watcher_shows_a_message_if_d_key_is_pressed_and_there_are_no_changes + system_proc do |*args| + puts "[SYSTEM] #{args.join(' ')}" + true + end + + file_system = FileSystem.new + stdout, = file_system.stub(:find_new_and_changed_paths, []) do + run_watcher(file_system:, stdin: "dq", in: fixtures_path.join("example_project")) + end + + assert_includes(stdout, <<~EXPECTED) + [CLEAR] + No affected test files detected since the last git commit. + Watching for changes to source and test files. Press "q" to quit. + EXPECTED + end + private class Listener @@ -151,12 +188,11 @@ def paused? end end - def run_watcher(iterations: :indefinitely, in: ".", extra_args: [], stdin: nil) + def run_watcher(iterations: :indefinitely, in: ".", extra_args: [], stdin: nil, file_system: FileSystem.new) listen_thread = @listen_thread console = Console.new(stdin: stdin.nil? ? File::NULL : StringIO.new(stdin)) console.define_singleton_method(:clear) { puts "[CLEAR]" } console.define_singleton_method(:play_sound) { |sound| puts "[SOUND] #{sound.inspect}" } - file_system = FileSystem.new file_system.define_singleton_method(:listen) { |&callback| Listener.new(listen_thread, callback) } capture_io do