test_launcher 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/README.md +101 -18
  4. data/Rakefile +6 -0
  5. data/bin/test_launcher +8 -35
  6. data/lib/test_launcher.rb +22 -0
  7. data/lib/test_launcher/cli/input_parser.rb +73 -0
  8. data/lib/test_launcher/frameworks.rb +32 -0
  9. data/lib/test_launcher/frameworks/base.rb +54 -0
  10. data/lib/test_launcher/frameworks/implementation/collection.rb +27 -0
  11. data/lib/test_launcher/{tests/minitest → frameworks/implementation}/consolidator.rb +30 -32
  12. data/lib/test_launcher/frameworks/implementation/locator.rb +74 -0
  13. data/lib/test_launcher/frameworks/implementation/test_case.rb +65 -0
  14. data/lib/test_launcher/frameworks/minitest.rb +67 -0
  15. data/lib/test_launcher/frameworks/rspec.rb +50 -0
  16. data/lib/test_launcher/rubymine.rb +26 -0
  17. data/lib/test_launcher/rubymine/launcher.rb +55 -0
  18. data/lib/test_launcher/search/git.rb +64 -0
  19. data/lib/test_launcher/shell/runner.rb +19 -22
  20. data/lib/test_launcher/version.rb +1 -1
  21. data/test/install +3 -0
  22. data/test/test_helper.rb +32 -17
  23. data/test/test_launcher/fixtures/minitest/test/class_1_test.rb +17 -0
  24. data/test/test_launcher/fixtures/minitest/test/class_2_test.rb +5 -0
  25. data/test/test_launcher/fixtures/minitest/test/different_root/bin/spring +1 -0
  26. data/test/test_launcher/fixtures/minitest/test/different_root/test/different_root_test.rb +2 -0
  27. data/test/test_launcher/fixtures/rspec/spec/class_1_spec.rb +16 -0
  28. data/test/test_launcher/fixtures/rspec/spec/class_2_spec.rb +6 -0
  29. data/test/test_launcher/fixtures/rspec/spec/different_root/spec/different_root_spec.rb +2 -0
  30. data/test/test_launcher/minitest_integration_test.rb +67 -0
  31. data/test/test_launcher/rspec_integration_test.rb +67 -0
  32. data/test/test_launcher/rubymine_test.rb +48 -0
  33. data/test_launcher.gemspec +2 -3
  34. metadata +39 -44
  35. data/bin/test_runner +0 -1
  36. data/lib/test_launcher/searchers/git_searcher.rb +0 -38
  37. data/lib/test_launcher/tests/minitest/finder.rb +0 -50
  38. data/lib/test_launcher/tests/minitest/wrappers/multiple_files.rb +0 -23
  39. data/lib/test_launcher/tests/minitest/wrappers/multiple_roots.rb +0 -23
  40. data/lib/test_launcher/tests/minitest/wrappers/single_file.rb +0 -29
  41. data/lib/test_launcher/tests/minitest/wrappers/single_root.rb +0 -27
  42. data/lib/test_launcher/tests/minitest/wrappers/single_test.rb +0 -37
  43. data/lib/test_launcher/utils/path.rb +0 -32
  44. data/lib/test_launcher/utils/pluralize.rb +0 -14
  45. data/test/test_launcher/searchers/git_searcher_test.rb +0 -50
  46. data/test/test_launcher/tests/minitest/consolidator_integration_test.rb +0 -191
  47. data/test/test_launcher/tests/minitest/finder_test.rb +0 -71
@@ -0,0 +1,27 @@
1
+ require "delegate"
2
+
3
+ module TestLauncher
4
+ module Frameworks
5
+ module Implementation
6
+ class Collection < SimpleDelegator
7
+ alias :results :__getobj__
8
+
9
+ def file_count
10
+ results.group_by(&:file).size
11
+ end
12
+
13
+ def one_example?
14
+ examples_found? && results.size == 1
15
+ end
16
+
17
+ def examples_found?
18
+ results.any?(&:is_example?)
19
+ end
20
+
21
+ def last_edited
22
+ results.sort_by(&:mtime).last
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,15 +1,7 @@
1
- require "test_launcher/utils/path"
2
-
3
- require "test_launcher/tests/minitest/wrappers/single_test"
4
- require "test_launcher/tests/minitest/wrappers/single_file"
5
- require "test_launcher/tests/minitest/wrappers/multiple_files"
6
- require "test_launcher/utils/pluralize"
7
-
8
1
  module TestLauncher
9
- module Tests
10
- module Minitest
11
- class Consolidator < Struct.new(:search_results, :shell, :run_all)
12
- include Utils::Pluralize
2
+ module Frameworks
3
+ module Implementation
4
+ class Consolidator < Struct.new(:search_results, :shell, :runner, :run_all)
13
5
 
14
6
  def self.consolidate(*args)
15
7
  new(*args).consolidate
@@ -17,47 +9,44 @@ module TestLauncher
17
9
 
18
10
  def consolidate
19
11
  if search_results.empty?
20
- shell.warn "Could not find any tests."
21
- exit
22
- end
23
-
24
- if methods_found? && one_result?
12
+ nil
13
+ elsif one_example?
25
14
  shell.notify "Found #{methods_count_phrase} in #{file_count_phrase}."
26
- Wrappers::SingleTest.new(search_results.first)
27
- elsif methods_found? && same_file?
15
+ runner.single_example(search_results.first)
16
+ elsif examples_found? && same_file?
28
17
  shell.notify "Multiple test methods match in 1 file."
29
- Wrappers::SingleFile.new(search_results.first[:file])
30
- elsif methods_found? && run_last_edited?
18
+ runner.single_example(search_results.first)
19
+ elsif examples_found? && run_last_edited?
31
20
  shell.notify "Found #{methods_count_phrase} in #{file_count_phrase}."
32
21
  shell.notify "Running most recently edited. Run with '--all' to run all the tests."
33
- Wrappers::SingleTest.new(last_edited)
22
+ runner.single_example(last_edited)
34
23
  elsif files_found? && same_file?
35
24
  shell.notify "Found #{file_count_phrase}."
36
- Wrappers::SingleFile.new(search_results.first[:file])
25
+ runner.single_file(search_results.first)
37
26
  elsif files_found? && run_last_edited?
38
27
  shell.notify "Found #{file_count_phrase}."
39
28
  shell.notify "Running most recently edited. Run with '--all' to run all the tests."
40
- Wrappers::SingleFile.new(last_edited[:file])
29
+ runner.single_file(last_edited)
41
30
  else
42
31
  shell.notify "Found #{file_count_phrase}."
43
- Wrappers::MultipleFiles.wrap(search_results.map {|r| r[:file] }, shell)
32
+ runner.multiple_files(search_results.uniq {|sr| sr.file})
44
33
  end
45
34
  end
46
35
 
47
36
  def same_file?
48
- file_count == 1
37
+ file_count == 1
49
38
  end
50
39
 
51
- def one_result?
52
- same_file? && search_results.first[:line]
40
+ def one_example?
41
+ search_results.one_example?
53
42
  end
54
43
 
55
- def methods_found?
56
- !! search_results.first[:line]
44
+ def examples_found?
45
+ search_results.examples_found?
57
46
  end
58
47
 
59
48
  def files_found?
60
- ! methods_found?
49
+ ! examples_found?
61
50
  end
62
51
 
63
52
  def run_last_edited?
@@ -65,11 +54,11 @@ module TestLauncher
65
54
  end
66
55
 
67
56
  def last_edited
68
- search_results.sort_by {|r| File.mtime(r[:file])}.last
57
+ search_results.last_edited
69
58
  end
70
59
 
71
60
  def file_count
72
- search_results.group_by {|f| f[:file]}.size
61
+ search_results.file_count
73
62
  end
74
63
 
75
64
  def methods_count_phrase
@@ -79,6 +68,15 @@ module TestLauncher
79
68
  def file_count_phrase
80
69
  pluralize(file_count, "file")
81
70
  end
71
+
72
+ def pluralize(count, singular)
73
+ phrase = "#{count} #{singular}"
74
+ if count == 1
75
+ phrase
76
+ else
77
+ "#{phrase}s"
78
+ end
79
+ end
82
80
  end
83
81
  end
84
82
  end
@@ -0,0 +1,74 @@
1
+ require "test_launcher/frameworks/implementation/test_case"
2
+ require "test_launcher/frameworks/implementation/collection"
3
+
4
+ module TestLauncher
5
+ module Frameworks
6
+ module Implementation
7
+ class Locator < Struct.new(:query, :searcher)
8
+ private :query, :searcher
9
+
10
+ def prioritized_results
11
+ Collection.new(_prioritized_results)
12
+ end
13
+
14
+ def _prioritized_results
15
+ return files_found_by_absolute_path unless files_found_by_absolute_path.empty?
16
+
17
+ return examples_found_by_name unless examples_found_by_name.empty?
18
+
19
+ return files_found_by_file_name unless files_found_by_file_name.empty?
20
+
21
+ return files_found_by_full_regex unless files_found_by_full_regex.empty?
22
+
23
+ []
24
+ end
25
+
26
+ private
27
+
28
+ def files_found_by_absolute_path
29
+ return [] unless query.match(/^\//)
30
+
31
+ relative_file_path = query.sub(Dir.pwd, '').sub(/^\//, '')
32
+ [ build_result(file: relative_file_path) ]
33
+ end
34
+
35
+ def examples_found_by_name
36
+ @examples_found_by_name ||= full_regex_search(regex_pattern).map {|r| build_result(file: r[:file], query: query)}
37
+ end
38
+
39
+ def files_found_by_file_name
40
+ @files_found_by_file_name ||= searcher.find_files(query).select { |f| f.match(file_name_regex) }.map {|f| build_result(file: f) }
41
+ end
42
+
43
+ def files_found_by_full_regex
44
+ # we ignore the matched line since we don't know what to do with it
45
+ @files_found_by_full_regex ||= full_regex_search(query).map {|r| build_result(file: r[:file]) }
46
+ end
47
+
48
+ def full_regex_search(regex)
49
+ searcher.grep(regex, file_pattern: file_name_pattern)
50
+ end
51
+
52
+ def build_result(file:, query: nil)
53
+ test_case_class.from_search(file: file, query: query)
54
+ end
55
+
56
+ def file_name_regex
57
+ raise NotImplementedError
58
+ end
59
+
60
+ def file_name_pattern
61
+ raise NotImplementedError
62
+ end
63
+
64
+ def regex_pattern
65
+ raise NotImplementedError
66
+ end
67
+
68
+ def test_case_class
69
+ raise NotImplementedError
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,65 @@
1
+ require "pathname"
2
+
3
+ module TestLauncher
4
+ module Frameworks
5
+ module Implementation
6
+ class TestCase
7
+ attr_reader :file, :example
8
+
9
+ def self.from_search(file:, query: nil)
10
+ new(file: file, example: query)
11
+ end
12
+
13
+ def initialize(file:, example: nil)
14
+ @file = file
15
+ @example = example
16
+ end
17
+
18
+ def is_example?
19
+ !example.nil?
20
+ end
21
+
22
+ def mtime
23
+ @mtime ||= File.mtime(file)
24
+ end
25
+
26
+ def app_root
27
+ path = exploded_path[0...exploded_path.rindex(test_root_folder_name)]
28
+ File.join("/", path)
29
+ end
30
+
31
+ def test_root
32
+ File.join(app_root, test_root_folder_name)
33
+ end
34
+
35
+ def relative_test_path
36
+ path = exploded_path[exploded_path.rindex(test_root_folder_name)..-1]
37
+ File.join(path)
38
+ end
39
+
40
+ def spring_enabled?
41
+ return false if ENV['DISABLE_SPRING']
42
+
43
+ [
44
+ "bin/spring",
45
+ "bin/testunit"
46
+ ].any? {|f|
47
+ File.exist?(File.join(app_root, f))
48
+ }
49
+ end
50
+
51
+ def runner
52
+ raise NotImplementedError
53
+ end
54
+
55
+ def test_root_folder_name
56
+ raise NotImplementedError
57
+ end
58
+
59
+ def exploded_path
60
+ Pathname.new(file).each_filename.to_a
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,67 @@
1
+ require "test_launcher/frameworks/base"
2
+
3
+ module TestLauncher
4
+ module Frameworks
5
+ module Minitest
6
+
7
+ def self.active?
8
+ ! Dir.glob("**/test/**/*_test.rb").empty?
9
+ end
10
+
11
+ class Runner < Base::Runner
12
+ def single_example(test_case, exact_match: false)
13
+
14
+ name =
15
+ if exact_match
16
+ "--name=#{test_case.example}"
17
+ else
18
+ "--name=/#{test_case.example}/"
19
+ end
20
+
21
+ %{cd #{test_case.app_root} && #{test_case.runner} #{test_case.relative_test_path} #{name}}
22
+ end
23
+
24
+ def one_or_more_files(test_cases)
25
+ %{cd #{test_cases.first.app_root} && #{test_cases.first.runner} #{test_cases.map(&:relative_test_path).join(" ")}}
26
+ end
27
+ end
28
+
29
+ class Locator < Base::Locator
30
+ private
31
+
32
+ def file_name_regex
33
+ /.*_test\.rb/
34
+ end
35
+
36
+ def file_name_pattern
37
+ "*_test.rb"
38
+ end
39
+
40
+ def regex_pattern
41
+ "^\s*def test_.*#{query.sub(/^test_/, "")}.*"
42
+ end
43
+
44
+ def test_case_class
45
+ TestCase
46
+ end
47
+ end
48
+
49
+ class TestCase < Base::TestCase
50
+
51
+ def runner
52
+ if spring_enabled?
53
+ "bundle exec spring testunit"
54
+ elsif is_example?
55
+ "bundle exec ruby -I test"
56
+ else
57
+ "bundle exec ruby -I test -e 'ARGV.each {|f| require(File.join(Dir.pwd, f))}'"
58
+ end
59
+ end
60
+
61
+ def test_root_folder_name
62
+ "test"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,50 @@
1
+ require "shellwords"
2
+
3
+ require "test_launcher/frameworks/base"
4
+
5
+ module TestLauncher
6
+ module Frameworks
7
+ module RSpec
8
+
9
+ def self.active?
10
+ ! Dir.glob("**/*_spec.rb").empty?
11
+ end
12
+
13
+ class Runner < Base::Runner
14
+ def single_example(test_case)
15
+ %{cd #{test_case.app_root} && rspec #{test_case.relative_test_path} --example #{Shellwords.escape(test_case.example)}}
16
+ end
17
+
18
+ def one_or_more_files(test_cases)
19
+ %{cd #{test_cases.first.app_root} && rspec #{test_cases.map(&:relative_test_path).join(" ")}}
20
+ end
21
+ end
22
+
23
+ class Locator < Base::Locator
24
+ private
25
+
26
+ def file_name_regex
27
+ /.*_spec\.rb/
28
+ end
29
+
30
+ def file_name_pattern
31
+ '*_spec.rb'
32
+ end
33
+
34
+ def regex_pattern
35
+ "^\s*(it|context|(RSpec.)?describe) .*#{query}.* do.*"
36
+ end
37
+
38
+ def test_case_class
39
+ TestCase
40
+ end
41
+ end
42
+
43
+ class TestCase < Base::TestCase
44
+ def test_root_folder_name
45
+ "spec"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ require "test_launcher/rubymine/launcher"
2
+ require "test_launcher/shell/runner"
3
+
4
+ # To allow us to simply specify our run configuration as:
5
+ #
6
+ # -r test_launcher/rubymine
7
+ #
8
+ # we need to put the currently executing script in with the args.
9
+ #
10
+ # Consider the following examples:
11
+ #
12
+ # ruby -r test_launcher/rubymine /path/to/test.rb
13
+ #
14
+ # vs
15
+ #
16
+ # ruby -r test_launcher/rubymine spring testunit /path/to/test.rb
17
+ #
18
+ # In one case, our test to run is $0 and in another case it's an ARGV.
19
+ # So we throw them in the same bucket and let the launcher figure it
20
+ # out. It doesn't matter since we will `exec` a new command anyway.
21
+
22
+
23
+ TestLauncher::Rubymine::Launcher.new(
24
+ args: [$0].concat(ARGV),
25
+ shell: TestLauncher::Shell::Runner.new(log_path: "/dev/null")
26
+ ).launch
@@ -0,0 +1,55 @@
1
+ require "test_launcher/frameworks/minitest"
2
+ require "test_launcher/shell/runner"
3
+
4
+ module TestLauncher
5
+ module Rubymine
6
+ class Launcher
7
+ def initialize(args:, shell:)
8
+ @args = args
9
+ @shell = shell
10
+ end
11
+
12
+ def launch
13
+ if args.any? {|a| a.match("ruby-debug-ide")}
14
+ shell.puts "test_launcher: hijacking and debugging"
15
+
16
+ debug_command = "cd #{test_case.app_root} && ruby -I test #{args.join(" ")}"
17
+ shell.puts debug_command
18
+ shell.exec debug_command
19
+ else
20
+ shell.puts "test_launcher: hijacking and running:"
21
+ shell.puts command
22
+
23
+ shell.exec command
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def command
30
+ if test_case.is_example?
31
+ Frameworks::Minitest::Runner.new.single_example(test_case, exact_match: true)
32
+ else
33
+ Frameworks::Minitest::Runner.new.single_file(test_case)
34
+ end
35
+ end
36
+
37
+ def test_case
38
+ @test_case ||=
39
+ if args[-1].match('--name=')
40
+ Frameworks::Minitest::TestCase.new(file: args[-2], example: args[-1][/--name=(.*)/, 1])
41
+ else
42
+ Frameworks::Minitest::TestCase.new(file: args[-1])
43
+ end
44
+ end
45
+
46
+ def args
47
+ @args
48
+ end
49
+
50
+ def shell
51
+ @shell
52
+ end
53
+ end
54
+ end
55
+ end