Skip to content

Commit 81658a2

Browse files
committed
Improve go to relevant file performance
By doing a walk over the tree of directories in the workpsace we improve the dir Glob time because it only checks for the closest test directory folder, instead of searching in all the files of the workspace.
1 parent 8cbd8f8 commit 81658a2

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)