Class: RubyLsp::LspReporter
- Inherits:
-
Object
- Object
- RubyLsp::LspReporter
- Defined in:
- lib/ruby_lsp/test_reporters/lsp_reporter.rb
Class Method Summary collapse
-
.executed_under_test_runner? ⇒ Boolean
: -> bool.
-
.instance ⇒ Object
: -> LspReporter.
-
.start_coverage? ⇒ Boolean
: -> bool.
-
.uri_and_line_for(method_object) ⇒ Object
: (Method | UnboundMethod) -> [URI::Generic, Integer?]?.
Instance Method Summary collapse
-
#at_coverage_exit ⇒ Object
: -> void.
-
#at_exit ⇒ Object
: -> void.
-
#gather_coverage_results ⇒ Object
Gather the results returned by Coverage.result and format like the VS Code test explorer expects.
-
#initialize ⇒ LspReporter
constructor
: -> void.
-
#internal_shutdown ⇒ Object
This method is intended to be used by the RubyLsp::LspReporter class itself only.
-
#record_error(id:, message:, uri:) ⇒ Object
: (id: String, message: String?, uri: URI::Generic) -> void.
-
#record_fail(id:, message:, uri:) ⇒ Object
: (id: String, message: String, uri: URI::Generic) -> void.
-
#record_pass(id:, uri:) ⇒ Object
: (id: String, uri: URI::Generic) -> void.
-
#record_skip(id:, uri:) ⇒ Object
: (id: String, uri: URI::Generic) -> void.
-
#shutdown ⇒ Object
: -> void.
-
#start_test(id:, uri:, line: nil) ⇒ Object
: (id: String, uri: URI::Generic, ?line: Integer?) -> void.
Constructor Details
#initialize ⇒ LspReporter
: -> void
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 55 def initialize dir_path = File.join(Dir.tmpdir, "ruby-lsp") FileUtils.mkdir_p(dir_path) port_db_path = File.join(dir_path, "test_reporter_port_db.json") port = ENV["RUBY_LSP_REPORTER_PORT"] @io = begin # The environment variable is only used for tests. The extension always writes to the temporary file if port socket(port) elsif File.exist?(port_db_path) db = JSON.load_file(port_db_path) socket(db[Dir.pwd]) else # For tests that don't spawn the TCP server require "stringio" StringIO.new end rescue require "stringio" StringIO.new end #: IO | StringIO @invoked_shutdown = false #: bool = Thread::Queue.new #: Thread::Queue @writer = Thread.new { write_loop } #: Thread end |
Class Method Details
.executed_under_test_runner? ⇒ Boolean
: -> bool
26 27 28 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 26 def executed_under_test_runner? !!(ENV["RUBY_LSP_TEST_RUNNER"] && ENV["RUBY_LSP_ENV"] != "test") end |
.instance ⇒ Object
: -> LspReporter
16 17 18 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 16 def instance @instance ||= new end |
.start_coverage? ⇒ Boolean
: -> bool
21 22 23 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 21 def start_coverage? ENV["RUBY_LSP_TEST_RUNNER"] == "coverage" end |
.uri_and_line_for(method_object) ⇒ Object
: (Method | UnboundMethod) -> [URI::Generic, Integer?]?
31 32 33 34 35 36 37 38 39 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 31 def uri_and_line_for(method_object) file_path, line = method_object.source_location return unless file_path return if file_path.start_with?("(eval at ") uri = URI::Generic.from_path(path: File.(file_path)) zero_based_line = line ? line - 1 : nil [uri, zero_based_line] end |
Instance Method Details
#at_coverage_exit ⇒ Object
: -> void
207 208 209 210 211 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 207 def at_coverage_exit coverage_results = gather_coverage_results File.write(File.join(".ruby-lsp", "coverage_result.json"), coverage_results.to_json) internal_shutdown end |
#at_exit ⇒ Object
: -> void
214 215 216 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 214 def at_exit internal_shutdown unless @invoked_shutdown end |
#gather_coverage_results ⇒ Object
Gather the results returned by Coverage.result and format like the VS Code test explorer expects
Coverage result format:
Lines are reported in order as an array where each number is the number of times it was executed. For example, the following says that line 0 was executed 1 time and line 1 executed 3 times: [1, 3]. Nil values represent lines for which coverage is not available, like empty lines, comments or keywords like else
Branches are a hash containing the name of the branch and the location where it is found in tuples with the following elements: [NAME, ID, START_LINE, START_COLUMN, END_LINE, END_COLUMN] as the keys and the value is the number of times it was executed
Methods are a similar hash [ClassName, :method_name, START_LINE, START_COLUMN, END_LINE, END_COLUMN] => NUMBER OF EXECUTIONS
Example: {
"file_path" => {
"lines" => [1, 2, 3, nil],
"branches" => {
["&.", 0, 6, 21, 6, 65] => { [:then, 1, 6, 21, 6, 65] => 0, [:else, 5, 7, 0, 7, 87] => 1 }
},
"methods" => {
["Foo", :bar, 6, 21, 6, 65] => 0
}
} : -> Hash[String, statement_coverage]
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 158 def gather_coverage_results # Ignore coverage results inside dependencies bundle_path = Bundler.bundle_path.to_s result = Coverage.result.reject do |file_path, _coverage_info| file_path.start_with?(bundle_path) || !file_path.start_with?(Dir.pwd) end result.to_h do |file_path, coverage_info| # Format the branch coverage information as VS Code expects it and then group it based on the start line of # the conditional that causes the branching. We need to match each line coverage data with the branches that # spawn from that line branch_by_line = coverage_info[:branches] .flat_map do |branch, data| branch_name, _branch_id, branch_start_line, _branch_start_col, _branch_end_line, _branch_end_col = branch data.map do |then_or_else, execution_count| name, _id, start_line, start_column, end_line, end_column = then_or_else { groupingLine: branch_start_line, executed: execution_count, location: { start: { line: start_line, character: start_column }, end: { line: end_line, character: end_column }, }, label: "#{branch_name} #{name}", } end end .group_by { |branch| branch[:groupingLine] } # Format the line coverage information, gathering any branch coverage data associated with that line data = coverage_info[:lines].filter_map.with_index do |execution_count, line_index| next if execution_count.nil? { executed: execution_count, location: { line: line_index, character: 0 }, branches: branch_by_line[line_index] || [], } end # The expected format is URI => { executed: number_of_times_executed, location: { ... }, branches: [ ... ] } [URI::Generic.from_path(path: File.(file_path)).to_s, data] end end |
#internal_shutdown ⇒ Object
This method is intended to be used by the RubyLsp::LspReporter class itself only. If you’re writing a custom test reporter, use shutdown instead : -> void
96 97 98 99 100 101 102 103 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 96 def internal_shutdown @invoked_shutdown = true ("finish") .close @writer.join @io.close end |
#record_error(id:, message:, uri:) ⇒ Object
: (id: String, message: String?, uri: URI::Generic) -> void
126 127 128 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 126 def record_error(id:, message:, uri:) ("error", id: id, message: , uri: uri.to_s) end |
#record_fail(id:, message:, uri:) ⇒ Object
: (id: String, message: String, uri: URI::Generic) -> void
116 117 118 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 116 def record_fail(id:, message:, uri:) ("fail", id: id, message: , uri: uri.to_s) end |
#record_pass(id:, uri:) ⇒ Object
: (id: String, uri: URI::Generic) -> void
111 112 113 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 111 def record_pass(id:, uri:) ("pass", id: id, uri: uri.to_s) end |
#record_skip(id:, uri:) ⇒ Object
: (id: String, uri: URI::Generic) -> void
121 122 123 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 121 def record_skip(id:, uri:) ("skip", id: id, uri: uri.to_s) end |
#shutdown ⇒ Object
: -> void
85 86 87 88 89 90 91 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 85 def shutdown # When running in coverage mode, we don't want to inform the extension that we finished immediately after running # tests. We only do it after we finish processing coverage results, by invoking `internal_shutdown` return if ENV["RUBY_LSP_TEST_RUNNER"] == "coverage" internal_shutdown end |
#start_test(id:, uri:, line: nil) ⇒ Object
: (id: String, uri: URI::Generic, ?line: Integer?) -> void
106 107 108 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 106 def start_test(id:, uri:, line: nil) ("start", id: id, uri: uri.to_s, line: line) end |