Class: RubyLsp::LspReporter
- Inherits:
-
Object
- Object
- RubyLsp::LspReporter
- Includes:
- Singleton
- Defined in:
- lib/ruby_lsp/test_reporters/lsp_reporter.rb
Class Method Summary collapse
-
.executed_under_test_runner? ⇒ Boolean
: -> bool.
-
.start_coverage? ⇒ Boolean
: -> bool.
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.
-
#uri_and_line_for(method_object) ⇒ Object
: (Method | UnboundMethod) -> [URI::Generic, Integer?]?.
Constructor Details
#initialize ⇒ LspReporter
: -> void
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 28 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 end |
Class Method Details
.executed_under_test_runner? ⇒ Boolean
: -> bool
205 206 207 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 205 def executed_under_test_runner? !!(ENV["RUBY_LSP_TEST_RUNNER"] && ENV["RUBY_LSP_ENV"] != "test") end |
.start_coverage? ⇒ Boolean
: -> bool
200 201 202 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 200 def start_coverage? ENV["RUBY_LSP_TEST_RUNNER"] == "coverage" end |
Instance Method Details
#at_coverage_exit ⇒ Object
: -> void
187 188 189 190 191 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 187 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
194 195 196 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 194 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]
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 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 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 138 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
67 68 69 70 71 72 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 67 def internal_shutdown @invoked_shutdown = true ("finish") @io.close end |
#record_error(id:, message:, uri:) ⇒ Object
: (id: String, message: String?, uri: URI::Generic) -> void
95 96 97 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 95 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
85 86 87 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 85 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
80 81 82 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 80 def record_pass(id:, uri:) ("pass", id: id, uri: uri.to_s) end |
#record_skip(id:, uri:) ⇒ Object
: (id: String, uri: URI::Generic) -> void
90 91 92 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 90 def record_skip(id:, uri:) ("skip", id: id, uri: uri.to_s) end |
#shutdown ⇒ Object
: -> void
56 57 58 59 60 61 62 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 56 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
75 76 77 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 75 def start_test(id:, uri:, line: nil) ("start", id: id, uri: uri.to_s, line: line) end |
#uri_and_line_for(method_object) ⇒ Object
: (Method | UnboundMethod) -> [URI::Generic, Integer?]?
100 101 102 103 104 105 106 107 108 |
# File 'lib/ruby_lsp/test_reporters/lsp_reporter.rb', line 100 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 |