test_launcher 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|