ruby/test/yarp/language_server_test.rb
Jemma Issroff cc7f765f2c [Feature #19741] Sync all files in yarp
This commit is the initial sync of all files from ruby/yarp
into ruby/ruby. Notably, it does the following:

* Sync all ruby/yarp/lib/ files to ruby/ruby/lib/yarp
* Sync all ruby/yarp/src/ files to ruby/ruby/yarp/
* Sync all ruby/yarp/test/ files to ruby/ruby/test/yarp
2023-06-21 11:25:39 -07:00

363 lines
9 KiB
Ruby

# frozen_string_literal: true
require_relative "yarp_test_helper"
require "yarp/language_server"
module YARP
class LanguageServerTest < Test::Unit::TestCase
module Request
# Represents a hash pattern.
class Shape
attr_reader :values
def initialize(values)
@values = values
end
def ===(other)
values.all? do |key, value|
value == :any ? other.key?(key) : value === other[key]
end
end
end
# Represents an array pattern.
class Tuple
attr_reader :values
def initialize(values)
@values = values
end
def ===(other)
values.each_with_index.all? { |value, index| value === other[index] }
end
end
def self.[](value)
case value
when Array
Tuple.new(value.map { |child| self[child] })
when Hash
Shape.new(value.transform_values { |child| self[child] })
else
value
end
end
end
class Initialize < Struct.new(:id)
def to_hash
{ method: "initialize", id: id }
end
end
class Shutdown < Struct.new(:id)
def to_hash
{ method: "shutdown", id: id }
end
end
class TextDocumentDidOpen < Struct.new(:uri, :text)
def to_hash
{
method: "textDocument/didOpen",
params: { textDocument: { uri: uri, text: text } }
}
end
end
class TextDocumentDidChange < Struct.new(:uri, :text)
def to_hash
{
method: "textDocument/didChange",
params: {
textDocument: { uri: uri },
contentChanges: [{ text: text }]
}
}
end
end
class TextDocumentDidClose < Struct.new(:uri)
def to_hash
{
method: "textDocument/didClose",
params: { textDocument: { uri: uri } }
}
end
end
class TextDocumentCodeAction < Struct.new(:id, :uri, :diagnostics)
def to_hash
{
method: "textDocument/codeAction",
id: id,
params: {
textDocument: { uri: uri },
context: {
diagnostics: diagnostics,
},
},
}
end
end
class TextDocumentDiagnostic < Struct.new(:id, :uri)
def to_hash
{
method: "textDocument/diagnostic",
id: id,
params: {
textDocument: { uri: uri },
}
}
end
end
def test_reading_file
Tempfile.create(%w[test- .rb]) do |file|
file.write("class Foo; end")
file.rewind
responses = run_server([
Initialize.new(1),
Shutdown.new(3)
])
shape = Request[[
{ id: 1, result: { capabilities: Hash } },
{ id: 3, result: {} }
]]
assert_operator(shape, :===, responses)
end
end
def test_clean_shutdown
responses = run_server([Initialize.new(1), Shutdown.new(2)])
shape = Request[[
{ id: 1, result: { capabilities: Hash } },
{ id: 2, result: {} }
]]
assert_operator(shape, :===, responses)
end
def test_file_that_does_not_exist
responses = run_server([
Initialize.new(1),
Shutdown.new(3)
])
shape = Request[[
{ id: 1, result: { capabilities: Hash } },
{ id: 3, result: {} }
]]
assert_operator(shape, :===, responses)
end
def test_code_action_request
message = "this is an error"
diagnostic = {
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
message: message,
severity: 1,
}
responses = run_server([
Initialize.new(1),
TextDocumentDidOpen.new("file:///path/to/file.rb", <<~RUBY),
1 + (
RUBY
TextDocumentCodeAction.new(2, "file:///path/to/file.rb", [diagnostic]),
Shutdown.new(3)
])
shape = Request[[
{ id: 1, result: { capabilities: Hash } },
{ id: 2, result: [
{
title: "Report incorrect error: `#{message}`",
kind: "quickfix",
diagnostics: [diagnostic],
command: {
title: "Report incorrect error",
command: "vscode.open",
arguments: [String]
}
}
],
},
{ id: 3, result: {} }
]]
assert_operator(shape, :===, responses)
assert(responses.dig(1, :result, 0, :command, :arguments, 0).include?(URI.encode_www_form_component(message)))
end
def test_code_action_request_no_diagnostic
responses = run_server([
Initialize.new(1),
TextDocumentDidOpen.new("file:///path/to/file.rb", <<~RUBY),
1 + (
RUBY
TextDocumentCodeAction.new(2, "file:///path/to/file.rb", []),
Shutdown.new(3)
])
shape = Request[[
{ id: 1, result: { capabilities: Hash } },
{ id: 2, result: [] },
{ id: 3, result: {} }
]]
assert_operator(shape, :===, responses)
end
def test_code_action_request_no_content
message = "this is an error"
diagnostic = {
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
message: message,
severity: 1,
}
responses = run_server([
Initialize.new(1),
TextDocumentCodeAction.new(2, "file:///path/to/file.rb", [diagnostic]),
Shutdown.new(3)
])
shape = Request[[
{ id: 1, result: { capabilities: Hash } },
{ id: 2, result: nil },
{ id: 3, result: {} }
]]
assert_operator(shape, :===, responses)
end
def test_diagnostics_request_error
responses = run_server([
Initialize.new(1),
TextDocumentDidOpen.new("file:///path/to/file.rb", <<~RUBY),
1 + (
RUBY
TextDocumentDiagnostic.new(2, "file:///path/to/file.rb"),
Shutdown.new(3)
])
shape = Request[[
{ id: 1, result: { capabilities: Hash } },
{ id: 2, result: { kind: "full", items: [
{
range: {
start: { line: Integer, character: Integer },
end: { line: Integer, character: Integer }
},
message: String,
severity: Integer
},
] } },
{ id: 3, result: {} }
]]
assert_operator(shape, :===, responses)
assert(responses.dig(1, :result, :items).count { |item| item[:severity] == 1 } > 0)
end
def test_diagnostics_request_warning
responses = run_server([
Initialize.new(1),
TextDocumentDidOpen.new("file:///path/to/file.rb", <<~RUBY),
a/b /c
RUBY
TextDocumentDiagnostic.new(2, "file:///path/to/file.rb"),
Shutdown.new(3)
])
shape = Request[[
{ id: 1, result: { capabilities: Hash } },
{ id: 2, result: { kind: "full", items: [
{
range: {
start: { line: Integer, character: Integer },
end: { line: Integer, character: Integer }
},
message: String,
severity: Integer
},
] } },
{ id: 3, result: {} }
]]
assert_operator(shape, :===, responses)
assert(responses.dig(1, :result, :items).count { |item| item[:severity] == 2 } > 0)
end
def test_diagnostics_request_nothing
responses = run_server([
Initialize.new(1),
TextDocumentDidOpen.new("file:///path/to/file.rb", <<~RUBY),
a = 1
RUBY
TextDocumentDiagnostic.new(2, "file:///path/to/file.rb"),
Shutdown.new(3)
])
shape = Request[[
{ id: 1, result: { capabilities: Hash } },
{ id: 2, result: { kind: "full", items: [] } },
{ id: 3, result: {} }
]]
assert_operator(shape, :===, responses)
assert_equal(0, responses.dig(1, :result, :items).size)
end
def test_diagnostics_request_no_content
responses = run_server([
Initialize.new(1),
TextDocumentDiagnostic.new(2, "file:///path/to/file.rb"),
Shutdown.new(3)
])
shape = Request[[
{ id: 1, result: { capabilities: Hash } },
{ id: 2, result: nil },
{ id: 3, result: {} }
]]
assert_operator(shape, :===, responses)
end
private
def write(content)
request = content.to_hash.merge(jsonrpc: "2.0").to_json
"Content-Length: #{request.bytesize}\r\n\r\n#{request}"
end
def read(content)
[].tap do |messages|
while (headers = content.gets("\r\n\r\n"))
source = content.read(headers[/Content-Length: (\d+)/i, 1].to_i)
messages << JSON.parse(source, symbolize_names: true)
end
end
end
def run_server(messages)
input = StringIO.new(messages.map { |message| write(message) }.join)
output = StringIO.new
LanguageServer.new(
input: input,
output: output,
).run
read(output.tap(&:rewind))
end
end
end