From 4e7bc71e62704353e7e25cdf3f7f1dc2b94dfb60 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Fri, 25 Jul 2025 12:00:19 -0400 Subject: [PATCH] Allow running tests without a bundle --- lib/ruby_lsp/listeners/test_discovery.rb | 25 +++++++++++++++++++----- lib/ruby_lsp/listeners/test_style.rb | 2 +- test/requests/discover_tests_test.rb | 23 ++++++++++++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/lib/ruby_lsp/listeners/test_discovery.rb b/lib/ruby_lsp/listeners/test_discovery.rb index 0dfb222800..18c4dc8ed4 100644 --- a/lib/ruby_lsp/listeners/test_discovery.rb +++ b/lib/ruby_lsp/listeners/test_discovery.rb @@ -61,11 +61,26 @@ def calc_fully_qualified_name(name) #: (Prism::ClassNode node, String fully_qualified_name) -> Array[String] def calc_attached_ancestors(node, fully_qualified_name) - @index.linearized_ancestors_of(fully_qualified_name) - rescue RubyIndexer::Index::NonExistingNamespaceError - # When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still - # provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test - [node.superclass&.slice].compact + superclass = node.superclass + + begin + ancestors = @index.linearized_ancestors_of(fully_qualified_name) + # If the project has no bundle and a test class inherits from `Minitest::Test`, the linearized ancestors will + # not include the parent class because we never indexed it in the first place. Here we add the superclass + # directly, so that we can support running tests in projects without a bundle + return ancestors if ancestors.length > 1 + + # If all we found is the class itself, then manually include the parent class + if ancestors.first == fully_qualified_name && superclass + return [*ancestors, superclass.slice] + end + + ancestors + rescue RubyIndexer::Index::NonExistingNamespaceError + # When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still + # provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test + [superclass&.slice].compact + end end #: (Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode node) -> String diff --git a/lib/ruby_lsp/listeners/test_style.rb b/lib/ruby_lsp/listeners/test_style.rb index cec87830e2..f78be22cc5 100644 --- a/lib/ruby_lsp/listeners/test_style.rb +++ b/lib/ruby_lsp/listeners/test_style.rb @@ -145,7 +145,7 @@ def handle_test_unit_groups(file_path, groups_and_examples) MINITEST_REPORTER_PATH = File.expand_path("../test_reporters/minitest_reporter.rb", __dir__) #: String TEST_UNIT_REPORTER_PATH = File.expand_path("../test_reporters/test_unit_reporter.rb", __dir__) #: String BASE_COMMAND = begin - Bundler.with_original_env { Bundler.default_lockfile } + Bundler.with_unbundled_env { Bundler.default_lockfile } "bundle exec ruby" rescue Bundler::GemfileNotFound "ruby" diff --git a/test/requests/discover_tests_test.rb b/test/requests/discover_tests_test.rb index c3a96fec29..51ca7357fe 100644 --- a/test/requests/discover_tests_test.rb +++ b/test/requests/discover_tests_test.rb @@ -769,6 +769,29 @@ def test_spec_using_describe_with_additional_descriptions end end + def test_can_discover_tests_even_if_parent_class_was_not_indexed + source = <<~RUBY + class MyTest < Minitest::Test + def test_something; end + end + RUBY + + with_server(source) do |server, uri| + server.process_message(id: 1, method: "rubyLsp/discoverTests", params: { + textDocument: { uri: uri }, + }) + + items = get_response(server) + + assert_equal( + ["MyTest"], + items.map { |i| i[:id] }, + ) + assert_equal(["MyTest#test_something"], items[0][:children].map { |i| i[:id] }) + assert_all_items_tagged_with(items, :minitest) + end + end + private def create_test_discovery_addon