From d497975868aefc701d3fe9f2422c0c13d8403bbf Mon Sep 17 00:00:00 2001 From: Matt Brictson Date: Tue, 20 Feb 2024 18:03:53 -0800 Subject: [PATCH] Run all tests by default; ability to run tests by arbitrary path(s) Similar to RSpec and the Rails test runner, this commit adds a feature where the `mt` executable can run multiple tests at once based on command line arguments. When executed with no args, it now runs all tests by default. ```sh # Run all tests bin/mt # Run a specific test file bin/mt test/cli_test.rb # Run a directory of tests bin/mt test/commands # Run many tests/directories at once bin/mt test/cli_test.rb test/commands ``` --- lib/mighty_test/cli.rb | 30 +++++- lib/mighty_test/file_system.rb | 5 + lib/mighty_test/option_parser.rb | 3 +- .../test/helpers/users_helper_test.rb | 0 .../rails_project/test/models/account_test.rb | 0 .../test/system/users_system_test.rb | 0 test/mighty_test/cli_test.rb | 93 +++++++++++++++++-- 7 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 test/fixtures/rails_project/test/helpers/users_helper_test.rb create mode 100644 test/fixtures/rails_project/test/models/account_test.rb create mode 100644 test/fixtures/rails_project/test/system/users_system_test.rb diff --git a/lib/mighty_test/cli.rb b/lib/mighty_test/cli.rb index 5f08a32..304e6ab 100644 --- a/lib/mighty_test/cli.rb +++ b/lib/mighty_test/cli.rb @@ -1,6 +1,7 @@ module MightyTest class CLI - def initialize(env: ENV, option_parser: OptionParser.new, runner: MinitestRunner.new) + def initialize(file_system: FileSystem.new, env: ENV, option_parser: OptionParser.new, runner: MinitestRunner.new) + @file_system = file_system @env = env.to_h @option_parser = option_parser @runner = runner @@ -26,7 +27,7 @@ def run(argv: ARGV) private - attr_reader :env, :path_args, :extra_args, :options, :option_parser, :runner + attr_reader :file_system, :env, :path_args, :extra_args, :options, :option_parser, :runner def print_help # Minitest already prints the `-h, --help` option, so omit mighty_test's @@ -44,14 +45,33 @@ def run_test_by_line_number test_name = TestParser.new(path).test_name_at_line(line.to_i) if test_name - runner.run_inline_and_exit!(path, args: ["-n", "/^#{Regexp.quote(test_name)}$/"] + extra_args) + run_tests_and_exit!(path, flags: ["-n", "/^#{Regexp.quote(test_name)}$/"]) else - runner.run_inline_and_exit!(args: extra_args) + run_tests_and_exit! end end def run_tests_by_path - runner.run_inline_and_exit!(*path_args, args: extra_args) + test_paths = find_test_paths + run_tests_and_exit!(*test_paths) + end + + def find_test_paths + return file_system.find_test_paths if path_args.empty? + + path_args.flat_map do |path| + if Dir.exist?(path) + file_system.find_test_paths(path) + elsif File.exist?(path) + [path] + else + raise ArgumentError, "#{path} does not exist" + end + end + end + + def run_tests_and_exit!(*test_paths, flags: []) + runner.run_inline_and_exit!(*test_paths, args: extra_args + flags) end def handle_exception(e) # rubocop:disable Naming/MethodParameterName diff --git a/lib/mighty_test/file_system.rb b/lib/mighty_test/file_system.rb index 222cc9f..0c49794 100644 --- a/lib/mighty_test/file_system.rb +++ b/lib/mighty_test/file_system.rb @@ -12,5 +12,10 @@ def find_matching_test_file(path) test_path = path[%r{^(?:app|lib)/(.+)\.[^\.]+$}, 1].then { "test/#{_1}_test.rb" } test_path if test_path && File.exist?(test_path) end + + def find_test_paths(directory="test") + glob = File.join(directory, "**/*_test.rb") + Dir[glob] + end end end diff --git a/lib/mighty_test/option_parser.rb b/lib/mighty_test/option_parser.rb index d79b380..e047462 100644 --- a/lib/mighty_test/option_parser.rb +++ b/lib/mighty_test/option_parser.rb @@ -6,7 +6,8 @@ def initialize @parser = ::OptionParser.new do |op| op.require_exact = true op.banner = <<~BANNER - Usage: mt ... + Usage: mt + mt [test file...] [test dir...] mt --watch BANNER diff --git a/test/fixtures/rails_project/test/helpers/users_helper_test.rb b/test/fixtures/rails_project/test/helpers/users_helper_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/rails_project/test/models/account_test.rb b/test/fixtures/rails_project/test/models/account_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/rails_project/test/system/users_system_test.rb b/test/fixtures/rails_project/test/system/users_system_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/mighty_test/cli_test.rb b/test/mighty_test/cli_test.rb index e6e1ab6..843fd3e 100644 --- a/test/mighty_test/cli_test.rb +++ b/test/mighty_test/cli_test.rb @@ -2,6 +2,8 @@ module MightyTest class CLITest < Minitest::Test + include FixturesPath + def test_help_flag_prints_usage_and_minitest_options result = cli_run(argv: ["--help"]) @@ -27,15 +29,96 @@ def test_version_flag_prints_version assert_equal(VERSION, result.stdout.chomp) end + def test_with_no_args_runs_all_tests_in_the_test_directory + with_fake_minitest_runner do |runner, executed_tests| + cli_run(argv: [], chdir: fixtures_path.join("rails_project"), runner:) + + assert_equal( + %w[ + test/helpers/users_helper_test.rb + test/models/account_test.rb + test/models/user_test.rb + test/system/users_system_test.rb + ], + executed_tests.sort + ) + end + end + + def test_with_a_directory_arg_runs_all_test_files_in_that_directory + with_fake_minitest_runner do |runner, executed_tests| + cli_run(argv: ["test/models"], chdir: fixtures_path.join("rails_project"), runner:) + + assert_equal( + %w[ + test/models/account_test.rb + test/models/user_test.rb + ], + executed_tests.sort + ) + end + end + + def test_with_a_mixture_of_file_and_directory_args_runs_all_matching_tests + with_fake_minitest_runner do |runner, executed_tests| + cli_run(argv: %w[test/system test/models/user_test.rb], chdir: fixtures_path.join("rails_project"), runner:) + + assert_equal( + %w[ + test/models/user_test.rb + test/system/users_system_test.rb + ], + executed_tests.sort + ) + end + end + + def test_with_explict_file_args_runs_those_files_regardless_of_whether_they_appear_to_be_tests + with_fake_minitest_runner do |runner, executed_tests| + cli_run(argv: ["app/models/user.rb"], chdir: fixtures_path.join("rails_project"), runner:) + + assert_equal( + %w[ + app/models/user.rb + ], + executed_tests.sort + ) + end + end + + def test_with_directory_args_only_runs_files_that_appear_to_be_tests + with_fake_minitest_runner do |runner, executed_tests| + cli_run(argv: ["app/models"], chdir: fixtures_path.join("rails_project"), runner:) + + assert_empty(executed_tests) + end + end + + def test_with_non_existent_path_raises_an_error + error = assert_raises(ArgumentError) do + cli_run(argv: ["test/models/non_existent_test.rb"], chdir: fixtures_path.join("rails_project")) + end + + assert_includes(error.message, "test/models/non_existent_test.rb does not exist") + end + private - def cli_run(argv:, env: {}, stdin: nil, raise_on_failure: true) + def with_fake_minitest_runner + executed_tests = [] + runner = MinitestRunner.new + runner.stub(:run_inline_and_exit!, ->(*test_files, **) { executed_tests.append(*test_files.flatten) }) do + yield(runner, executed_tests) + end + end + + def cli_run(argv:, env: {}, chdir: ".", runner: nil, raise_on_failure: true) exitstatus = true - orig_stdin = $stdin - $stdin = StringIO.new(stdin) if stdin stdout, stderr = capture_io do - CLI.new(env:).run(argv:) + Dir.chdir(chdir) do + CLI.new(**{ env:, runner: }.compact).run(argv:) + end rescue SystemExit => e exitstatus = e.status end @@ -44,8 +127,6 @@ def cli_run(argv:, env: {}, stdin: nil, raise_on_failure: true) raise "CLI exited with status: #{exitstatus}" if raise_on_failure && result.failure? result - ensure - $stdin = orig_stdin end end end