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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +101 -18
- data/Rakefile +6 -0
- data/bin/test_launcher +8 -35
- data/lib/test_launcher.rb +22 -0
- data/lib/test_launcher/cli/input_parser.rb +73 -0
- data/lib/test_launcher/frameworks.rb +32 -0
- data/lib/test_launcher/frameworks/base.rb +54 -0
- data/lib/test_launcher/frameworks/implementation/collection.rb +27 -0
- data/lib/test_launcher/{tests/minitest → frameworks/implementation}/consolidator.rb +30 -32
- data/lib/test_launcher/frameworks/implementation/locator.rb +74 -0
- data/lib/test_launcher/frameworks/implementation/test_case.rb +65 -0
- data/lib/test_launcher/frameworks/minitest.rb +67 -0
- data/lib/test_launcher/frameworks/rspec.rb +50 -0
- data/lib/test_launcher/rubymine.rb +26 -0
- data/lib/test_launcher/rubymine/launcher.rb +55 -0
- data/lib/test_launcher/search/git.rb +64 -0
- data/lib/test_launcher/shell/runner.rb +19 -22
- data/lib/test_launcher/version.rb +1 -1
- data/test/install +3 -0
- data/test/test_helper.rb +32 -17
- data/test/test_launcher/fixtures/minitest/test/class_1_test.rb +17 -0
- data/test/test_launcher/fixtures/minitest/test/class_2_test.rb +5 -0
- data/test/test_launcher/fixtures/minitest/test/different_root/bin/spring +1 -0
- data/test/test_launcher/fixtures/minitest/test/different_root/test/different_root_test.rb +2 -0
- data/test/test_launcher/fixtures/rspec/spec/class_1_spec.rb +16 -0
- data/test/test_launcher/fixtures/rspec/spec/class_2_spec.rb +6 -0
- data/test/test_launcher/fixtures/rspec/spec/different_root/spec/different_root_spec.rb +2 -0
- data/test/test_launcher/minitest_integration_test.rb +67 -0
- data/test/test_launcher/rspec_integration_test.rb +67 -0
- data/test/test_launcher/rubymine_test.rb +48 -0
- data/test_launcher.gemspec +2 -3
- metadata +39 -44
- data/bin/test_runner +0 -1
- data/lib/test_launcher/searchers/git_searcher.rb +0 -38
- data/lib/test_launcher/tests/minitest/finder.rb +0 -50
- data/lib/test_launcher/tests/minitest/wrappers/multiple_files.rb +0 -23
- data/lib/test_launcher/tests/minitest/wrappers/multiple_roots.rb +0 -23
- data/lib/test_launcher/tests/minitest/wrappers/single_file.rb +0 -29
- data/lib/test_launcher/tests/minitest/wrappers/single_root.rb +0 -27
- data/lib/test_launcher/tests/minitest/wrappers/single_test.rb +0 -37
- data/lib/test_launcher/utils/path.rb +0 -32
- data/lib/test_launcher/utils/pluralize.rb +0 -14
- data/test/test_launcher/searchers/git_searcher_test.rb +0 -50
- data/test/test_launcher/tests/minitest/consolidator_integration_test.rb +0 -191
- 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
|
10
|
-
module
|
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
|
-
|
21
|
-
|
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
|
-
|
27
|
-
elsif
|
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
|
-
|
30
|
-
elsif
|
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
|
-
|
22
|
+
runner.single_example(last_edited)
|
34
23
|
elsif files_found? && same_file?
|
35
24
|
shell.notify "Found #{file_count_phrase}."
|
36
|
-
|
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
|
-
|
29
|
+
runner.single_file(last_edited)
|
41
30
|
else
|
42
31
|
shell.notify "Found #{file_count_phrase}."
|
43
|
-
|
32
|
+
runner.multiple_files(search_results.uniq {|sr| sr.file})
|
44
33
|
end
|
45
34
|
end
|
46
35
|
|
47
36
|
def same_file?
|
48
|
-
|
37
|
+
file_count == 1
|
49
38
|
end
|
50
39
|
|
51
|
-
def
|
52
|
-
|
40
|
+
def one_example?
|
41
|
+
search_results.one_example?
|
53
42
|
end
|
54
43
|
|
55
|
-
def
|
56
|
-
|
44
|
+
def examples_found?
|
45
|
+
search_results.examples_found?
|
57
46
|
end
|
58
47
|
|
59
48
|
def files_found?
|
60
|
-
!
|
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.
|
57
|
+
search_results.last_edited
|
69
58
|
end
|
70
59
|
|
71
60
|
def file_count
|
72
|
-
search_results.
|
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
|