From 105e05e89168c8f5a5110955ddef7bf87bb4c0a6 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 7 Feb 2024 23:45:12 -0500 Subject: [PATCH] Parse tests with prism This changes TestParser to parse with prism instead of ripper if it is available for the current version of Ruby. It's within the margin for the speed, and its significantly less code that is easier to read and should be easier to maintain. --- railties/lib/rails/test_unit/test_parser.rb | 39 ++++---- railties/test/test_unit/test_parser_test.rb | 98 +++++++++++---------- 2 files changed, 67 insertions(+), 70 deletions(-) diff --git a/railties/lib/rails/test_unit/test_parser.rb b/railties/lib/rails/test_unit/test_parser.rb index 328ae9caf4..5644d9b0c9 100644 --- a/railties/lib/rails/test_unit/test_parser.rb +++ b/railties/lib/rails/test_unit/test_parser.rb @@ -12,38 +12,29 @@ module Rails module TestUnit # Parse a test file to extract the line ranges of all tests in both # method-style (def test_foo) and declarative-style (test "foo" do) - class TestParser < Prism::Visitor + module TestParser # Helper to translate a method object into the path and line range where # the method was defined. def self.definition_for(method) filepath, start_line = method.source_location - Prism.parse_file(filepath).value.accept(new(ranges = {})) - (end_line = ranges[start_line]) && [filepath, (start_line..end_line)] - end + queue = [Prism.parse_file(filepath).value] - def self.definitions_for(source, filepath) - Prism.parse(source, filepath: filepath).value.accept(new(ranges = {})) - ranges - end + while (node = queue.shift) + case node.type + when :def_node + if node.name.start_with?("test") && node.location.start_line == start_line + return [filepath, start_line..node.location.end_line] + end + when :call_node + if node.name == :test && node.location.start_line == start_line + return [filepath, start_line..node.location.end_line] + end + end - attr_reader :ranges - - def initialize(ranges) - @ranges = ranges - end - - def visit_def_node(node) - if node.name.start_with?("test") - ranges[node.location.start_line] = node.location.end_line + queue.concat(node.compact_child_nodes) end - super - end - def visit_call_node(node) - if node.name == :test - ranges[node.location.start_line] = node.location.end_line - end - super + nil end end end diff --git a/railties/test/test_unit/test_parser_test.rb b/railties/test/test_unit/test_parser_test.rb index 72474bc8df..5013deba7d 100644 --- a/railties/test/test_unit/test_parser_test.rb +++ b/railties/test/test_unit/test_parser_test.rb @@ -5,55 +5,61 @@ require "active_support/testing/autorun" require "rails/test_unit/test_parser" +class TestParserTestFixture < ActiveSupport::TestCase + def test_method + assert true + + assert true + end + + def test_oneline; assert true; end + + test "declarative" do + assert true + + assert true + end + + test("declarative w/parens") do + assert true + end + + self.test "declarative explicit receiver" do + assert true + + assert true + end + + test("declarative oneline") { assert true } + + test("declarative oneline do") do assert true end + + test("declarative multiline w/ braces") { + assert true + assert_not false + } +end + class TestParserTest < ActiveSupport::TestCase def test_parser - example_test = <<~RUBY - require "test_helper" + actual = + TestParserTestFixture + .instance_methods(false) + .map { |method| TestParserTestFixture.instance_method(method) } + .sort_by { |method| method.source_location[1] } + .map { |method| [method.name, *Rails::TestUnit::TestParser.definition_for(method)] } - class ExampleTest < ActiveSupport::TestCase - def test_method - assert true + expected = [ + [:test_method, __FILE__, 9..13], + [:test_oneline, __FILE__, 15..15], + [:test_declarative, __FILE__, 17..21], + [:"test_declarative_w/parens", __FILE__, 23..25], + [:test_declarative_explicit_receiver, __FILE__, 27..31], + [:test_declarative_oneline, __FILE__, 33..33], + [:test_declarative_oneline_do, __FILE__, 35..35], + [:"test_declarative_multiline_w/_braces", __FILE__, 37..40] + ] - - end - - def test_oneline; assert true; end - - test "declarative" do - assert true - end - - test("declarative w/parens") do - assert true - - end - - self.test "declarative explicit receiver" do - assert true - end - - test("declarative oneline") { assert true } - - test("declarative oneline do") do assert true end - - test("declarative multiline w/ braces") { - assert true - refute false - } - end - RUBY - - actual_map = Rails::TestUnit::TestParser.definitions_for(example_test, "example_test.rb") - expected_map = { - 4 => 8, # test_method - 10 => 10, # test_oneline - 12 => 14, # declarative - 16 => 19, # declarative w/parens - 21 => 23, # declarative explicit receiver - 25 => 25, # declarative oneline - 27 => 27, # declarative oneilne do - 29 => 32 # declarative multiline w/braces - } - assert_equal expected_map, actual_map + assert_equal expected, actual end end