Skip to content

Commit fbb37c0

Browse files
jsharpifyStivaros
andcommitted
railties/test_parser: Cache work done in definition_for.
`definition_for` is used to extract the line numbers of a test, which is relevant when filtering tests to a line number or range, eg my_test.rb:1-2. The filter itself operates by, for each test, comparing the lines the test spans against the filter. In the current code, this will call `Prism.parse_file` and then walk the parse result until the matching definition/call is found. For files with a large number of tests, this is fairly onerous and can dominate the test runtime. Fortunately, we can parse the file once and cache the starts and ends of every def/call node. This beings the `definition_for` runtime down from seconds to low milliseconds. We also see a reduction in gc time that is probably attributable to not repeatedly parsing the file. For a test run in an 240 test file that went from 11s of parsing to 20ms, gc went from 4.5-2.2 seconds. Finally, for any user that has written a reporter or otherwise hooked into the filters, this should provide speedup as well. Co-authored-by: Stath Stivaros <[email protected]>
1 parent c22f7f9 commit fbb37c0

File tree

1 file changed

+18
-15
lines changed

1 file changed

+18
-15
lines changed

railties/lib/rails/test_unit/test_parser.rb

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,32 @@ module TestUnit
1313
# Parse a test file to extract the line ranges of all tests in both
1414
# method-style (def test_foo) and declarative-style (test "foo" do)
1515
module TestParser
16+
@begins_to_ends = {}
1617
# Helper to translate a method object into the path and line range where
1718
# the method was defined.
1819
def self.definition_for(method)
1920
filepath, start_line = method.source_location
20-
queue = [Prism.parse_file(filepath).value]
21+
@begins_to_ends[filepath] ||= ranges(filepath)
22+
return unless end_line = @begins_to_ends[filepath][start_line]
23+
[filepath, start_line..end_line]
24+
end
2125

22-
while (node = queue.shift)
23-
case node.type
24-
when :def_node
25-
if node.location.start_line == start_line
26-
return [filepath, start_line..node.location.end_line]
26+
private
27+
def self.ranges(filepath)
28+
queue = [Prism.parse_file(filepath).value]
29+
begins_to_ends = {}
30+
while (node = queue.shift)
31+
case node.type
32+
when :def_node
33+
begins_to_ends[node.location.start_line] = node.location.end_line
34+
when :call_node
35+
begins_to_ends[node.location.start_line] = node.location.end_line
2736
end
28-
when :call_node
29-
if node.location.start_line == start_line
30-
return [filepath, start_line..node.location.end_line]
31-
end
32-
end
3337

34-
queue.concat(node.compact_child_nodes)
38+
queue.concat(node.compact_child_nodes)
39+
end
40+
begins_to_ends
3541
end
36-
37-
nil
38-
end
3942
end
4043
end
4144
end

0 commit comments

Comments
 (0)