├── .gitignore ├── test ├── cases │ ├── helper.rb │ ├── test_multiple_nested.rb │ └── test_with_foreign_method.rb └── test_line.rb ├── Gemfile ├── .travis.yml ├── Rakefile ├── lib └── minitest │ ├── line │ └── describe_track.rb │ └── line_plugin.rb ├── minitest-line.gemspec ├── README.md ├── CHANGELOG.md └── MIT-LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /Gemfile.lock 2 | /.bundle 3 | -------------------------------------------------------------------------------- /test/cases/helper.rb: -------------------------------------------------------------------------------- 1 | def create_test 2 | it "create" do 3 | 1.must_equal 1 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | if v = ENV['MINITEST'] 6 | gem 'minitest', v 7 | end 8 | 9 | group :test do 10 | gem 'rake' 11 | end 12 | 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.0 4 | - 2.1 5 | - 2.2 6 | - 2.3 7 | - 2.4 8 | env: 9 | - MINITEST="~>5.8.0" 10 | - MINITEST="~>5.9.0" 11 | - MINITEST="~>5.10.0" 12 | - MINITEST="~>5.11.0" 13 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'rake/testtask' 3 | 4 | task :default => :test 5 | 6 | desc 'Runs tests' 7 | task :test do 8 | test = 'test/test_line.rb' 9 | ruby File.expand_path(test) 10 | ruby test 11 | end 12 | -------------------------------------------------------------------------------- /test/cases/test_multiple_nested.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | describe 'a' do 4 | it "works" do 5 | puts "TEST-A" 6 | end 7 | end 8 | 9 | describe 'b' do 10 | it "works" do 11 | puts "TEST-B" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/cases/test_with_foreign_method.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helper' 2 | require 'minitest/autorun' 3 | 4 | describe '' do 5 | create_test 6 | 7 | it "works" do 8 | puts "WORKS" 9 | end 10 | 11 | it "fails" do 12 | puts "FAILS" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/minitest/line/describe_track.rb: -------------------------------------------------------------------------------- 1 | module Minitest 2 | module Line 3 | module DescribeTrack 4 | def describe(*args, &block) 5 | klass = super 6 | klass.instance_variable_set(:@minitest_line_caller, caller(0..5)) 7 | klass 8 | end 9 | end 10 | end 11 | end 12 | 13 | Object.send(:include, Minitest::Line::DescribeTrack) 14 | -------------------------------------------------------------------------------- /minitest-line.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new "minitest-line", "0.6.5" do |s| 2 | s.description = s.summary = "Focused tests for Minitest" 3 | s.email = "judofyr@gmail.com" 4 | s.homepage = "https://github.com/judofyr/minitest-line" 5 | s.authors = ['Magnus Holm'] 6 | s.license = "MIT" 7 | s.files = Dir['lib/**/*.rb'] 8 | s.add_runtime_dependency('minitest', '~> 5.0') 9 | s.required_ruby_version = '>= 2.0.0' 10 | end 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minitest-line 2 | 3 | A Minitest 5 plugin for running focused tests. 4 | 5 | ```Bash 6 | gem install minitest-line 7 | ruby test/my_file -l 5 8 | ``` 9 | 10 | If you want to be able to run describe block by line number add 11 | ```Ruby 12 | require 'minitest/line/describe_track' 13 | ``` 14 | 15 | ## Acknowledgments 16 | 17 | This plugin was inspired by [James Mead](https://github.com/floehopper) pull request to Minitest: [Run a test by line number from the command line (a.k.a. "run focussed test")](https://github.com/seattlerb/minitest/pull/267) 18 | 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## HEAD 2 | 3 | ## 0.6.4 - 2017-06-17 4 | 5 | * Avoid warnings [grosser] 6 | 7 | ## 0.6.3 - 2015-07-13 8 | 9 | * Handles describe-blocks better [grosser] 10 | * Use colors on TTY [grosser] 11 | 12 | ## 0.6.2 - 2014-05-20 13 | 14 | * Show relative paths [judofyr] 15 | * Improve copy-pasting [grosser, #2] 16 | 17 | ## 0.6.1 - 2014-03-25 18 | 19 | * Bug fix: Don't show how to run skipped tests [judofyr] 20 | 21 | ## 0.6.0 - 2014-03-25 22 | 23 | * Show how to run focus on failing tests [judofyr] 24 | 25 | ## 0.5.0 - 2013-05-26 26 | 27 | * Initial version 28 | 29 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Magnus Holm 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/minitest/line_plugin.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | module Minitest 4 | module Line 5 | class << self 6 | def tests_with_lines 7 | target_file = $0 8 | methods_with_lines(target_file).concat describes_with_lines(target_file) 9 | end 10 | 11 | private 12 | 13 | def methods_with_lines(target_file) 14 | runnables.flat_map do |runnable| 15 | rname = runnable.name 16 | runnable.runnable_methods.map do |name| 17 | file, line = runnable.instance_method(name).source_location 18 | next unless file == target_file 19 | test_name = (rname ? "#{rname}##{name}" : name) 20 | [test_name, line] 21 | end 22 | end.uniq.compact 23 | end 24 | 25 | def describes_with_lines(target_file) 26 | runnables.map do |runnable| 27 | next unless caller = runnable.instance_variable_defined?(:@minitest_line_caller) && runnable.instance_variable_get(:@minitest_line_caller) 28 | next unless line = caller.detect { |l| l.include?(target_file) } 29 | ["/#{Regexp.escape(runnable.name)}/", line[/:(\d+):in/, 1].to_i] 30 | end.compact 31 | end 32 | 33 | def runnables 34 | Minitest::Runnable.runnables 35 | end 36 | end 37 | end 38 | 39 | def self.plugin_line_options(opts, options) 40 | opts.on '-l', '--line N', Integer, "Run test at line number" do |lineno| 41 | options[:line] = lineno 42 | end 43 | end 44 | 45 | def self.plugin_line_init(options) 46 | unless exp_line = options[:line] 47 | reporter.reporters << LineReporter.new 48 | return 49 | end 50 | 51 | tests = Minitest::Line.tests_with_lines 52 | 53 | filter, _ = tests.sort_by { |n, l| -l }.detect { |n, l| exp_line >= l } 54 | 55 | raise "Could not find test method before line #{exp_line}" unless filter 56 | 57 | options[:filter] = filter 58 | end 59 | 60 | class LineReporter < Reporter 61 | def initialize(*) 62 | super 63 | @failures = [] 64 | end 65 | 66 | def record(result) 67 | if !result.skipped? && !result.passed? 68 | @failures << result 69 | end 70 | end 71 | 72 | def report 73 | return unless @failures.any? 74 | io.puts 75 | io.puts "Focus on failing tests:" 76 | pwd = Pathname.new(Dir.pwd) 77 | @failures.each do |res| 78 | result = (res.respond_to?(:source_location) ? res : res.method(res.name)) 79 | file, line = result.source_location 80 | 81 | if file 82 | file = Pathname.new(file) 83 | file = file.relative_path_from(pwd) if file.absolute? 84 | output = "ruby #{file} -l #{line}" 85 | output = "\e[31m#{output}\e[0m" if $stdout.tty? 86 | io.puts output 87 | end 88 | end 89 | end 90 | end 91 | 92 | def self.plugin_line_inject_reporter 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /test/test_line.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH << File.expand_path('../../lib', __FILE__) 2 | $VERBOSE = true 3 | 4 | require 'stringio' 5 | require 'minitest/autorun' 6 | require 'minitest/mock' 7 | require 'minitest/line/describe_track' 8 | 9 | class LineExample < Minitest::Test 10 | def test_hello 11 | p :hello 12 | end 13 | 14 | def test_world 15 | p :world 16 | end 17 | 18 | def test_failure 19 | flunk 20 | end 21 | 22 | def test_skip 23 | skip 24 | end 25 | end 26 | 27 | class Line2Example < Minitest::Test 28 | def test_hello 29 | p :hello 30 | end 31 | end 32 | 33 | DescribeExample = describe "DescribeExample" do 34 | it "hello" do 35 | p :hello 36 | end 37 | 38 | describe "here" do 39 | describe "comes" do 40 | let(:unused) { } 41 | 42 | it "the nesting" do 43 | p :nested 44 | end 45 | 46 | it "is amazing" do 47 | p :amazing 48 | end 49 | 50 | def self.indirect(*args, &block) 51 | describe(*args, &block) 52 | end 53 | 54 | indirect "it is" do 55 | it "works" do 56 | p :indirect 57 | end 58 | end 59 | end 60 | end 61 | 62 | it "world" do 63 | p :world 64 | end 65 | end 66 | 67 | Minitest::Runnable.runnables.delete(LineExample) 68 | Minitest::Runnable.runnables.delete(Line2Example) 69 | describe_examples = Minitest::Runnable.runnables.select { |c| c == DescribeExample || c < DescribeExample } 70 | Minitest::Runnable.runnables.delete_if { |c| describe_examples.include?(c) } 71 | 72 | if Minitest.respond_to?(:parallel_executor=) 73 | dummy = Class.new { 74 | def start 75 | end 76 | 77 | def shutdown 78 | end 79 | } 80 | Minitest.parallel_executor = dummy.new 81 | end 82 | 83 | describe "Minitest::Line" do 84 | def pending 85 | yield 86 | rescue Minitest::Assertion 87 | skip 88 | else 89 | raise "It works!" 90 | end 91 | 92 | def run_class(klasses, args = []) 93 | Minitest::Runnable.stub :runnables, klasses do 94 | $stdout = io = StringIO.new 95 | Minitest.run(args) 96 | $stdout = STDOUT 97 | io.string 98 | end 99 | end 100 | 101 | def sh(command, options={}) 102 | result = `#{command} #{"2>&1" unless options[:keep_output]}` 103 | raise "#{options[:fail] ? "SUCCESS" : "FAIL"} #{command}\n#{result}" if $?.success? == !!options[:fail] 104 | result 105 | end 106 | 107 | it "finds tests by line number" do 108 | [*10..13, *28..30].each do |line| 109 | output = run_class [LineExample, Line2Example], ['--line', line.to_s] 110 | assert_match /1 runs/, output 111 | assert_match /:hello/, output 112 | refute_match /:world/, output 113 | end 114 | 115 | (14..17).each do |line| 116 | output = run_class [LineExample], ['--line', line.to_s] 117 | assert_match /1 runs/, output 118 | assert_match /:world/, output 119 | refute_match /:hello/, output 120 | end 121 | end 122 | 123 | it "prints failing tests after test run" do 124 | output = run_class [LineExample] 125 | assert_match /Focus on failing tests:/, output 126 | assert_match /#{File.basename(__FILE__)} -l 18/, output 127 | refute_match /-l 22/, output 128 | end 129 | 130 | if __FILE__.start_with?("/") 131 | it "shows focus relative to pwd" do 132 | dir = File.dirname(__FILE__) 133 | Dir.chdir(dir) do 134 | output = run_class [LineExample] 135 | assert_match "ruby #{File.basename(__FILE__)} -l 18", output 136 | end 137 | end 138 | end 139 | 140 | it "fails when given a line before any test" do 141 | assert_raises(RuntimeError) do 142 | run_class [LineExample], ['--line', '8'] 143 | end 144 | end 145 | 146 | it "runs last test when given a line after last test" do 147 | output = run_class [LineExample], ['--line', '81'] 148 | assert_match /1 runs/, output 149 | assert_match /1 skip/, output 150 | end 151 | 152 | it "runs tests declared with it" do 153 | output = run_class describe_examples, ['--line', '34'] 154 | assert_match /1 runs/, output 155 | assert_match /:hello/, output 156 | refute_match /:world/, output 157 | end 158 | 159 | it "runs tests declared with it inside nested describes" do 160 | output = run_class describe_examples, ['--line', '46'] 161 | assert_match /1 runs/, output 162 | assert_match /:amazing/, output 163 | refute_match /:nesting/, output 164 | end 165 | 166 | it "runs tests declared with describe" do 167 | output = run_class describe_examples, ['--line', '38'] 168 | assert_match /3 runs/, output 169 | assert_match /:nested/, output 170 | assert_match /:amazing/, output 171 | refute_match /:hello/, output 172 | refute_match /:world/, output 173 | end 174 | 175 | it "runs tests declared with describe when missing the line" do 176 | output = run_class describe_examples, ['--line', '40'] 177 | assert_match /3 runs/, output 178 | end 179 | 180 | it "runs tests declared with describe when using helper methods" do 181 | output = run_class describe_examples, ['--line', '54'] 182 | assert_match /1 runs/, output 183 | assert_match /indirect/, output 184 | end 185 | 186 | it "can run when multiple nested tests have the same method name" do 187 | result = sh("ruby #{Bundler.root}/test/cases/test_multiple_nested.rb -l 4") 188 | result.must_include "TEST-A" 189 | result.wont_include "TEST-B" 190 | end 191 | 192 | describe "when first test is a foreign test" do 193 | it "can run tests by line with absolute path" do 194 | result = sh("ruby #{Bundler.root}/test/cases/test_with_foreign_method.rb -l 7") 195 | result.must_include "WORKS" 196 | result.wont_include "FAILS" 197 | end 198 | 199 | it "can run tests by line with relative path" do 200 | result = sh("ruby test/cases/test_with_foreign_method.rb -l 7") 201 | result.must_include "WORKS" 202 | result.wont_include "FAILS" 203 | end 204 | end 205 | end 206 | 207 | --------------------------------------------------------------------------------