Skip to content

Commit 0c4d46c

Browse files
authored
Merge pull request Shopify#3731 from domingo2000/improve-go-to-relevant-file-performance
Improve go to relevant file performance
2 parents b2afe05 + 81658a2 commit 0c4d46c

File tree

2 files changed

+130
-128
lines changed

2 files changed

+130
-128
lines changed

lib/ruby_lsp/requests/go_to_relevant_file.rb

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,46 @@ def perform
3535

3636
#: -> Array[String]
3737
def find_relevant_paths
38-
candidate_paths = Dir.glob(File.join("**", relevant_filename_pattern))
38+
pattern = File.join(search_root, "**", relevant_filename_pattern)
39+
candidate_paths = Dir.glob(pattern)
40+
3941
return [] if candidate_paths.empty?
4042

41-
find_most_similar_with_jaccard(candidate_paths).map { File.join(@workspace_path, _1) }
43+
find_most_similar_with_jaccard(candidate_paths).map { |path| File.expand_path(path, @workspace_path) }
44+
end
45+
46+
# Determine the search roots based on the closest test directories.
47+
# This scopes the search to reduce the number of files that need to be checked.
48+
#: -> String
49+
def search_root
50+
current_path = File.join(".", @path)
51+
current_dir = File.dirname(current_path)
52+
while current_dir != "."
53+
dir_basename = File.basename(current_dir)
54+
55+
# If current directory is a test directory, return its parent as search root
56+
if TEST_KEYWORDS.include?(dir_basename)
57+
return File.dirname(current_dir)
58+
end
59+
60+
# Search the test directories by walking up the directory tree
61+
begin
62+
contains_test_dir = Dir
63+
.entries(current_dir)
64+
.filter { |entry| TEST_KEYWORDS.include?(entry) }
65+
.any? { |entry| File.directory?(File.join(current_dir, entry)) }
66+
67+
return current_dir if contains_test_dir
68+
rescue Errno::EACCES, Errno::ENOENT
69+
# Skip directories we can't read
70+
end
71+
72+
# Move up one level
73+
parent_dir = File.dirname(current_dir)
74+
current_dir = parent_dir
75+
end
76+
77+
"."
4278
end
4379

4480
#: -> String

test/requests/go_to_relevant_file_test.rb

Lines changed: 92 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -4,151 +4,117 @@
44
require "test_helper"
55

66
class GoToRelevantFileTest < Minitest::Test
7-
def test_when_input_is_test_file_returns_array_of_implementation_file_locations
8-
stub_glob_pattern("**/go_to_relevant_file.rb", ["lib/ruby_lsp/requests/go_to_relevant_file.rb"])
9-
10-
test_file_path = "/workspace/test/requests/go_to_relevant_file_test.rb"
11-
expected = ["/workspace/lib/ruby_lsp/requests/go_to_relevant_file.rb"]
12-
13-
result = RubyLsp::Requests::GoToRelevantFile.new(test_file_path, "/workspace").perform
14-
assert_equal(expected, result)
15-
end
16-
17-
def test_when_input_is_implementation_file_returns_array_of_test_file_locations
18-
pattern =
19-
"**/{{test_,spec_,integration_test_}go_to_relevant_file,go_to_relevant_file{_test,_spec,_integration_test}}.rb"
20-
stub_glob_pattern(pattern, ["test/requests/go_to_relevant_file_test.rb"])
21-
22-
impl_path = "/workspace/lib/ruby_lsp/requests/go_to_relevant_file.rb"
23-
expected = ["/workspace/test/requests/go_to_relevant_file_test.rb"]
24-
25-
result = RubyLsp::Requests::GoToRelevantFile.new(impl_path, "/workspace").perform
26-
assert_equal(expected, result)
27-
end
28-
29-
def test_return_all_file_locations_that_have_the_same_highest_coefficient
30-
pattern = "**/{{test_,spec_,integration_test_}some_feature,some_feature{_test,_spec,_integration_test}}.rb"
31-
matches = [
32-
"test/unit/some_feature_test.rb",
33-
"test/integration/some_feature_test.rb",
34-
]
35-
stub_glob_pattern(pattern, matches)
36-
37-
impl_path = "/workspace/lib/ruby_lsp/requests/some_feature.rb"
38-
expected = [
39-
"/workspace/test/unit/some_feature_test.rb",
40-
"/workspace/test/integration/some_feature_test.rb",
41-
]
42-
43-
result = RubyLsp::Requests::GoToRelevantFile.new(impl_path, "/workspace").perform
44-
assert_equal(expected.sort, result.sort)
45-
end
46-
47-
def test_return_empty_array_when_no_filename_matches
48-
pattern = "**/{{test_,spec_,integration_test_}nonexistent_file,nonexistent_file{_test,_spec,_integration_test}}.rb"
49-
stub_glob_pattern(pattern, [])
50-
51-
file_path = "/workspace/lib/ruby_lsp/requests/nonexistent_file.rb"
52-
result = RubyLsp::Requests::GoToRelevantFile.new(file_path, "/workspace").perform
53-
assert_empty(result)
54-
end
55-
56-
def test_it_finds_implementation_when_file_has_test_suffix
57-
stub_glob_pattern("**/feature.rb", ["lib/feature.rb"])
58-
59-
test_path = "/workspace/test/feature_test.rb"
60-
expected = ["/workspace/lib/feature.rb"]
61-
62-
result = RubyLsp::Requests::GoToRelevantFile.new(test_path, "/workspace").perform
63-
assert_equal(expected, result)
7+
def setup
8+
@workspace = Dir.mktmpdir
649
end
6510

66-
def test_it_finds_implementation_when_file_has_spec_suffix
67-
stub_glob_pattern("**/feature.rb", ["lib/feature.rb"])
68-
69-
test_path = "/workspace/spec/feature_spec.rb"
70-
expected = ["/workspace/lib/feature.rb"]
71-
72-
result = RubyLsp::Requests::GoToRelevantFile.new(test_path, "/workspace").perform
73-
assert_equal(expected, result)
11+
def teardown
12+
FileUtils.remove_entry(@workspace)
7413
end
7514

76-
def test_it_finds_implementation_when_file_has_integration_test_suffix
77-
stub_glob_pattern("**/feature.rb", ["lib/feature.rb"])
78-
79-
test_path = "/workspace/test/feature_integration_test.rb"
80-
expected = ["/workspace/lib/feature.rb"]
81-
82-
result = RubyLsp::Requests::GoToRelevantFile.new(test_path, "/workspace").perform
83-
assert_equal(expected, result)
84-
end
85-
86-
def test_it_finds_implementation_when_file_has_test_prefix
87-
stub_glob_pattern("**/feature.rb", ["lib/feature.rb"])
88-
89-
test_path = "/workspace/test/test_feature.rb"
90-
expected = ["/workspace/lib/feature.rb"]
91-
92-
result = RubyLsp::Requests::GoToRelevantFile.new(test_path, "/workspace").perform
93-
assert_equal(expected, result)
15+
def test_when_input_is_test_file_returns_array_of_implementation_file_locations
16+
Dir.chdir(@workspace) do
17+
lib_dir = File.join(@workspace, "lib/ruby_lsp/requests")
18+
test_dir = File.join(@workspace, "test/requests")
19+
FileUtils.mkdir_p(lib_dir)
20+
FileUtils.mkdir_p(test_dir)
21+
22+
impl_file = File.join(lib_dir, "go_to_relevant_file.rb")
23+
test_file = File.join(test_dir, "go_to_relevant_file_test.rb")
24+
FileUtils.touch(impl_file)
25+
FileUtils.touch(test_file)
26+
27+
result = RubyLsp::Requests::GoToRelevantFile.new(test_file, @workspace).perform
28+
assert_equal([impl_file], result)
29+
end
9430
end
9531

96-
def test_it_finds_implementation_when_file_has_spec_prefix
97-
stub_glob_pattern("**/feature.rb", ["lib/feature.rb"])
98-
99-
test_path = "/workspace/test/spec_feature.rb"
100-
expected = ["/workspace/lib/feature.rb"]
101-
102-
result = RubyLsp::Requests::GoToRelevantFile.new(test_path, "/workspace").perform
103-
assert_equal(expected, result)
32+
def test_when_input_is_implementation_file_returns_array_of_test_file_locations
33+
Dir.chdir(@workspace) do
34+
lib_dir = File.join(@workspace, "lib/ruby_lsp/requests")
35+
test_dir = File.join(@workspace, "test/requests")
36+
FileUtils.mkdir_p(lib_dir)
37+
FileUtils.mkdir_p(test_dir)
38+
39+
impl_file = File.join(lib_dir, "go_to_relevant_file.rb")
40+
test_file = File.join(test_dir, "go_to_relevant_file_test.rb")
41+
FileUtils.touch(impl_file)
42+
FileUtils.touch(test_file)
43+
44+
result = RubyLsp::Requests::GoToRelevantFile.new(impl_file, @workspace).perform
45+
assert_equal([test_file], result)
46+
end
10447
end
10548

106-
def test_it_finds_implementation_when_file_has_integration_test_prefix
107-
stub_glob_pattern("**/feature.rb", ["lib/feature.rb"])
49+
def test_return_empty_array_when_no_filename_matches
50+
Dir.chdir(@workspace) do
51+
lib_dir = File.join(@workspace, "lib/ruby_lsp/requests")
52+
FileUtils.mkdir_p(lib_dir)
10853

109-
test_path = "/workspace/test/integration_test_feature.rb"
110-
expected = ["/workspace/lib/feature.rb"]
54+
impl_file = File.join(lib_dir, "nonexistent_file.rb")
55+
FileUtils.touch(impl_file)
11156

112-
result = RubyLsp::Requests::GoToRelevantFile.new(test_path, "/workspace").perform
113-
assert_equal(expected, result)
57+
result = RubyLsp::Requests::GoToRelevantFile.new(impl_file, @workspace).perform
58+
assert_empty(result)
59+
end
11460
end
11561

116-
def test_it_finds_tests_for_implementation
117-
pattern = "**/{{test_,spec_,integration_test_}feature,feature{_test,_spec,_integration_test}}.rb"
118-
stub_glob_pattern(pattern, ["test/feature_test.rb"])
119-
120-
impl_path = "/workspace/lib/feature.rb"
121-
expected = ["/workspace/test/feature_test.rb"]
122-
123-
result = RubyLsp::Requests::GoToRelevantFile.new(impl_path, "/workspace").perform
124-
assert_equal(expected, result)
62+
def test_it_finds_multiple_matching_tests
63+
Dir.chdir(@workspace) do
64+
lib_dir = File.join(@workspace, "lib/ruby_lsp/requests")
65+
test_root = File.join(@workspace, "test") # ensure top-level test dir exists
66+
test_unit = File.join(test_root, "unit")
67+
test_int = File.join(test_root, "integration")
68+
69+
FileUtils.mkdir_p(lib_dir)
70+
FileUtils.mkdir_p(test_unit)
71+
FileUtils.mkdir_p(test_int)
72+
73+
impl_file = File.join(lib_dir, "some_feature.rb")
74+
unit_test = File.join(test_unit, "some_feature_test.rb")
75+
int_test = File.join(test_int, "some_feature_test.rb")
76+
FileUtils.touch(impl_file)
77+
FileUtils.touch(unit_test)
78+
FileUtils.touch(int_test)
79+
80+
result = RubyLsp::Requests::GoToRelevantFile.new(impl_file, @workspace).perform
81+
82+
assert_equal(
83+
[unit_test, int_test].sort,
84+
result.sort,
85+
)
86+
end
12587
end
12688

127-
def test_it_finds_specs_for_implementation
128-
pattern = "**/{{test_,spec_,integration_test_}feature,feature{_test,_spec,_integration_test}}.rb"
129-
stub_glob_pattern(pattern, ["spec/feature_spec.rb"])
130-
131-
impl_path = "/workspace/lib/feature.rb"
132-
expected = ["/workspace/spec/feature_spec.rb"]
89+
def test_search_within_implementation_test_root
90+
Dir.chdir(@workspace) do
91+
lib_a_dir = File.join(@workspace, "a")
92+
lib_a_test_dir = File.join(@workspace, "a", "test")
93+
FileUtils.mkdir_p(lib_a_dir)
94+
FileUtils.mkdir_p(lib_a_test_dir)
13395

134-
result = RubyLsp::Requests::GoToRelevantFile.new(impl_path, "/workspace").perform
135-
assert_equal(expected, result)
136-
end
96+
lib_b_dir = File.join(@workspace, "b")
97+
lib_b_test_dir = File.join(@workspace, "b", "test")
98+
FileUtils.mkdir_p(lib_b_dir)
99+
FileUtils.mkdir_p(lib_b_test_dir)
137100

138-
def test_it_finds_integration_tests_for_implementation
139-
pattern = "**/{{test_,spec_,integration_test_}feature,feature{_test,_spec,_integration_test}}.rb"
140-
stub_glob_pattern(pattern, ["test/feature_integration_test.rb"])
101+
impl_a_file = File.join(lib_a_dir, "implementation.rb")
102+
unit_a_test = File.join(lib_a_test_dir, "implementation_test.rb")
141103

142-
impl_path = "/workspace/lib/feature.rb"
143-
expected = ["/workspace/test/feature_integration_test.rb"]
104+
impl_b_file = File.join(lib_b_dir, "implementation.rb")
105+
unit_b_test = File.join(lib_b_test_dir, "implementation_test.rb")
144106

145-
result = RubyLsp::Requests::GoToRelevantFile.new(impl_path, "/workspace").perform
146-
assert_equal(expected, result)
147-
end
107+
FileUtils.touch(impl_a_file)
108+
FileUtils.touch(unit_a_test)
109+
FileUtils.touch(impl_b_file)
110+
FileUtils.touch(unit_b_test)
148111

149-
private
112+
result = RubyLsp::Requests::GoToRelevantFile.new(impl_a_file, @workspace).perform
150113

151-
def stub_glob_pattern(pattern, matches)
152-
Dir.stubs(:glob).with(pattern).returns(matches)
114+
assert_equal(
115+
[unit_a_test].sort,
116+
result.sort,
117+
)
118+
end
153119
end
154120
end

0 commit comments

Comments
 (0)