diff --git a/lib/ruby_lsp/listeners/test_style.rb b/lib/ruby_lsp/listeners/test_style.rb index f78be22cc..b18ed7d32 100644 --- a/lib/ruby_lsp/listeners/test_style.rb +++ b/lib/ruby_lsp/listeners/test_style.rb @@ -33,13 +33,15 @@ def resolve_test_commands(items) if tags.include?("test_dir") if children.empty? - full_files.concat(Dir.glob( - "#{path}/**/{*_test,test_*,*_spec}.rb", - File::Constants::FNM_EXTGLOB | File::Constants::FNM_PATHNAME, - )) + full_files.concat( + Dir.glob( + "#{path}/**/{*_test,test_*,*_spec}.rb", + File::Constants::FNM_EXTGLOB | File::Constants::FNM_PATHNAME, + ).map! { |p| Shellwords.escape(p) }, + ) end elsif tags.include?("test_file") - full_files << path if children.empty? + full_files << Shellwords.escape(path) if children.empty? elsif tags.include?("test_group") # If all of the children of the current test group are other groups, then there's no need to add it to the # aggregated examples @@ -114,7 +116,7 @@ def handle_minitest_groups(file_path, groups_and_examples) end load_path = spec?(file_path) ? "-Ispec" : "-Itest" - "#{COMMAND} #{load_path} #{file_path} --name \"/#{regex}/\"" + "#{COMMAND} #{load_path} #{Shellwords.escape(file_path)} --name \"/#{regex}/\"" end #: (String, Hash[String, Hash[Symbol, untyped]]) -> Array[String] @@ -125,7 +127,7 @@ def handle_test_unit_groups(file_path, groups_and_examples) Shellwords.escape(TestDiscovery::DYNAMIC_REFERENCE_MARKER), ".*", ) - command = +"#{COMMAND} -Itest #{file_path} --testcase \"/^#{group_regex}\\$/\"" + command = +"#{COMMAND} -Itest #{Shellwords.escape(file_path)} --testcase \"/^#{group_regex}\\$/\"" unless examples.empty? command << if examples.length == 1 diff --git a/test/requests/resolve_test_commands_test.rb b/test/requests/resolve_test_commands_test.rb index a5e149327..b84848a46 100644 --- a/test/requests/resolve_test_commands_test.rb +++ b/test/requests/resolve_test_commands_test.rb @@ -753,6 +753,89 @@ def test_resolve_test_command_for_minitest_spec_with_backticks ) end end + + def test_resolve_properly_escapes_single_file_paths + with_server do |server| + server.process_message({ + id: 1, + method: "rubyLsp/resolveTestCommands", + params: { + items: [ + { + id: "file:///test/server(v2)_test.rb", + uri: "file:///test/server(v2)_test.rb", + label: "/test/server(v2)_test.rb", + tags: ["test_file", "framework:minitest"], + children: [], + }, + ], + }, + }) + + result = server.pop_response.response + assert_equal( + ["#{COMMAND} -Itest -e \"ARGV.each { |f| require f }\" /test/server\\(v2\\)_test.rb"], + result[:commands], + ) + end + end + + def test_resolve_properly_escapes_file_paths_within_directories + with_server do |server| + Dir.stubs(:glob).returns(["/other/test/fake(v2)_test.rb"]) + server.process_message({ + id: 1, + method: "rubyLsp/resolveTestCommands", + params: { + items: [ + { + id: "file:///other/test", + uri: "file:///other/test", + label: "/other/test", + tags: ["test_dir", "framework:minitest"], + children: [], + }, + ], + }, + }) + + result = server.pop_response.response + assert_equal( + ["#{COMMAND} -Itest -e \"ARGV.each { |f| require f }\" /other/test/fake\\(v2\\)_test.rb"], + result[:commands], + ) + end + end + + def test_resolve_properly_escapes_file_paths_in_groups + with_server do |server| + server.process_message({ + id: 1, + method: "rubyLsp/resolveTestCommands", + params: { + items: [ + { + id: "ServerTest", + uri: "file:///test/server(v2)_test.rb", + label: "ServerTest", + range: { + start: { line: 0, character: 0 }, + end: { line: 30, character: 3 }, + }, + tags: ["framework:minitest", "test_group"], + children: [], + }, + ], + }, + }) + + result = server.pop_response.response + assert_equal( + ["#{COMMAND} -Itest /test/server\\(v2\\)_test.rb --name \"/^ServerTest(#|::)/\""], + result[:commands], + ) + end + end end class ResolveTestCommandsTestUnitTest < Minitest::Test @@ -1254,6 +1337,89 @@ def test_resolve_test_command_mix_of_directories_and_examples ) end end + + def test_resolve_properly_escapes_single_file_paths + with_server do |server| + server.process_message({ + id: 1, + method: "rubyLsp/resolveTestCommands", + params: { + items: [ + { + id: "file:///test/server(v2)_test.rb", + uri: "file:///test/server(v2)_test.rb", + label: "/test/server(v2)_test.rb", + tags: ["test_file", "framework:test_unit"], + children: [], + }, + ], + }, + }) + + result = server.pop_response.response + assert_equal( + ["#{COMMAND} -Itest -e \"ARGV.each { |f| require f }\" /test/server\\(v2\\)_test.rb"], + result[:commands], + ) + end + end + + def test_resolve_properly_escapes_file_paths_within_directories + with_server do |server| + Dir.stubs(:glob).returns(["/other/test/fake(v2)_test.rb"]) + server.process_message({ + id: 1, + method: "rubyLsp/resolveTestCommands", + params: { + items: [ + { + id: "file:///other/test", + uri: "file:///other/test", + label: "/other/test", + tags: ["test_dir", "framework:test_unit"], + children: [], + }, + ], + }, + }) + + result = server.pop_response.response + assert_equal( + ["#{COMMAND} -Itest -e \"ARGV.each { |f| require f }\" /other/test/fake\\(v2\\)_test.rb"], + result[:commands], + ) + end + end + + def test_resolve_properly_escapes_file_paths_in_groups + with_server do |server| + server.process_message({ + id: 1, + method: "rubyLsp/resolveTestCommands", + params: { + items: [ + { + id: "ServerTest", + uri: "file:///test/server(v2)_test.rb", + label: "ServerTest", + range: { + start: { line: 0, character: 0 }, + end: { line: 30, character: 3 }, + }, + tags: ["framework:test_unit", "test_group"], + children: [], + }, + ], + }, + }) + + result = server.pop_response.response + assert_equal( + ["#{COMMAND} -Itest /test/server\\(v2\\)_test.rb --testcase \"/^ServerTest\\$/\""], + result[:commands], + ) + end + end end class ResolveTestCommandsOtherFrameworksTest < Minitest::Test